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