1/*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18#include <folly/experimental/TimerFD.h>
19#include <folly/io/async/DelayedDestruction.h>
20#include <map>
21
22namespace folly {
23// generic TimerFD based timeout manager
24class TimerFDTimeoutManager : public TimerFD {
25 public:
26 using UniquePtr =
27 std::unique_ptr<TimerFDTimeoutManager, DelayedDestruction::Destructor>;
28 using SharedPtr = std::shared_ptr<TimerFDTimeoutManager>;
29
30 public:
31 class Callback
32 : public boost::intrusive::list_base_hook<
33 boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
34 public:
35 Callback() = default;
36 explicit Callback(TimerFDTimeoutManager* mgr) : mgr_(mgr) {}
37 virtual ~Callback() = default;
38
39 virtual void timeoutExpired() noexcept = 0;
40 virtual void callbackCanceled() noexcept {
41 timeoutExpired();
42 }
43
44 const std::chrono::microseconds& getExpirationTime() const {
45 return expirationTime_;
46 }
47
48 void setExpirationTime(
49 TimerFDTimeoutManager* mgr,
50 const std::chrono::microseconds& expirationTime) {
51 mgr_ = mgr;
52 expirationTime_ = expirationTime;
53 }
54
55 std::chrono::microseconds getTimeRemaining() const {
56 return getTimeRemaining(std::chrono::steady_clock::now());
57 }
58
59 std::chrono::microseconds getTimeRemaining(
60 std::chrono::steady_clock::time_point now) const {
61 auto nowMs = std::chrono::duration_cast<std::chrono::microseconds>(
62 now.time_since_epoch());
63 if (expirationTime_ > nowMs) {
64 return std::chrono::duration_cast<std::chrono::microseconds>(
65 expirationTime_ - nowMs);
66 }
67
68 return std::chrono::microseconds(0);
69 }
70
71 void scheduleTimeout(std::chrono::microseconds timeout) {
72 if (mgr_) {
73 mgr_->scheduleTimeout(this, timeout);
74 }
75 }
76
77 bool cancelTimeout() {
78 return mgr_->cancelTimeout(this);
79 }
80
81 private:
82 TimerFDTimeoutManager* mgr_{nullptr};
83 std::chrono::microseconds expirationTime_{0};
84 };
85
86 explicit TimerFDTimeoutManager(folly::EventBase* eventBase);
87 ~TimerFDTimeoutManager() override;
88
89 // from TimerFD
90 void onTimeout() noexcept final;
91
92 size_t cancelAll();
93 void scheduleTimeout(Callback* callback, std::chrono::microseconds timeout);
94 bool cancelTimeout(Callback* callback);
95
96 template <class F>
97 void scheduleTimeoutFn(F fn, std::chrono::microseconds timeout) {
98 struct Wrapper : Callback {
99 explicit Wrapper(F f) : fn_(std::move(f)) {}
100 void timeoutExpired() noexcept override {
101 try {
102 fn_();
103 } catch (std::exception const& e) {
104 LOG(ERROR) << "HHWheelTimerBase timeout callback threw an exception: "
105 << e.what();
106 } catch (...) {
107 LOG(ERROR)
108 << "HHWheelTimerBase timeout callback threw a non-exception.";
109 }
110 delete this;
111 }
112 F fn_;
113 };
114 Wrapper* w = new Wrapper(std::move(fn));
115 scheduleTimeout(w, timeout);
116 }
117
118 size_t count() const;
119
120 private:
121 void processExpiredTimers();
122 void scheduleNextTimer();
123
124 std::chrono::steady_clock::time_point getCurTime() {
125 return std::chrono::steady_clock::now();
126 }
127
128 // we can attempt to schedule new entries while in processExpiredTimers
129 // we want to reschedule the timers once we're done with the processing
130 bool processingExpired_{false};
131
132 typedef boost::intrusive::
133 list<Callback, boost::intrusive::constant_time_size<false>>
134 CallbackList;
135 std::map<std::chrono::microseconds, CallbackList> callbacks_;
136 CallbackList inProgressList_;
137};
138} // namespace folly
139