blob: 6e58956963b40e2eb335d6fa1d52877373ed3326 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <lib/zx/event.h>
#include <lib/zx/profile.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <dispatcher-pool/dispatcher-event-source.h>
#include <atomic>
namespace dispatcher {
class ThreadPool;
// class ExecutionDomain
//
// In the dispatcher framework, ExecutionDomains represent a context which
// specific types of EventSources become bound to during activation. While many
// EventSources may have interesting things happening on them simultaneously,
// the ExecutionDomain they are bound to guarantees that only one EventSource's
// handler will be executed at any given point in time.
//
// Once created using the static Create method, an ExecutionDomain is
// immediately Active. New EventSources may be activated on it until the domain
// becomes deactivated, at which point new attempts to activate an event source
// on the execution domain will fail.
//
// Deactivating an execution domain will automatically deactivate all event
// sources currently bound to the domain. When deactivated from outside of the
// context of a dispatch operation, callers use the Deactivate method and will
// be synchronized with any currently in-flight dispatch operations. IOW -
// after Deactivate() returns, all dispatch operations are guaranteed to be
// finished and no new dispatch operations will be started.
//
// If an execution domain needs to be deactivated from the context of a dispatch
// operations, the DeactivateFromWithinDomain method is used instead. No new
// dispatch operations will be started, and the system will be completely
// deactivated when the current in-flight dispatch operation unwinds.
//
class ExecutionDomain : public fbl::RefCounted<ExecutionDomain> {
public:
// Token and ScopedToken are small (empty) objects which are intended to be
// used with the clang static thread analysis framework in order to express
// concurrency guarantees which arise from the serialized-dispatch behavior
// provided by ExecutionsDomains. By obtaining the capability represented
// by an execution domain's token in an event source's processing handler,
// users may assert at compile time that they are executing in handlers in a
// serialized fashion imposed by a particular execution domain. For
// example...
//
// class Thingy {
// ...
// // Require that we be running in my_domain_ in order to call handle
// // channel.
// void HandleChannel(Channel* ch) __TA_REQUIRES(my_domain_.token());
// ...
//
// // This variable can only be changes while running in my_domain_
// uint32_t my_state_ __TA_GUARDED(my_domain_.token());
// }
//
// void Thingy::Activate() {
// Channel::ProcessHandler phandler(
// [thingy = fbl::WrapRefPtr(this)](Channel* ch) {
// // Establish the fact that this callback is running in my_domain_
// ExecutionDomain::ScopedToken token(my_domain_->token());
// thingy->HandleChannel(ch);
// });
//
// my_channel_.Activate(..., std::move(phandler), ...);
//
// my_state_++; // This fails, we are not running in the domain.
// }
//
// void Thingy::HandleChannel(Channel* ch) {
// my_state_++; // This succeeds, we are running in the domain.
// DeactivateFromWithinDomain(); // So does this.
//
// // This fails, we should call not Deactivate from within the domain.
// Deactivate();
// }
//
//
struct __TA_CAPABILITY("role") Token { };
class __TA_SCOPED_CAPABILITY ScopedToken {
public:
explicit ScopedToken(const Token& token) __TA_ACQUIRE(token) { }
~ScopedToken() __TA_RELEASE() { }
};
static fbl::RefPtr<ExecutionDomain> Create(zx::profile profile = {});
void Deactivate() __TA_EXCLUDES(domain_token_) { Deactivate(true); }
void DeactivateFromWithinDomain() __TA_REQUIRES(domain_token_) { Deactivate(false); }
bool deactivated() const __TA_NO_THREAD_SAFETY_ANALYSIS {
return (deactivated_.load() != 0);
}
const Token& token() __TA_RETURN_CAPABILITY(domain_token_) { return domain_token_; }
private:
friend class fbl::RefPtr<ExecutionDomain>;
friend class Channel;
friend class EventSource;
friend class Thread;
friend class ThreadPool;
friend class Timer;
friend class WakeupEvent;
using DispatchState = EventSource::DispatchState;
struct ThreadPoolListTraits {
static fbl::DoublyLinkedListNodeState<fbl::RefPtr<ExecutionDomain>>&
node_state(ExecutionDomain& domain) {
return domain.thread_pool_node_state_;
}
};
ExecutionDomain(fbl::RefPtr<ThreadPool> thread_pool, zx::event dispatch_idle_evt);
virtual ~ExecutionDomain();
void Deactivate(bool sync_dispatch);
fbl::RefPtr<ThreadPool> GetThreadPool() __TA_EXCLUDES(sources_lock_);
zx_status_t AddEventSource(fbl::RefPtr<EventSource>&& source)
__TA_EXCLUDES(sources_lock_);
void RemoveEventSource(EventSource* source)
__TA_EXCLUDES(sources_lock_);
// Add an event source which has pending work to the queue of pending
// work for this owner. Returns true if this was the first pending job
// added to the queue, and therefor the calling thread is responsible
// for processing the contents of the queue now.
bool AddPendingWork(EventSource* source)
__TA_REQUIRES(source->obj_lock_) __TA_EXCLUDES(sources_lock_);
// Attempt to remove an event source from this owner's pending work
// list. Returns true if the source was a member of the list and was
// removed, false otherwise.
bool RemovePendingWork(EventSource* source)
__TA_REQUIRES(source->obj_lock_) __TA_EXCLUDES(sources_lock_);
// Process the pending work queue.
void DispatchPendingWork();
fbl::Mutex sources_lock_;
Token domain_token_;
std::atomic<uint32_t> deactivated_ __TA_GUARDED(sources_lock_);
bool dispatch_in_progress_ __TA_GUARDED(sources_lock_) = false;
bool dispatch_sync_in_progress_ __TA_GUARDED(sources_lock_) = false;
fbl::RefPtr<ThreadPool> thread_pool_ __TA_GUARDED(sources_lock_);
zx::event dispatch_idle_evt_;
// The list of all sources bound to us, as well as the sources which are
// currently waiting to be dispatched.
fbl::DoublyLinkedList<fbl::RefPtr<EventSource>,
EventSource::SourcesListTraits> sources_
__TA_GUARDED(sources_lock_);
fbl::DoublyLinkedList<fbl::RefPtr<EventSource>,
EventSource::PendingWorkListTraits> pending_work_
__TA_GUARDED(sources_lock_);
// Node state for existing in our thread pool's execution domain list.
fbl::DoublyLinkedListNodeState<fbl::RefPtr<ExecutionDomain>> thread_pool_node_state_;
};
// A helper macro which can ease some of the namespace pain of establishing the
// fact that you are running in a particular execution domain. Instead of
// saying something like this...
//
// ::dispatcher::ExecutionDomain::ScopedToken token(my_domain_->token());
//
// One can also say...
//
// OBTAIN_EXECUTION_DOMAIN_TOKEN(token, my_domain_);
//
#define OBTAIN_EXECUTION_DOMAIN_TOKEN(_sym_name, _exe_domain) \
::dispatcher::ExecutionDomain::ScopedToken _sym_name((_exe_domain)->token())
} // namespace dispatcher