1 // Copyright 2019 The Chromium 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 "util/alarm.h"
6
7 #include "util/osp_logging.h"
8
9 namespace openscreen {
10
11 class Alarm::CancelableFunctor {
12 public:
CancelableFunctor(Alarm * alarm)13 explicit CancelableFunctor(Alarm* alarm) : alarm_(alarm) {
14 OSP_DCHECK(alarm_);
15 OSP_DCHECK(!alarm_->queued_fire_);
16 alarm_->queued_fire_ = this;
17 }
18
~CancelableFunctor()19 ~CancelableFunctor() { Cancel(); }
20
CancelableFunctor(CancelableFunctor && other)21 CancelableFunctor(CancelableFunctor&& other) : alarm_(other.alarm_) {
22 other.alarm_ = nullptr;
23 if (alarm_) {
24 OSP_DCHECK_EQ(alarm_->queued_fire_, &other);
25 alarm_->queued_fire_ = this;
26 }
27 }
28
operator =(CancelableFunctor && other)29 CancelableFunctor& operator=(CancelableFunctor&& other) {
30 Cancel();
31 alarm_ = other.alarm_;
32 other.alarm_ = nullptr;
33 if (alarm_) {
34 OSP_DCHECK_EQ(alarm_->queued_fire_, &other);
35 alarm_->queued_fire_ = this;
36 }
37 return *this;
38 }
39
operator ()()40 void operator()() noexcept {
41 if (alarm_) {
42 OSP_DCHECK_EQ(alarm_->queued_fire_, this);
43 alarm_->queued_fire_ = nullptr;
44 alarm_->TryInvoke();
45 alarm_ = nullptr;
46 }
47 }
48
Cancel()49 void Cancel() {
50 if (alarm_) {
51 OSP_DCHECK_EQ(alarm_->queued_fire_, this);
52 alarm_->queued_fire_ = nullptr;
53 alarm_ = nullptr;
54 }
55 }
56
57 private:
58 Alarm* alarm_;
59 };
60
Alarm(ClockNowFunctionPtr now_function,TaskRunner * task_runner)61 Alarm::Alarm(ClockNowFunctionPtr now_function, TaskRunner* task_runner)
62 : now_function_(now_function), task_runner_(task_runner) {
63 OSP_DCHECK(now_function_);
64 OSP_DCHECK(task_runner_);
65 }
66
~Alarm()67 Alarm::~Alarm() {
68 if (queued_fire_) {
69 queued_fire_->Cancel();
70 OSP_DCHECK(!queued_fire_);
71 }
72 }
73
Cancel()74 void Alarm::Cancel() {
75 scheduled_task_ = TaskRunner::Task();
76 }
77
ScheduleWithTask(TaskRunner::Task task,Clock::time_point desired_alarm_time)78 void Alarm::ScheduleWithTask(TaskRunner::Task task,
79 Clock::time_point desired_alarm_time) {
80 OSP_DCHECK(task.valid());
81
82 scheduled_task_ = std::move(task);
83
84 const Clock::time_point now = now_function_();
85 alarm_time_ = std::max(now, desired_alarm_time);
86
87 // Ensure that a later firing will occur, and not too late.
88 if (queued_fire_) {
89 if (next_fire_time_ <= alarm_time_) {
90 return;
91 }
92 queued_fire_->Cancel();
93 OSP_DCHECK(!queued_fire_);
94 }
95 InvokeLater(now, alarm_time_);
96 }
97
InvokeLater(Clock::time_point now,Clock::time_point fire_time)98 void Alarm::InvokeLater(Clock::time_point now, Clock::time_point fire_time) {
99 OSP_DCHECK(!queued_fire_);
100 next_fire_time_ = fire_time;
101 // Note: Instantiating the CancelableFunctor below sets |this->queued_fire_|.
102 task_runner_->PostTaskWithDelay(CancelableFunctor(this), fire_time - now);
103 }
104
TryInvoke()105 void Alarm::TryInvoke() {
106 if (!scheduled_task_.valid()) {
107 return; // This Alarm was canceled in the meantime.
108 }
109
110 // If this is an early firing, re-schedule for later. This happens if
111 // Schedule() was called again before this firing had occurred.
112 const Clock::time_point now = now_function_();
113 if (now < alarm_time_) {
114 InvokeLater(now, alarm_time_);
115 return;
116 }
117
118 // Move the client Task to the stack before executing, just in case the task
119 // itself: a) calls any Alarm methods re-entrantly, or b) causes the
120 // destruction of this Alarm instance.
121 // WARNING: |this| is not valid after here!
122 TaskRunner::Task task = std::move(scheduled_task_);
123 task();
124 }
125
126 // static
127 constexpr Clock::time_point Alarm::kImmediately;
128
129 } // namespace openscreen
130