Properly std::chrono'ize HHWheelTimer
[folly.git] / folly / io / async / HHWheelTimer.h
1 /*
2  * Copyright 2017 Facebook, Inc.
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
19 #include <folly/Optional.h>
20 #include <folly/io/async/AsyncTimeout.h>
21 #include <folly/io/async/DelayedDestruction.h>
22
23 #include <boost/intrusive/list.hpp>
24 #include <glog/logging.h>
25
26 #include <chrono>
27 #include <cstddef>
28 #include <list>
29 #include <memory>
30
31 namespace folly {
32
33 /**
34  * Hashed Hierarchical Wheel Timer
35  *
36  * We model timers as the number of ticks until the next
37  * due event.  We allow 32-bits of space to track this
38  * due interval, and break that into 4 regions of 8 bits.
39  * Each region indexes into a bucket of 256 lists.
40  *
41  * Bucket 0 represents those events that are due the soonest.
42  * Each tick causes us to look at the next list in a bucket.
43  * The 0th list in a bucket is special; it means that it is time to
44  * flush the timers from the next higher bucket and schedule them
45  * into a different bucket.
46  *
47  * This technique results in a very cheap mechanism for
48  * maintaining time and timers.
49  *
50  * Unlike the original timer wheel paper, this implementation does
51  * *not* tick constantly, and instead calculates the exact next wakeup
52  * time.
53  */
54 class HHWheelTimer : private folly::AsyncTimeout,
55                      public folly::DelayedDestruction {
56  public:
57   // This type has always been a misnomer, because it is not a unique pointer.
58   using UniquePtr = std::unique_ptr<HHWheelTimer, Destructor>;
59   using SharedPtr = std::shared_ptr<HHWheelTimer>;
60
61   template <typename... Args>
62   static UniquePtr newTimer(Args&&... args) {
63     return UniquePtr(new HHWheelTimer(std::forward<Args>(args)...));
64   }
65
66   /**
67    * A callback to be notified when a timeout has expired.
68    */
69   class Callback
70       : public boost::intrusive::list_base_hook<
71             boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
72    public:
73     Callback() = default;
74     virtual ~Callback();
75
76     /**
77      * timeoutExpired() is invoked when the timeout has expired.
78      */
79     virtual void timeoutExpired() noexcept = 0;
80
81     /// This callback was canceled. The default implementation is to just
82     /// proxy to `timeoutExpired` but if you care about the difference between
83     /// the timeout finishing or being canceled you can override this.
84     virtual void callbackCanceled() noexcept {
85       timeoutExpired();
86     }
87
88     /**
89      * Cancel the timeout, if it is running.
90      *
91      * If the timeout is not scheduled, cancelTimeout() does nothing.
92      */
93     void cancelTimeout() {
94       if (wheel_ == nullptr) {
95         // We're not scheduled, so there's nothing to do.
96         return;
97       }
98       cancelTimeoutImpl();
99     }
100
101     /**
102      * Return true if this timeout is currently scheduled, and false otherwise.
103      */
104     bool isScheduled() const {
105       return wheel_ != nullptr;
106     }
107
108    protected:
109     /**
110      * Don't override this unless you're doing a test. This is mainly here so
111      * that we can override it to simulate lag in steady_clock.
112      */
113     virtual std::chrono::steady_clock::time_point getCurTime() {
114       return std::chrono::steady_clock::now();
115     }
116
117    private:
118     // Get the time remaining until this timeout expires
119     std::chrono::milliseconds getTimeRemaining(
120         std::chrono::steady_clock::time_point now) const {
121       if (now >= expiration_) {
122         return std::chrono::milliseconds(0);
123       }
124       return std::chrono::duration_cast<std::chrono::milliseconds>(
125           expiration_ - now);
126     }
127
128     void setScheduled(HHWheelTimer* wheel,
129                       std::chrono::milliseconds);
130     void cancelTimeoutImpl();
131
132     HHWheelTimer* wheel_{nullptr};
133     std::chrono::steady_clock::time_point expiration_{};
134     int bucket_{-1};
135
136     typedef boost::intrusive::list<
137       Callback,
138       boost::intrusive::constant_time_size<false> > List;
139
140     std::shared_ptr<RequestContext> context_;
141
142     // Give HHWheelTimer direct access to our members so it can take care
143     // of scheduling/cancelling.
144     friend class HHWheelTimer;
145   };
146
147   /**
148    * Create a new HHWheelTimer with the specified interval and the
149    * default timeout value set.
150    *
151    * Objects created using this version of constructor can be used
152    * to schedule both variable interval timeouts using
153    * scheduleTimeout(callback, timeout) method, and default
154    * interval timeouts using scheduleTimeout(callback) method.
155    */
156   static int DEFAULT_TICK_INTERVAL;
157   explicit HHWheelTimer(
158       folly::TimeoutManager* timeoutManager,
159       std::chrono::milliseconds intervalMS =
160           std::chrono::milliseconds(DEFAULT_TICK_INTERVAL),
161       AsyncTimeout::InternalEnum internal = AsyncTimeout::InternalEnum::NORMAL,
162       std::chrono::milliseconds defaultTimeoutMS =
163           std::chrono::milliseconds(-1));
164
165   /**
166    * Cancel all outstanding timeouts
167    *
168    * @returns the number of timeouts that were cancelled.
169    */
170   size_t cancelAll();
171
172   /**
173    * Get the tick interval for this HHWheelTimer.
174    *
175    * Returns the tick interval in milliseconds.
176    */
177   std::chrono::milliseconds getTickInterval() const {
178     return interval_;
179   }
180
181   /**
182    * Get the default timeout interval for this HHWheelTimer.
183    *
184    * Returns the timeout interval in milliseconds.
185    */
186   std::chrono::milliseconds getDefaultTimeout() const {
187     return defaultTimeout_;
188   }
189
190   /**
191    * Schedule the specified Callback to be invoked after the
192    * specified timeout interval.
193    *
194    * If the callback is already scheduled, this cancels the existing timeout
195    * before scheduling the new timeout.
196    */
197   void scheduleTimeout(Callback* callback,
198                        std::chrono::milliseconds timeout);
199   void scheduleTimeoutImpl(Callback* callback,
200                        std::chrono::milliseconds timeout);
201
202   /**
203    * Schedule the specified Callback to be invoked after the
204    * default timeout interval.
205    *
206    * If the callback is already scheduled, this cancels the existing timeout
207    * before scheduling the new timeout.
208    *
209    * This method uses CHECK() to make sure that the default timeout was
210    * specified on the object initialization.
211    */
212   void scheduleTimeout(Callback* callback);
213
214   template <class F>
215   void scheduleTimeoutFn(F fn, std::chrono::milliseconds timeout) {
216     struct Wrapper : Callback {
217       Wrapper(F f) : fn_(std::move(f)) {}
218       void timeoutExpired() noexcept override {
219         try {
220           fn_();
221         } catch (std::exception const& e) {
222           LOG(ERROR) << "HHWheelTimer timeout callback threw an exception: "
223             << e.what();
224         } catch (...) {
225           LOG(ERROR) << "HHWheelTimer timeout callback threw a non-exception.";
226         }
227         delete this;
228       }
229       F fn_;
230     };
231     Wrapper* w = new Wrapper(std::move(fn));
232     scheduleTimeout(w, timeout);
233   }
234
235   /**
236    * Return the number of currently pending timeouts
237    */
238   uint64_t count() const {
239     return count_;
240   }
241
242   bool isDetachable() const {
243     return !folly::AsyncTimeout::isScheduled();
244   }
245
246   using folly::AsyncTimeout::attachEventBase;
247   using folly::AsyncTimeout::detachEventBase;
248   using folly::AsyncTimeout::getTimeoutManager;
249
250  protected:
251   /**
252    * Protected destructor.
253    *
254    * Use destroy() instead.  See the comments in DelayedDestruction for more
255    * details.
256    */
257   virtual ~HHWheelTimer();
258
259  private:
260   // Forbidden copy constructor and assignment operator
261   HHWheelTimer(HHWheelTimer const &) = delete;
262   HHWheelTimer& operator=(HHWheelTimer const &) = delete;
263
264   // Methods inherited from AsyncTimeout
265   virtual void timeoutExpired() noexcept;
266
267   std::chrono::milliseconds interval_;
268   std::chrono::milliseconds defaultTimeout_;
269
270   static constexpr int WHEEL_BUCKETS = 4;
271   static constexpr int WHEEL_BITS = 8;
272   static constexpr unsigned int WHEEL_SIZE = (1 << WHEEL_BITS);
273   static constexpr unsigned int WHEEL_MASK = (WHEEL_SIZE - 1);
274   static constexpr uint32_t LARGEST_SLOT = 0xffffffffUL;
275
276   typedef Callback::List CallbackList;
277   CallbackList buckets_[WHEEL_BUCKETS][WHEEL_SIZE];
278   std::vector<uint64_t> bitmap_;
279
280   int64_t timeToWheelTicks(std::chrono::milliseconds t) {
281     return t.count() / interval_.count();
282   }
283
284   bool cascadeTimers(int bucket, int tick);
285   int64_t lastTick_;
286   int64_t expireTick_;
287   uint64_t count_;
288   std::chrono::steady_clock::time_point startTime_;
289
290   int64_t calcNextTick();
291
292   void scheduleNextTimeout();
293
294   bool* processingCallbacksGuard_;
295   CallbackList timeouts; // Timeouts queued to run
296
297   std::chrono::steady_clock::time_point getCurTime() {
298     return std::chrono::steady_clock::now();
299   }
300 };
301
302 } // folly