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 #include <lib/zx/event.h>
6 
7 #include <dispatcher-pool/dispatcher-execution-domain.h>
8 #include <dispatcher-pool/dispatcher-thread-pool.h>
9 
10 #include <utility>
11 
12 namespace dispatcher {
13 
14 // static
Create(uint32_t priority)15 fbl::RefPtr<ExecutionDomain> ExecutionDomain::Create(uint32_t priority) {
16     zx::event evt;
17     if (zx::event::create(0, &evt) != ZX_OK)
18         return nullptr;
19 
20     if (evt.signal(0u, ZX_USER_SIGNAL_0) != ZX_OK)
21         return nullptr;
22 
23     fbl::RefPtr<ThreadPool> thread_pool;
24     zx_status_t res = ThreadPool::Get(&thread_pool, priority);
25     if (res != ZX_OK)
26         return nullptr;
27     ZX_DEBUG_ASSERT(thread_pool != nullptr);
28 
29     fbl::AllocChecker ac;
30     auto new_domain = fbl::AdoptRef(new (&ac) ExecutionDomain(thread_pool, std::move(evt)));
31     if (!ac.check())
32         return nullptr;
33 
34     res = thread_pool->AddDomainToPool(new_domain);
35     if (res != ZX_OK)
36         return nullptr;
37 
38     return new_domain;
39 }
40 
ExecutionDomain(fbl::RefPtr<ThreadPool> thread_pool,zx::event dispatch_idle_evt)41 ExecutionDomain::ExecutionDomain(fbl::RefPtr<ThreadPool> thread_pool,
42                                  zx::event dispatch_idle_evt)
43     : deactivated_(0),
44       thread_pool_(std::move(thread_pool)),
45       dispatch_idle_evt_(std::move(dispatch_idle_evt)) {
46     ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
47     ZX_DEBUG_ASSERT(dispatch_idle_evt_.is_valid());
48 }
49 
~ExecutionDomain()50 ExecutionDomain::~ExecutionDomain() {
51     // Assert that the Owner implementation properly deactivated itself
52     // before destructing.
53     ZX_DEBUG_ASSERT(deactivated());
54     ZX_DEBUG_ASSERT(sources_.is_empty());
55     ZX_DEBUG_ASSERT(!thread_pool_node_state_.InContainer());
56 }
57 
Deactivate(bool sync_dispatch)58 void ExecutionDomain::Deactivate(bool sync_dispatch) {
59     // Flag ourselves as deactivated.  This will prevent any new event sources
60     // from being added to the sources_ list.  We can then swap the contents of
61     // the sources_ list with a temp list, leave the lock and deactivate all of
62     // the sources at our leisure.
63     fbl::DoublyLinkedList<fbl::RefPtr<EventSource>, EventSource::SourcesListTraits> to_deactivate;
64     bool sync_needed = false;
65 
66     {
67         fbl::AutoLock sources_lock(&sources_lock_);
68         if (deactivated()) {
69             ZX_DEBUG_ASSERT(sources_.is_empty());
70         } else {
71             deactivated_.store(1u);
72             to_deactivate.swap(sources_);
73         }
74 
75         // If there are dispatch operations currently in flight, clear the
76         // dispatch idle event and set the flag indicating to the dispatch
77         // operation that it needs to set the event when it finishes.
78         if (dispatch_in_progress_) {
79             sync_needed = true;
80             if (!dispatch_sync_in_progress_) {
81                 __UNUSED zx_status_t res;
82                 dispatch_sync_in_progress_ = true;
83                 res = dispatch_idle_evt_.signal(ZX_USER_SIGNAL_0, 0u);
84                 ZX_DEBUG_ASSERT(res == ZX_OK);
85             }
86         }
87     }
88 
89     // Now deactivate all of our event sources and release all of our references.
90     if (!to_deactivate.is_empty()) {
91         for (auto& source : to_deactivate) {
92             source.Deactivate();
93         }
94         to_deactivate.clear();
95     }
96 
97     // Synchronize if needed
98     if (sync_needed && sync_dispatch) {
99         __UNUSED zx_status_t res;
100         zx_signals_t pending;
101 
102         res = dispatch_idle_evt_.wait_one(ZX_USER_SIGNAL_0, zx::deadline_after(zx::sec(5)), &pending);
103 
104         ZX_DEBUG_ASSERT(res == ZX_OK);
105         ZX_DEBUG_ASSERT((pending & ZX_USER_SIGNAL_0) != 0);
106     }
107 
108     // Finally, exit our thread pool and release our reference to it.
109     decltype(thread_pool_) pool;
110     {
111         fbl::AutoLock sources_lock(&sources_lock_);
112         pool = std::move(thread_pool_);
113     }
114 
115     if (pool != nullptr)
116         pool->RemoveDomainFromPool(this);
117 }
118 
GetThreadPool()119 fbl::RefPtr<ThreadPool> ExecutionDomain::GetThreadPool() {
120     fbl::AutoLock sources_lock(&sources_lock_);
121     return fbl::RefPtr<ThreadPool>(thread_pool_);
122 }
123 
AddEventSource(fbl::RefPtr<EventSource> && event_source)124 zx_status_t ExecutionDomain::AddEventSource(
125         fbl::RefPtr<EventSource>&& event_source) {
126     if (event_source == nullptr)
127         return ZX_ERR_INVALID_ARGS;
128 
129     // This check is a bit sketchy...  This event_source should *never* be in
130     // any ExecutionDomain's event_source list at this point in time, however if
131     // it is, we don't really know what lock we need to obtain to make this
132     // observation atomically.  That said, the check will not mutate any state,
133     // so it should be safe.  It just might not catch a bad situation which
134     // should never happen.
135     ZX_DEBUG_ASSERT(!event_source->InExecutionDomain());
136 
137     // If this ExecutionDomain has become deactivated, then it is not accepting
138     // any new event sources.  Fail the request to add this event_source.
139     fbl::AutoLock sources_lock(&sources_lock_);
140     if (deactivated())
141         return ZX_ERR_BAD_STATE;
142 
143     // We are still active.  Transfer the reference to this event_source to our set
144     // of sources.
145     sources_.push_front(std::move(event_source));
146     return ZX_OK;
147 }
148 
RemoveEventSource(EventSource * event_source)149 void ExecutionDomain::RemoveEventSource(EventSource* event_source) {
150     fbl::AutoLock sources_lock(&sources_lock_);
151 
152     // Has this ExecutionDomain become deactivated?  If so, then this
153     // event_source may still be on a list (the local 'to_deactivate' list in
154     // Deactivate), but it is not in the ExecutionDomain's sources_ list, so
155     // there is nothing to do here.
156     if (deactivated()) {
157         ZX_DEBUG_ASSERT(sources_.is_empty());
158         return;
159     }
160 
161     // If the event_source has not already been removed from the domain's list, do
162     // so now.
163     if (event_source->InExecutionDomain())
164         sources_.erase(*event_source);
165 }
166 
AddPendingWork(EventSource * event_source)167 bool ExecutionDomain::AddPendingWork(EventSource* event_source) {
168     ZX_DEBUG_ASSERT(event_source != nullptr);
169     ZX_DEBUG_ASSERT(!event_source->InPendingList());
170     ZX_DEBUG_ASSERT(event_source->dispatch_state() == DispatchState::WaitingOnPort);
171 
172     // If this ExecutionDomain has become deactivated, then it is not accepting
173     // any new pending work.   Do not add the source to the pending work queue,
174     // and do not tell the caller that it should be processing the queue when we
175     // return.  The event source is now in the Idle state.
176     fbl::AutoLock sources_lock(&sources_lock_);
177     if (deactivated()) {
178         event_source->dispatch_state_ = DispatchState::Idle;
179         return false;
180     }
181 
182     // Add this event source to the back of the pending work queue, and tell the
183     // caller whether or not it is responsible for processing the queue.
184     bool ret = !dispatch_in_progress_;
185     if (ret) {
186         ZX_DEBUG_ASSERT(pending_work_.is_empty());
187         dispatch_in_progress_ = true;
188     }
189 
190     event_source->dispatch_state_ = DispatchState::DispatchPending;
191     pending_work_.push_back(fbl::WrapRefPtr(event_source));
192 
193     return ret;
194 }
195 
RemovePendingWork(EventSource * event_source)196 bool ExecutionDomain::RemovePendingWork(EventSource* event_source) {
197     ZX_DEBUG_ASSERT(event_source != nullptr);
198 
199     fbl::AutoLock sources_lock(&sources_lock_);
200     if (!event_source->InPendingList())
201         return false;
202 
203     // If we were on the pending list, then our state must be DispatchPending;
204     ZX_DEBUG_ASSERT(event_source->dispatch_state() == DispatchState::DispatchPending);
205     pending_work_.erase(*event_source);
206     return true;
207 }
208 
DispatchPendingWork()209 void ExecutionDomain::DispatchPendingWork() {
210     // While we have work waiting in the pending queue, dispatch it.
211     //
212     // TODO(johngro) : To prevent starvation issues, we should probably only
213     // perform a finite amount of work, and unwind out into the port wait
214     // operation to give other event source owners a chance if this ends up
215     // going on for too long.
216     while (true) {
217         // Enter the sources lock and take a reference to the front of the
218         // pending queue.  If the pending work queue is empty, or we have been
219         // deactivated, we are finished.
220         fbl::RefPtr<EventSource> source;
221         {
222             fbl::AutoLock sources_lock(&sources_lock_);
223             ZX_DEBUG_ASSERT(dispatch_in_progress_);
224             if (deactivated() || pending_work_.is_empty()) {
225                 // Clear the pending work queue and the dispatch in progress
226                 // flag.  If someone is attempting to synchronize with dispatch
227                 // operations in flight, set the event indicating that we are
228                 // now idle.
229                 pending_work_.clear();
230                 dispatch_in_progress_ = false;
231                 if (dispatch_sync_in_progress_) {
232                     __UNUSED zx_status_t res;
233                     res = dispatch_idle_evt_.signal(0u, ZX_USER_SIGNAL_0);
234                     ZX_DEBUG_ASSERT(res == ZX_OK);
235                 }
236                 return;
237             }
238 
239             source = pending_work_.begin().CopyPointer();
240         }
241 
242         // Attempt to transition to the Dispatching state.  If this fails, it
243         // means that we were canceled after we left the sources_lock_ but
244         // before we managed to re-enter both the EventSource's object lock and
245         // the execution domain's sources lock.  If this is the case, just move
246         // on to the next pending source.
247         ZX_DEBUG_ASSERT(source != nullptr);
248         if (source->BeginDispatching())
249             source->Dispatch(this);
250     }
251 }
252 
253 }  // namespace dispatcher
254