1 // Copyright 2017 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #pragma once 6 7 #include <zircon/assert.h> 8 #include <zircon/compiler.h> 9 #include <zircon/types.h> 10 #include <lib/zx/event.h> 11 #include <fbl/intrusive_double_list.h> 12 #include <fbl/mutex.h> 13 #include <fbl/ref_counted.h> 14 #include <fbl/ref_ptr.h> 15 16 #include <dispatcher-pool/dispatcher-event-source.h> 17 18 #include <atomic> 19 20 namespace dispatcher { 21 22 class ThreadPool; 23 24 // class ExecutionDomain 25 // 26 // In the dispatcher framework, ExecutionDomains represent a context which 27 // specific types of EventSources become bound to during activation. While many 28 // EventSources may have interesting things happening on them simultaniously, 29 // the ExecutionDomain they are bound to guarantees that only one EventSource's 30 // handler will be executed at any given point in time. 31 // 32 // Once created using the static Create method, an ExecutionDomain is 33 // immediately Active. New EventSources may be activated on it until the domain 34 // becomes deactivated, at which point new attempts to activate an event source 35 // on the execution domain will fail. 36 // 37 // Deactivating an execution domain will automatically deactivate all event 38 // sources currently bound to the domain. When deactivated from outside of the 39 // context of a dispatch operation, callers use the Deactivate method and will 40 // be synchronized with any currently in-flight dispatch operations. IOW - 41 // after Deactivate() returns, all dispatch operatons are guaranteed to be 42 // finished and no new dispatch operations will be started. 43 // 44 // If an execution domain needs to be deactivated from the context of a dispatch 45 // operations, the DeactivateFromWithinDomain method is used instead. No new 46 // dispatch operations will be started, and the system will be completely 47 // deactivated when the current in-flight dispatch operation unwinds. 48 // 49 class ExecutionDomain : public fbl::RefCounted<ExecutionDomain> { 50 public: 51 // Token and ScopedToken are small (empty) objects which are intended to be 52 // used with the clang static thread analysis framework in order to express 53 // concurrency guarantees which arise from the serialized-dispatch behavior 54 // provided by ExecutionsDomains. By obtaining the capability represented 55 // by an execution domain's token in an event source's processing handler, 56 // users may assert at compile time that they are executing in handlers in a 57 // serialized fashion imposed by a particular execution domain. For 58 // example... 59 // 60 // class Thingy { 61 // ... 62 // // Require that we be running in my_domain_ in order to call handle 63 // // channel. 64 // void HandleChannel(Channel* ch) __TA_REQUIRES(my_domain_.token()); 65 // ... 66 // 67 // // This variable can only be changes while running in my_domain_ 68 // uint32_t my_state_ __TA_GUARDED(my_domain_.token()); 69 // } 70 // 71 // void Thingy::Activate() { 72 // Channel::ProcessHandler phandler( 73 // [thingy = fbl::WrapRefPtr(this)](Channel* ch) { 74 // // Establish the fact that this callback is running in my_domain_ 75 // ExecutionDomain::ScopedToken token(my_domain_->token()); 76 // thingy->HandleChannel(ch); 77 // }); 78 // 79 // my_channel_.Activate(..., std::move(phandler), ...); 80 // 81 // my_state_++; // This fails, we are not running in the domain. 82 // } 83 // 84 // void Thingy::HandleChannel(Channel* ch) { 85 // my_state_++; // This succeeds, we are running in the domain. 86 // DeactivateFromWithinDomain(); // So does this. 87 // 88 // // This fails, we should call not Deactivate from within the domain. 89 // Deactivate(); 90 // } 91 // 92 // 93 struct __TA_CAPABILITY("role") Token { }; 94 class __TA_SCOPED_CAPABILITY ScopedToken { 95 public: ScopedToken(const Token & token)96 explicit ScopedToken(const Token& token) __TA_ACQUIRE(token) { } __TA_RELEASE()97 ~ScopedToken() __TA_RELEASE() { } 98 }; 99 100 static constexpr uint32_t DEFAULT_PRIORITY = 16; 101 static fbl::RefPtr<ExecutionDomain> Create(uint32_t priority = DEFAULT_PRIORITY); 102 Deactivate()103 void Deactivate() __TA_EXCLUDES(domain_token_) { Deactivate(true); } DeactivateFromWithinDomain()104 void DeactivateFromWithinDomain() __TA_REQUIRES(domain_token_) { Deactivate(false); } 105 deactivated()106 bool deactivated() const __TA_NO_THREAD_SAFETY_ANALYSIS { 107 return (deactivated_.load() != 0); 108 } 109 token()110 const Token& token() __TA_RETURN_CAPABILITY(domain_token_) { return domain_token_; } 111 112 private: 113 friend class fbl::RefPtr<ExecutionDomain>; 114 friend class Channel; 115 friend class EventSource; 116 friend class Thread; 117 friend class ThreadPool; 118 friend class Timer; 119 friend class WakeupEvent; 120 using DispatchState = EventSource::DispatchState; 121 122 struct ThreadPoolListTraits { 123 static fbl::DoublyLinkedListNodeState<fbl::RefPtr<ExecutionDomain>>& node_stateThreadPoolListTraits124 node_state(ExecutionDomain& domain) { 125 return domain.thread_pool_node_state_; 126 } 127 }; 128 129 ExecutionDomain(fbl::RefPtr<ThreadPool> thread_pool, zx::event dispatch_idle_evt); 130 virtual ~ExecutionDomain(); 131 132 void Deactivate(bool sync_dispatch); 133 134 fbl::RefPtr<ThreadPool> GetThreadPool() __TA_EXCLUDES(sources_lock_); 135 zx_status_t AddEventSource(fbl::RefPtr<EventSource>&& source) 136 __TA_EXCLUDES(sources_lock_); 137 void RemoveEventSource(EventSource* source) 138 __TA_EXCLUDES(sources_lock_); 139 140 // Add an event source which has pending work to the queue of pending 141 // work for this owner. Returns true if this was the first pending job 142 // added to the queue, and therefor the calling thread is responsible 143 // for processing the contents of the queue now. 144 bool AddPendingWork(EventSource* source) 145 __TA_REQUIRES(source->obj_lock_) __TA_EXCLUDES(sources_lock_); 146 147 // Attempt to remove an event source from this owner's pending work 148 // list. Returns true if the source was a member of the list and was 149 // removed, false otherwise. 150 bool RemovePendingWork(EventSource* source) 151 __TA_REQUIRES(source->obj_lock_) __TA_EXCLUDES(sources_lock_); 152 153 // Process the pending work queue. 154 void DispatchPendingWork(); 155 156 fbl::Mutex sources_lock_; 157 Token domain_token_; 158 std::atomic<uint32_t> deactivated_ __TA_GUARDED(sources_lock_); 159 bool dispatch_in_progress_ __TA_GUARDED(sources_lock_) = false; 160 bool dispatch_sync_in_progress_ __TA_GUARDED(sources_lock_) = false; 161 fbl::RefPtr<ThreadPool> thread_pool_ __TA_GUARDED(sources_lock_); 162 zx::event dispatch_idle_evt_; 163 164 // The list of all sources bound to us, as well as the sources which are 165 // currently waiting to be dispatched. 166 fbl::DoublyLinkedList<fbl::RefPtr<EventSource>, 167 EventSource::SourcesListTraits> sources_ 168 __TA_GUARDED(sources_lock_); 169 fbl::DoublyLinkedList<fbl::RefPtr<EventSource>, 170 EventSource::PendingWorkListTraits> pending_work_ 171 __TA_GUARDED(sources_lock_); 172 173 // Node state for existing in our thread pool's execution domain list. 174 fbl::DoublyLinkedListNodeState<fbl::RefPtr<ExecutionDomain>> thread_pool_node_state_; 175 }; 176 177 // A helper macro which can ease some of the namespace pain of establishing the 178 // fact that you are running in a particular execution domain. Instead of 179 // saying something like this... 180 // 181 // ::dispatcher::ExecutionDomain::ScopedToken token(my_domain_->token()); 182 // 183 // One can also say... 184 // 185 // OBTAIN_EXECUTION_DOMAIN_TOKEN(token, my_domain_); 186 // 187 #define OBTAIN_EXECUTION_DOMAIN_TOKEN(_sym_name, _exe_domain) \ 188 ::dispatcher::ExecutionDomain::ScopedToken _sym_name((_exe_domain)->token()) 189 190 } // namespace dispatcher 191