| // 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 |