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