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