allow AsyncSignalHandler to attach and detach from an EventBase
[folly.git] / folly / io / async / HHWheelTimer.h
index 0661a35e3118f44da86b2ac5d9e5c4e9dbb19933..89cf1f17dd8cff128f039ebb2df1d05d856e1755 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -33,14 +33,6 @@ namespace folly {
 /**
  * Hashed Hierarchical Wheel Timer
  *
- * Comparison:
- * AsyncTimeout - a single timeout.
- * HHWheelTimer - a set of efficient timeouts with different interval,
- *    but timeouts are not exact.
- *
- * All of the above are O(1) in insertion, tick update and cancel
-
- * This implementation ticks once every 10ms.
  * We model timers as the number of ticks until the next
  * due event.  We allow 32-bits of space to track this
  * due interval, and break that into 4 regions of 8 bits.
@@ -53,8 +45,11 @@ namespace folly {
  * into a different bucket.
  *
  * This technique results in a very cheap mechanism for
- * maintaining time and timers, provided that we can maintain
- * a consistent rate of ticks.
+ * maintaining time and timers.
+ *
+ * Unlike the original timer wheel paper, this implementation does
+ * *not* tick constantly, and instead calculates the exact next wakeup
+ * time.
  */
 class HHWheelTimer : private folly::AsyncTimeout,
                      public folly::DelayedDestruction {
@@ -71,12 +66,11 @@ class HHWheelTimer : private folly::AsyncTimeout,
   /**
    * A callback to be notified when a timeout has expired.
    */
-  class Callback {
+  class Callback
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
    public:
-    Callback()
-      : wheel_(nullptr)
-      , expiration_(0) {}
-
+    Callback() = default;
     virtual ~Callback();
 
     /**
@@ -116,36 +110,31 @@ class HHWheelTimer : private folly::AsyncTimeout,
      * Don't override this unless you're doing a test. This is mainly here so
      * that we can override it to simulate lag in steady_clock.
      */
-    virtual std::chrono::milliseconds getCurTime() {
-      return std::chrono::duration_cast<std::chrono::milliseconds>(
-        std::chrono::steady_clock::now().time_since_epoch());
+    virtual std::chrono::steady_clock::time_point getCurTime() {
+      return std::chrono::steady_clock::now();
     }
 
    private:
     // Get the time remaining until this timeout expires
     std::chrono::milliseconds getTimeRemaining(
-          std::chrono::milliseconds now) const {
+        std::chrono::steady_clock::time_point now) const {
       if (now >= expiration_) {
         return std::chrono::milliseconds(0);
       }
-      return expiration_ - now;
+      return std::chrono::duration_cast<std::chrono::milliseconds>(
+          expiration_ - now);
     }
 
     void setScheduled(HHWheelTimer* wheel,
                       std::chrono::milliseconds);
     void cancelTimeoutImpl();
 
-    HHWheelTimer* wheel_;
-    std::chrono::milliseconds expiration_;
-
-    typedef boost::intrusive::list_member_hook<
-      boost::intrusive::link_mode<boost::intrusive::auto_unlink> > ListHook;
-
-    ListHook hook_;
+    HHWheelTimer* wheel_{nullptr};
+    std::chrono::steady_clock::time_point expiration_{};
+    int bucket_{-1};
 
     typedef boost::intrusive::list<
       Callback,
-      boost::intrusive::member_hook<Callback, ListHook, &Callback::hook_>,
       boost::intrusive::constant_time_size<false> > List;
 
     std::shared_ptr<RequestContext> context_;
@@ -212,7 +201,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
 
   /**
    * Schedule the specified Callback to be invoked after the
-   * fefault timeout interval.
+   * default timeout interval.
    *
    * If the callback is already scheduled, this cancels the existing timeout
    * before scheduling the new timeout.
@@ -250,20 +239,6 @@ class HHWheelTimer : private folly::AsyncTimeout,
     return count_;
   }
 
-  /**
-   * This turns on more exact timing.  By default the wheel timer
-   * increments its cached time only once everyN (default) ticks.
-   *
-   * With catchupEveryN at 1, timeouts will only be delayed until the
-   * next tick, at which point all overdue timeouts are called.  The
-   * wheel timer is approximately 2x slower with this set to 1.
-   *
-   * Load testing in opt mode showed skew was about 1% with no catchup.
-   */
-  void setCatchupEveryN(uint32_t everyN) {
-    catchupEveryN_ = everyN;
-  }
-
   bool isDetachable() const {
     return !folly::AsyncTimeout::isScheduled();
   }
@@ -279,7 +254,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
    * Use destroy() instead.  See the comments in DelayedDestruction for more
    * details.
    */
-  virtual ~HHWheelTimer();
+  ~HHWheelTimer() override;
 
  private:
   // Forbidden copy constructor and assignment operator
@@ -287,7 +262,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
   HHWheelTimer& operator=(HHWheelTimer const &) = delete;
 
   // Methods inherited from AsyncTimeout
-  virtual void timeoutExpired() noexcept;
+  void timeoutExpired() noexcept override;
 
   std::chrono::milliseconds interval_;
   std::chrono::milliseconds defaultTimeout_;
@@ -300,21 +275,28 @@ class HHWheelTimer : private folly::AsyncTimeout,
 
   typedef Callback::List CallbackList;
   CallbackList buckets_[WHEEL_BUCKETS][WHEEL_SIZE];
+  std::vector<uint64_t> bitmap_;
 
   int64_t timeToWheelTicks(std::chrono::milliseconds t) {
     return t.count() / interval_.count();
   }
 
   bool cascadeTimers(int bucket, int tick);
-  int64_t nextTick_;
+  int64_t lastTick_;
+  int64_t expireTick_;
   uint64_t count_;
-  std::chrono::milliseconds now_;
+  std::chrono::steady_clock::time_point startTime_;
+
+  int64_t calcNextTick();
 
-  static constexpr uint32_t DEFAULT_CATCHUP_EVERY_N = 10;
+  void scheduleNextTimeout();
 
-  uint32_t catchupEveryN_;
-  uint32_t expirationsSinceCatchup_;
   bool* processingCallbacksGuard_;
+  CallbackList timeouts; // Timeouts queued to run
+
+  std::chrono::steady_clock::time_point getCurTime() {
+    return std::chrono::steady_clock::now();
+  }
 };
 
 } // folly