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