blob: 1b6cd6d5b11b9b212b4a7659b806bbba99053bb5 [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 <lib/zx/timer.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <dispatcher-pool/dispatcher-execution-domain.h>
#include <dispatcher-pool/dispatcher-timer.h>
#include <utility>
namespace dispatcher {
// static
fbl::RefPtr<Timer> Timer::Create(zx_time_t early_slop_nsec) {
fbl::AllocChecker ac;
auto ptr = new (&ac) Timer(early_slop_nsec);
if (!ac.check())
return nullptr;
return fbl::AdoptRef(ptr);
}
zx_status_t Timer::Activate(fbl::RefPtr<ExecutionDomain> domain,
ProcessHandler process_handler,
uint32_t slack_type) {
if (process_handler == nullptr)
return ZX_ERR_INVALID_ARGS;
fbl::AutoLock obj_lock(&obj_lock_);
if (is_active() || handle_.is_valid())
return ZX_ERR_BAD_STATE;
zx::timer timer;
zx_status_t res = zx::timer::create(slack_type, ZX_CLOCK_MONOTONIC, &timer);
if (res != ZX_OK)
return res;
// TODO(johngro): Set the early slop time on the timer.
res = ActivateLocked(std::move(timer), std::move(domain));
if (res != ZX_OK)
return res;
process_handler_ = std::move(process_handler);
return ZX_OK;
}
void Timer::Deactivate() {
ProcessHandler old_process_handler;
{
fbl::AutoLock obj_lock(&obj_lock_);
DisarmLocked();
InternalDeactivateLocked();
// If we are in the process of actively dispatching, do not discard our
// handler just yet. It is currently being used by the dispatch thread.
// Instead, wait until the dispatch thread unwinds and allow it to clean
// up the handler.
//
// Otherwise, transfer the handler state into local storage and let it
// destruct after we have released the object lock.
if (dispatch_state() != DispatchState::Dispatching) {
ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) ||
(dispatch_state() == DispatchState::WaitingOnPort));
old_process_handler = std::move(process_handler_);
}
}
}
zx_status_t Timer::Arm(zx_time_t deadline, zx_duration_t slack_amount) {
fbl::AutoLock obj_lock(&obj_lock_);
// If we are in the process of waiting on the port, or there is a dispatch
// in flight, attempt to cancel the pending timer operation.
if ((dispatch_state() == DispatchState::WaitingOnPort) ||
(dispatch_state() == DispatchState::DispatchPending)) {
CancelPendingLocked();
}
// Reset the armed state of the timer.
DisarmLocked();
// If we are no longer active, we cannot arm the timer.
if (!is_active())
return ZX_ERR_BAD_HANDLE;
// If we are still active, we should still have a valid handle
ZX_DEBUG_ASSERT(handle_.is_valid());
// Record the current armed status.
armed_ = true;
deadline_ = deadline;
slack_amount_ = slack_amount;
// If we are currently Idle, set the timer and post a wait on our port.
// Otherwise, there is a dispatch in flight that we failed to cancel. The
// timer will take appropriate action when the in-flight operation hits
// Dispatch.
if (dispatch_state() == DispatchState::Idle) {
return SetTimerAndWaitLocked();
}
return ZX_OK;
}
void Timer::Cancel() {
fbl::AutoLock obj_lock(&obj_lock_);
// Disarm the timer and clear the internal bookkeeping.
DisarmLocked();
// If the timer handle has been closed, or the timer object is in the idle
// state, or we are in the middle of a dispatch, then we are done.
if (!handle_.is_valid() ||
(dispatch_state() == DispatchState::Idle) ||
(dispatch_state() == DispatchState::Dispatching)) {
return;
}
// We are either waiting on the port, or we are waiting in the dispatch
// queue. Attempt to cancel any pending dispatch operation. It's OK if
// this fails, it just means that the dispatch operation is in flight; we'll
// figure it out by the time we hit Dispatch.
ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::WaitingOnPort) ||
(dispatch_state() == DispatchState::DispatchPending));
if (dispatch_state() == DispatchState::WaitingOnPort)
CancelPendingLocked();
}
void Timer::Dispatch(ExecutionDomain* domain) {
ZX_DEBUG_ASSERT(domain != nullptr);
ZX_DEBUG_ASSERT(process_handler_ != nullptr);
// Check to make sure this timer should still fire. It is possible that the
// timer was canceled or changed at a point where the dispatch operation was
// already in flight and could not be cancelled.
bool do_dispatch;
{
fbl::AutoLock obj_lock(&obj_lock_);
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching);
timer_set_ = false;
// If we were disarmed, we are now back in the idle state
if (!armed_) {
dispatch_state_ = DispatchState::Idle;
return;
}
// If the timer was moved into the future, skip the dispatch operation,
// but fall into the code which re-sets the timer. Otherwise, the timer
// has now fired and we should reset our internal bookkeeping.
do_dispatch =
(zx_time_add_duration(zx_clock_get_monotonic(), early_slop_nsec_) >= deadline_);
if (do_dispatch) {
DisarmLocked();
}
}
// Now, if we are actually supposed to dispatch this event, do so.
zx_status_t res = do_dispatch ? process_handler_(this) : ZX_OK;
// Finally, handle either re-arming the timer, or cleaning up as needed.
ProcessHandler old_process_handler;
{
fbl::AutoLock obj_lock(&obj_lock_);
ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching);
dispatch_state_ = DispatchState::Idle;
// Was there a problem during processing? If so, make sure that we
// de-activate ourselves. Otherwise, if we are still active, and we are
// supposed to be armed, attempt to set up the next wait-on-port
// operation.
if (res != ZX_OK) {
InternalDeactivateLocked();
} else {
if (is_active() && armed_) {
res = SetTimerAndWaitLocked();
if (res != ZX_OK) {
// TODO(johngro) : This should not fail, we should probably
// log something instead of simply silently deactivating.
InternalDeactivateLocked();
}
}
}
// Have we become deactivated (either during dispatching or just now)?
// If so, move our process handler state outside of our lock so that it
// can safely destruct.
if (!is_active()) {
old_process_handler = std::move(process_handler_);
}
}
}
void Timer::DisarmLocked() {
// If the timer was set at the kernel level, cancel it. No matter what, we
// are now no longer armed.
if (timer_set_ && handle_.is_valid()) {
zx_timer_cancel(handle_.get());
}
timer_set_ = false;
armed_ = false;
}
zx_status_t Timer::SetTimerAndWaitLocked() {
ZX_DEBUG_ASSERT(armed_);
zx_status_t res = zx_timer_set(handle_.get(), deadline_, slack_amount_);
if (res != ZX_OK) {
DisarmLocked();
return res;
}
timer_set_ = true;
res = WaitOnPortLocked();
if (res != ZX_OK) {
DisarmLocked();
}
return res;
}
} // namespace dispatcher