Commit 3a06d042 authored by Stepan Palamarchuk's avatar Stepan Palamarchuk Committed by Facebook Github Bot

Cancel timeouts about to run in the cancelAll

Summary:
Currently if `cancelAll` is called from inside the `timeoutExpired` of one of the callbacks, it will not cancel timeouts that we're about to run (they were extracted from the buckets already).

This diff fixes that behavior by also canceling timeouts in `timeoutsToRunNow_` list (note, we were already doing that in the destructor).

Reviewed By: yfeldblum

Differential Revision: D13531908

fbshipit-source-id: f05ba31f2ac845851c1560d2ebdf41aa995b2deb
parent 948bc7b6
...@@ -99,12 +99,6 @@ HHWheelTimer::~HHWheelTimer() { ...@@ -99,12 +99,6 @@ HHWheelTimer::~HHWheelTimer() {
*processingCallbacksGuard_ = true; *processingCallbacksGuard_ = true;
} }
}); });
while (!timeouts.empty()) {
auto* cb = &timeouts.front();
timeouts.pop_front();
cb->cancelTimeout();
cb->callbackCanceled();
}
cancelAll(); cancelAll();
} }
...@@ -214,7 +208,7 @@ void HHWheelTimer::timeoutExpired() noexcept { ...@@ -214,7 +208,7 @@ void HHWheelTimer::timeoutExpired() noexcept {
while (!cbs->empty()) { while (!cbs->empty()) {
auto* cb = &cbs->front(); auto* cb = &cbs->front();
cbs->pop_front(); cbs->pop_front();
timeouts.push_back(*cb); timeoutsToRunNow_.push_back(*cb);
} }
if (idx == 0) { if (idx == 0) {
...@@ -226,9 +220,9 @@ void HHWheelTimer::timeoutExpired() noexcept { ...@@ -226,9 +220,9 @@ void HHWheelTimer::timeoutExpired() noexcept {
} }
} }
while (!timeouts.empty()) { while (!timeoutsToRunNow_.empty()) {
auto* cb = &timeouts.front(); auto* cb = &timeoutsToRunNow_.front();
timeouts.pop_front(); timeoutsToRunNow_.pop_front();
count_--; count_--;
cb->wheel_ = nullptr; cb->wheel_ = nullptr;
cb->expiration_ = {}; cb->expiration_ = {};
...@@ -266,13 +260,13 @@ size_t HHWheelTimer::cancelAll() { ...@@ -266,13 +260,13 @@ size_t HHWheelTimer::cancelAll() {
} }
for (size_t i = 0; i < countBuckets; ++i) { for (size_t i = 0; i < countBuckets; ++i) {
auto& bucket = buckets[i]; cancelTimeoutsFromList(buckets[i]);
while (!bucket.empty()) {
auto& cb = bucket.front();
cb.cancelTimeout();
cb.callbackCanceled();
}
} }
// Swap the list to prevent potential recursion if cancelAll is called by
// one of the callbacks.
CallbackList timeoutsToRunNow;
timeoutsToRunNow.swap(timeoutsToRunNow_);
count += cancelTimeoutsFromList(timeoutsToRunNow);
} }
return count; return count;
...@@ -304,6 +298,17 @@ void HHWheelTimer::scheduleNextTimeout() { ...@@ -304,6 +298,17 @@ void HHWheelTimer::scheduleNextTimeout() {
} }
} }
size_t HHWheelTimer::cancelTimeoutsFromList(CallbackList& timeouts) {
size_t count = 0;
while (!timeouts.empty()) {
++count;
auto& cb = timeouts.front();
cb.cancelTimeout();
cb.callbackCanceled();
}
return count;
}
int64_t HHWheelTimer::calcNextTick() { int64_t HHWheelTimer::calcNextTick() {
auto intervals = (getCurTime() - startTime_) / interval_; auto intervals = (getCurTime() - startTime_) / interval_;
// Slow eventbases will have skew between the actual time and the // Slow eventbases will have skew between the actual time and the
......
...@@ -305,8 +305,13 @@ class HHWheelTimer : private folly::AsyncTimeout, ...@@ -305,8 +305,13 @@ class HHWheelTimer : private folly::AsyncTimeout,
void scheduleNextTimeout(); void scheduleNextTimeout();
size_t cancelTimeoutsFromList(CallbackList& timeouts);
bool* processingCallbacksGuard_; bool* processingCallbacksGuard_;
CallbackList timeouts; // Timeouts queued to run // Timeouts that we're about to run. They're already extracted from their
// corresponding buckets, so we need this list for the `cancelAll` to be able
// to cancel them.
CallbackList timeoutsToRunNow_;
std::chrono::steady_clock::time_point getCurTime() { std::chrono::steady_clock::time_point getCurTime() {
return std::chrono::steady_clock::now(); return std::chrono::steady_clock::now();
......
...@@ -399,10 +399,19 @@ TEST_F(HHWheelTimerTest, lambdaThrows) { ...@@ -399,10 +399,19 @@ TEST_F(HHWheelTimerTest, lambdaThrows) {
TEST_F(HHWheelTimerTest, cancelAll) { TEST_F(HHWheelTimerTest, cancelAll) {
StackWheelTimer t(&eventBase, milliseconds(1)); StackWheelTimer t(&eventBase, milliseconds(1));
TestTimeout tt; TestTimeout t1;
t.scheduleTimeout(&tt, std::chrono::minutes(1)); TestTimeout t2;
EXPECT_EQ(1, t.cancelAll()); t.scheduleTimeout(&t1, std::chrono::milliseconds(1));
EXPECT_EQ(1, tt.canceledTimestamps.size()); t.scheduleTimeout(&t2, std::chrono::milliseconds(1));
size_t canceled = 0;
t1.fn = [&] { canceled += t.cancelAll(); };
t2.fn = [&] { canceled += t.cancelAll(); };
// Sleep 20ms to ensure both timeouts will fire in a single event (in case
// they ended up in different slots)
::usleep(20000);
eventBase.loop();
EXPECT_EQ(1, t1.canceledTimestamps.size() + t2.canceledTimestamps.size());
EXPECT_EQ(1, canceled);
} }
TEST_F(HHWheelTimerTest, IntrusivePtr) { TEST_F(HHWheelTimerTest, IntrusivePtr) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment