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