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