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