blob: 5287fc551673e628e8aa938f012e31c3e8d76cce [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <fbl/auto_call.h>
#include <dispatcher-pool/dispatcher-event-source.h>
#include <dispatcher-pool/dispatcher-execution-domain.h>
#include <dispatcher-pool/dispatcher-thread-pool.h>
#include <utility>
namespace dispatcher {
EventSource::EventSource(zx_signals_t process_signal_mask)
: process_signal_mask_(process_signal_mask) { }
EventSource::~EventSource() {
ZX_DEBUG_ASSERT(domain_ == nullptr);
ZX_DEBUG_ASSERT(!InExecutionDomain());
ZX_DEBUG_ASSERT(!InPendingList());
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
}
void EventSource::InternalDeactivateLocked() {
// If we are no longer active, we can just get out now. We should be able
// to assert that our handle has been closed and that we are in either the
// Idle or Dispatching state, or that handle is still valid and we are in
// WaitingOnPort state (meaning that there is a thread in flight from the
// thread pool which is about to realize that we have become deactivated)
if (!is_active()) {
ZX_DEBUG_ASSERT(
( handle_.is_valid() && (dispatch_state() == DispatchState::WaitingOnPort)) ||
(!handle_.is_valid() && ((dispatch_state() == DispatchState::Dispatching) ||
(dispatch_state() == DispatchState::Idle))));
return;
}
// Attempt to cancel any pending operations. Do not close the handle if it
// was too late to cancel and we are still waiting on the port.
CancelPendingLocked();
if (dispatch_state() != DispatchState::WaitingOnPort) {
ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) ||
(dispatch_state() == DispatchState::Dispatching));
handle_.reset();
}
// If we still have a domain, remove ourselves from the domain's event
// source list, then release our reference to it.
if (domain_ != nullptr) {
domain_->RemoveEventSource(this);
domain_ = nullptr;
}
// Release our cached thread pool reference.
thread_pool_.reset();
}
zx_status_t EventSource::ActivateLocked(zx::handle handle, fbl::RefPtr<ExecutionDomain> domain) {
if ((domain == nullptr) || !handle.is_valid())
return ZX_ERR_INVALID_ARGS;
if (is_active() || handle_.is_valid())
return ZX_ERR_BAD_STATE;
ZX_DEBUG_ASSERT(thread_pool_ == nullptr);
auto thread_pool = domain->GetThreadPool();
if (thread_pool == nullptr)
return ZX_ERR_BAD_STATE;
// Add ourselves to our domain's list of event sources.
zx_status_t res = domain->AddEventSource(fbl::WrapRefPtr(this));
if (res != ZX_OK)
return res;
handle_ = std::move(handle);
domain_ = std::move(domain);
thread_pool_ = std::move(thread_pool);
return ZX_OK;
}
zx_status_t EventSource::WaitOnPortLocked() {
// If we are attempting to wait, we should not already have a wait pending.
// In particular, we need to be in the idle state.
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
// Attempting to wait when our domain is null indicates that we are in the
// process of dying, and the wait should be denied.
if (!is_active())
return ZX_ERR_BAD_STATE;
zx_status_t res = DoPortWaitLocked();
// If the wait async succeeded, then we now have a pending wait operation,
// and the kernel is now holding an unmanaged reference to us. Flag the
// pending wait, and manually bump our ref count.
if (res == ZX_OK) {
dispatch_state_ = DispatchState::WaitingOnPort;
this->AddRef();
}
return res;
}
zx_status_t EventSource::CancelPendingLocked() {
// If we are still active, remove ourselves from the domain's
// pending work list.
if (is_active()) {
// If we were on the pending work list, then our state must have been
// DispatchPending (and now should be Idle)
if (domain_->RemovePendingWork(this)) {
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::DispatchPending);
dispatch_state_ = DispatchState::Idle;
}
// If there is a wait operation currently pending, attempt to cancel it.
//
// If we succeed, manually drop the unmanaged reference which the kernel
// was holding and transition to the Idle state.
//
// If we fail, it must be because the wait has completed and is being
// dispatched on another thread. Do not transition to Idle, or release
// the kernel reference.
if (dispatch_state() == DispatchState::WaitingOnPort) {
zx_status_t res = DoPortCancelLocked();
if (res == ZX_OK) {
__UNUSED bool should_destruct;
dispatch_state_ = DispatchState::Idle;
should_destruct = this->Release();
ZX_DEBUG_ASSERT(should_destruct == false);
} else {
ZX_DEBUG_ASSERT(res == ZX_ERR_NOT_FOUND);
}
}
}
return (dispatch_state() == DispatchState::Idle) ? ZX_OK : ZX_ERR_BAD_STATE;
}
zx_status_t EventSource::DoPortWaitLocked() {
ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
return thread_pool_->WaitOnPort(handle_,
reinterpret_cast<uint64_t>(this),
process_signal_mask(),
ZX_WAIT_ASYNC_ONCE);
}
zx_status_t EventSource::DoPortCancelLocked() {
ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
return thread_pool_->CancelWaitOnPort(handle_, reinterpret_cast<uint64_t>(this));
}
bool EventSource::BeginDispatching() {
fbl::AutoLock obj_lock(&obj_lock_);
if (dispatch_state() != DispatchState::DispatchPending)
return false;
ZX_DEBUG_ASSERT(InPendingList());
__UNUSED zx_status_t res;
res = CancelPendingLocked();
ZX_DEBUG_ASSERT(res == ZX_OK);
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle);
dispatch_state_ = DispatchState::Dispatching;
return true;
}
fbl::RefPtr<ExecutionDomain> EventSource::ScheduleDispatch(
const zx_port_packet_t& pkt) {
// Something interesting happened. Enter the lock and...
//
// 1) Sanity check our dispatch state. We should be in "WaitingOnPort.
// 2) Assert that something interesting happened.
// 3) If our domain is still active, add this event source to the pending work
// queue. If we are the first event source to enter the queue, return a
// reference to our domain to the dispatcher thread so that it can start
// to process the pending work.
fbl::AutoLock obj_lock(&obj_lock_);
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::WaitingOnPort);
ZX_DEBUG_ASSERT((pkt.type == ZX_PKT_TYPE_INTERRUPT) ||
(pkt.signal.observed & process_signal_mask()));
if (!is_active()) {
// We have no domain, we must have become deactivated while the thread
// from the thread pool was in flight (making it impossible for the
// Deactivator to reclaim the kernel's reference to us). Go ahead and
// transition to idle, and close our handle.
dispatch_state_ = DispatchState::Idle;
handle_.reset();
return nullptr;
}
// Copy the pending port packet to the internal event source storage,
// then add ourselves to the domain's pending queue. If we were the
// first event source to join our domain's pending queue, then take a
// reference to our domain so that we can start to process pending work
// once we have left the obj_lock. If we are not the first to join the
// queue, just get out. The thread which is currently handling pending
// jobs for this domain will handle this one when the time comes.
pending_pkt_ = pkt;
return domain_->AddPendingWork(this) ? domain_ : nullptr;
}
} // namespace dispatcher