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 <zircon/syscalls.h>
6#include <zircon/types.h>
7#include <fbl/auto_call.h>
8
9#include <dispatcher-pool/dispatcher-event-source.h>
10#include <dispatcher-pool/dispatcher-execution-domain.h>
11#include <dispatcher-pool/dispatcher-thread-pool.h>
12
13namespace dispatcher {
14
15EventSource::EventSource(zx_signals_t process_signal_mask)
16    : process_signal_mask_(process_signal_mask) { }
17
18EventSource::~EventSource() {
19    ZX_DEBUG_ASSERT(domain_ == nullptr);
20    ZX_DEBUG_ASSERT(!InExecutionDomain());
21    ZX_DEBUG_ASSERT(!InPendingList());
22    ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
23}
24
25void EventSource::InternalDeactivateLocked() {
26    // If we are no longer active, we can just get out now.  We should be able
27    // to assert that our handle has been closed and that we are in either the
28    // Idle or Dispatching state, or that handle is still valid and we are in
29    // WaitingOnPort state (meaning that there is a thread in flight from the
30    // thread pool which is about to realize that we have become deactivated)
31    if (!is_active()) {
32        ZX_DEBUG_ASSERT(
33                ( handle_.is_valid() &&  (dispatch_state() == DispatchState::WaitingOnPort)) ||
34                (!handle_.is_valid() && ((dispatch_state() == DispatchState::Dispatching) ||
35                                         (dispatch_state() == DispatchState::Idle))));
36        return;
37    }
38
39    // Attempt to cancel any pending operations.  Do not close the handle if it
40    // was too late to cancel and we are still waiting on the port.
41    CancelPendingLocked();
42    if (dispatch_state() != DispatchState::WaitingOnPort) {
43        ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) ||
44                        (dispatch_state() == DispatchState::Dispatching));
45        handle_.reset();
46    }
47
48    // If we still have a domain, remove ourselves from the domain's event
49    // source list, then release our reference to it.
50    if (domain_ != nullptr) {
51        domain_->RemoveEventSource(this);
52        domain_ = nullptr;
53    }
54
55    // Release our cached thread pool reference.
56    thread_pool_.reset();
57}
58
59zx_status_t EventSource::ActivateLocked(zx::handle handle, fbl::RefPtr<ExecutionDomain> domain) {
60    if ((domain == nullptr) || !handle.is_valid())
61        return ZX_ERR_INVALID_ARGS;
62
63    if (is_active() || handle_.is_valid())
64        return ZX_ERR_BAD_STATE;
65    ZX_DEBUG_ASSERT(thread_pool_ == nullptr);
66
67    auto thread_pool = domain->GetThreadPool();
68    if (thread_pool == nullptr)
69        return ZX_ERR_BAD_STATE;
70
71    // Add ourselves to our domain's list of event sources.
72    zx_status_t res = domain->AddEventSource(fbl::WrapRefPtr(this));
73    if (res != ZX_OK)
74        return res;
75
76    handle_ = fbl::move(handle);
77    domain_ = fbl::move(domain);
78    thread_pool_ = fbl::move(thread_pool);
79
80    return ZX_OK;
81}
82
83zx_status_t EventSource::WaitOnPortLocked() {
84    // If we are attempting to wait, we should not already have a wait pending.
85    // In particular, we need to be in the idle state.
86    ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
87
88    // Attempting to wait when our domain is null indicates that we are in the
89    // process of dying, and the wait should be denied.
90    if (!is_active())
91        return ZX_ERR_BAD_STATE;
92
93    zx_status_t res = DoPortWaitLocked();
94
95    // If the wait async succeeded, then we now have a pending wait operation,
96    // and the kernel is now holding an unmanaged reference to us.  Flag the
97    // pending wait, and manually bump our ref count.
98    if (res == ZX_OK) {
99        dispatch_state_ = DispatchState::WaitingOnPort;
100        this->AddRef();
101    }
102
103    return res;
104}
105
106zx_status_t EventSource::CancelPendingLocked() {
107    // If we are still active, remove ourselves from the domain's
108    // pending work list.
109    if (is_active()) {
110        // If we were on the pending work list, then our state must have been
111        // DispatchPending (and now should be Idle)
112        if (domain_->RemovePendingWork(this)) {
113            ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::DispatchPending);
114            dispatch_state_ = DispatchState::Idle;
115        }
116
117        // If there is a wait operation currently pending, attempt to cancel it.
118        //
119        // If we succeed, manually drop the unmanaged reference which the kernel
120        // was holding and transition to the Idle state.
121        //
122        // If we fail, it must be because the wait has completed and is being
123        // dispatched on another thread.  Do not transition to Idle, or release
124        // the kernel reference.
125        if (dispatch_state() == DispatchState::WaitingOnPort) {
126            zx_status_t res = DoPortCancelLocked();
127
128            if (res == ZX_OK) {
129                __UNUSED bool should_destruct;
130
131                dispatch_state_ = DispatchState::Idle;
132                should_destruct = this->Release();
133
134                ZX_DEBUG_ASSERT(should_destruct == false);
135            } else {
136                ZX_DEBUG_ASSERT(res == ZX_ERR_NOT_FOUND);
137            }
138        }
139    }
140
141    return (dispatch_state() == DispatchState::Idle) ? ZX_OK : ZX_ERR_BAD_STATE;
142}
143
144zx_status_t EventSource::DoPortWaitLocked() {
145    ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
146    return thread_pool_->WaitOnPort(handle_,
147                                    reinterpret_cast<uint64_t>(this),
148                                    process_signal_mask(),
149                                    ZX_WAIT_ASYNC_ONCE);
150}
151
152zx_status_t EventSource::DoPortCancelLocked() {
153    ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
154    return thread_pool_->CancelWaitOnPort(handle_, reinterpret_cast<uint64_t>(this));
155}
156
157bool EventSource::BeginDispatching() {
158    fbl::AutoLock obj_lock(&obj_lock_);
159    if (dispatch_state() != DispatchState::DispatchPending)
160        return false;
161
162    ZX_DEBUG_ASSERT(InPendingList());
163
164    __UNUSED zx_status_t res;
165    res = CancelPendingLocked();
166    ZX_DEBUG_ASSERT(res == ZX_OK);
167    ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
168
169    dispatch_state_ = DispatchState::Dispatching;
170
171    return true;
172}
173
174fbl::RefPtr<ExecutionDomain> EventSource::ScheduleDispatch(
175        const zx_port_packet_t& pkt) {
176    // Something interesting happened.  Enter the lock and...
177    //
178    // 1) Sanity check, then reset wait_pending_.  There is no longer a wait pending.
179    // 2) Assert that something interesting happened.  If none of the
180    //    interesting things which happened are in the process_signal_mask_, then
181    //    just return.  The dispatcher thread will deactive us.
182    // 3) If our domain is still active, add this event source to the pending work
183    //    queue.  If we are the first event source to enter the queue, return a
184    //    reference to our domain to the dispatcher thread so that it can start
185    //    to process the pending work.
186    fbl::AutoLock obj_lock(&obj_lock_);
187
188    ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::WaitingOnPort);
189
190    ZX_DEBUG_ASSERT((pkt.type == ZX_PKT_TYPE_INTERRUPT) ||
191                    (pkt.signal.observed & process_signal_mask()));
192
193    if (domain_ == nullptr) {
194        dispatch_state_ = DispatchState::Idle;
195        return nullptr;
196    }
197
198    // Copy the pending port packet to the internal event source storage,
199    // then add ourselves to the domain's pending queue.  If we were the
200    // first event source to join our domain's pending queue, then take a
201    // reference to our domain so that we can start to process pending work
202    // once we have left the obj_lock.  If we are not the first to join the
203    // queue, just get out.  The thread which is currently handling pending
204    // jobs for this domain will handle this one when the time comes.
205    pending_pkt_ = pkt;
206    return domain_->AddPendingWork(this) ? domain_ : nullptr;
207}
208
209}  // namespace dispatcher
210