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