// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef UTIL_ALARM_H_ #define UTIL_ALARM_H_ #include #include "platform/api/task_runner.h" #include "platform/api/time.h" namespace openscreen { // A simple mechanism for running one Task in the future, but also allow for // canceling the Task before it runs and/or re-scheduling a replacement Task to // run at a different time. This mechanism is also scoped to its lifetime: if an // Alarm is destroyed while it is scheduled, the Task is automatically canceled. // It is safe for the client's Task to make re-entrant calls into all Alarm // methods. // // Example use case: When using a TaskRunner, an object can safely schedule a // callback into one of its instance methods (without the possibility of the // Task executing after the object is destroyed). // // Design: In order to support efficient, arbitrary canceling and re-scheduling // by the client, the Alarm posts a cancelable functor to the TaskRunner which, // when invoked, then checks to see whether the Alarm instance still exists and, // if so, calls its TryInvoke() method. The TryInvoke() method then determines: // a) whether the invocation time of the client's Task has changed; and b) // whether the Alarm was canceled in the meantime. From this, it either: a) does // nothing; b) re-posts a new cancelable functor to the TaskRunner, to try // running the client's Task later; or c) runs the client's Task. class Alarm { public: Alarm(ClockNowFunctionPtr now_function, TaskRunner* task_runner); ~Alarm(); // The design requires that Alarm instances not be copied or moved. Alarm(const Alarm&) = delete; Alarm& operator=(const Alarm&) = delete; Alarm(Alarm&&) = delete; Alarm& operator=(Alarm&&) = delete; // Schedule the |functor| to be invoked at |alarm_time|. If this Alarm was // already scheduled, the prior scheduling is canceled. The Functor can be any // callable target (e.g., function, lambda-expression, std::bind result, // etc.). If |alarm_time| is on or before "now," such as kImmediately, it is // scheduled to run as soon as possible. template inline void Schedule(Functor functor, Clock::time_point alarm_time) { ScheduleWithTask(TaskRunner::Task(std::move(functor)), alarm_time); } // Same as Schedule(), but invoke the functor at the given |delay| after right // now. template inline void ScheduleFromNow(Functor functor, Clock::duration delay) { ScheduleWithTask(TaskRunner::Task(std::move(functor)), now_function_() + delay); } // Cancels an already-scheduled task from running, or no-op. void Cancel(); // See comments for Schedule(). Generally, callers will want to call // Schedule() instead of this, for more-convenient caller-side syntax, unless // they already have a Task to pass-in. void ScheduleWithTask(TaskRunner::Task task, Clock::time_point alarm_time); // A special time_point value representing "as soon as possible." static constexpr Clock::time_point kImmediately = Clock::time_point::min(); private: // A move-only functor that holds a raw pointer back to |this| and can be // canceled before its call operator is invoked. When canceled, its call // operator becomes a no-op. class CancelableFunctor; // Posts a delayed call to TryInvoke() to the TaskRunner. void InvokeLater(Clock::time_point now, Clock::time_point fire_time); // Examines whether to invoke the client's Task now; or try again later; or // just do nothing. See class-level design comments. void TryInvoke(); const ClockNowFunctionPtr now_function_; TaskRunner* const task_runner_; // This is the task the client wants to have run at a specific point-in-time. // This is NOT the task that Alarm provides to the TaskRunner. TaskRunner::Task scheduled_task_; Clock::time_point alarm_time_{}; // When non-null, there is a task in the TaskRunner's queue that will call // TryInvoke() some time in the future. This member is exclusively maintained // by the CancelableFunctor class methods. CancelableFunctor* queued_fire_ = nullptr; // When the CancelableFunctor is scheduled to run. It may possibly execute // later than this, if the TaskRunner is falling behind. Clock::time_point next_fire_time_{}; }; } // namespace openscreen #endif // UTIL_ALARM_H_