HHWheelTimer::cancelAll
authorHans Fugal <fugalh@fb.com>
Fri, 26 Dec 2014 23:18:08 +0000 (15:18 -0800)
committerDave Watson <davejwatson@fb.com>
Mon, 29 Dec 2014 18:40:28 +0000 (10:40 -0800)
Summary: Provide a way to massacre outstanding timers.

Test Plan: new test, and using in wangle (different diff)

Reviewed By: davejwatson@fb.com

Subscribers: jsedgwick, exa, folly-diffs@

FB internal diff: D1753866

Tasks: 4548494

Signature: t1:1753866:1419361846:58ff6ca4d01b0d546495b032b454c8bf0fdb0277

folly/io/async/HHWheelTimer.cpp
folly/io/async/HHWheelTimer.h
folly/io/async/test/HHWheelTimerTest.cpp

index 5eec0ba488c7ecb6b01e85c59a02f0ce1596ea2f..d762224891d565875918a9872caf3b7fe78cc9ca 100644 (file)
@@ -203,4 +203,23 @@ void HHWheelTimer::timeoutExpired() noexcept {
   }
 }
 
+size_t HHWheelTimer::cancelAll() {
+  decltype(buckets_) buckets;
+  std::swap(buckets, buckets_);
+  size_t count = 0;
+
+  for (auto& tick : buckets) {
+    for (auto& bucket : tick) {
+      while (!bucket.empty()) {
+        auto& cb = bucket.front();
+        cb.cancelTimeout();
+        cb.callbackCanceled();
+        count++;
+      }
+    }
+  }
+
+  return count;
+}
+
 } // folly
index 7c6db8a0b428073e0e30ef4ad9a449758d380a01..b15de3e1c2e20744194e560d1ee819412ace2062 100644 (file)
@@ -76,6 +76,13 @@ class HHWheelTimer : private folly::AsyncTimeout,
      */
     virtual void timeoutExpired() noexcept = 0;
 
+    /// This callback was canceled. The default implementation is to just
+    /// proxy to `timeoutExpired` but if you care about the difference between
+    /// the timeout finishing or being canceled you can override this.
+    virtual void callbackCanceled() noexcept {
+      timeoutExpired();
+    }
+
     /**
      * Cancel the timeout, if it is running.
      *
@@ -152,10 +159,18 @@ class HHWheelTimer : private folly::AsyncTimeout,
    * Destroy the HHWheelTimer.
    *
    * A HHWheelTimer should only be destroyed when there are no more
-   * callbacks pending in the set.
+   * callbacks pending in the set. (If it helps you may use cancelAll() to
+   * cancel all pending timeouts explicitly before calling this.)
    */
   virtual void destroy();
 
+  /**
+   * Cancel all outstanding timeouts
+   *
+   * @returns the number of timeouts that were cancelled.
+   */
+  size_t cancelAll();
+
   /**
    * Get the tick interval for this HHWheelTimer.
    *
index 3193926f591a671d1cb53ea0661cfaef00172c58..e2b5c096291601200d97dbc962ec9627f66e98b5 100644 (file)
@@ -35,14 +35,23 @@ class TestTimeout : public HHWheelTimer::Callback {
   TestTimeout(HHWheelTimer* t, milliseconds timeout) {
     t->scheduleTimeout(this, timeout);
   }
-  virtual void timeoutExpired() noexcept {
+
+  void timeoutExpired() noexcept override {
     timestamps.push_back(TimePoint());
     if (fn) {
       fn();
     }
   }
 
+  void callbackCanceled() noexcept override {
+    canceledTimestamps.push_back(TimePoint());
+    if (fn) {
+      fn();
+    }
+  }
+
   std::deque<TimePoint> timestamps;
+  std::deque<TimePoint> canceledTimestamps;
   std::function<void()> fn;
 };
 
@@ -392,7 +401,15 @@ TEST_F(HHWheelTimerTest, lambda) {
 // at the console to confirm logging)
 TEST_F(HHWheelTimerTest, lambdaThrows) {
   StackWheelTimer t(&eventBase, milliseconds(1));
-  t.scheduleTimeoutFn([&]{ throw std::runtime_error("foo"); },
+  t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
                       milliseconds(1));
   eventBase.loop();
 }
+
+TEST_F(HHWheelTimerTest, cancelAll) {
+  StackWheelTimer t(&eventBase);
+  TestTimeout tt;
+  t.scheduleTimeout(&tt, std::chrono::minutes(1));
+  EXPECT_EQ(1, t.cancelAll());
+  EXPECT_EQ(1, tt.canceledTimestamps.size());
+}