Add a default timeout parameter to HHWheelTimer.
[folly.git] / folly / io / async / HHWheelTimer.cpp
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements. See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership. The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License. You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied. See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21 #include <folly/io/async/HHWheelTimer.h>
22 #include <folly/io/async/Request.h>
23
24 #include <folly/ScopeGuard.h>
25
26 #include <cassert>
27
28 using std::chrono::milliseconds;
29
30 namespace folly {
31
32 /**
33  * We want to select the default interval carefully.
34  * An interval of 10ms will give us 10ms * WHEEL_SIZE^WHEEL_BUCKETS
35  * for the largest timeout possible, or about 497 days.
36  *
37  * For a lower bound, we want a reasonable limit on local IO, 10ms
38  * seems short enough
39  *
40  * A shorter interval also has CPU implications, less than 1ms might
41  * start showing up in cpu perf.  Also, it might not be possible to set
42  * tick interval less than 10ms on older kernels.
43  */
44 int HHWheelTimer::DEFAULT_TICK_INTERVAL = 10;
45
46 HHWheelTimer::Callback::~Callback() {
47   if (isScheduled()) {
48     cancelTimeout();
49   }
50 }
51
52 void HHWheelTimer::Callback::setScheduled(HHWheelTimer* wheel,
53                                           std::chrono::milliseconds timeout) {
54   assert(wheel_ == nullptr);
55   assert(expiration_ == milliseconds(0));
56
57   wheel_ = wheel;
58
59   // Only update the now_ time if we're not in a timeout expired callback
60   if (wheel_->count_  == 0 && !wheel_->processingCallbacksGuard_) {
61     wheel_->now_ = getCurTime();
62   }
63
64   expiration_ = wheel_->now_ + timeout;
65 }
66
67 void HHWheelTimer::Callback::cancelTimeoutImpl() {
68   if (--wheel_->count_ <= 0) {
69     assert(wheel_->count_ == 0);
70     wheel_->AsyncTimeout::cancelTimeout();
71   }
72   hook_.unlink();
73
74   wheel_ = nullptr;
75   expiration_ = milliseconds(0);
76 }
77
78 HHWheelTimer::HHWheelTimer(folly::EventBase* eventBase,
79                            std::chrono::milliseconds intervalMS,
80                            AsyncTimeout::InternalEnum internal,
81                            std::chrono::milliseconds defaultTimeoutMS)
82   : AsyncTimeout(eventBase, internal)
83   , interval_(intervalMS)
84   , defaultTimeout_(defaultTimeoutMS)
85   , nextTick_(1)
86   , count_(0)
87   , catchupEveryN_(DEFAULT_CATCHUP_EVERY_N)
88   , expirationsSinceCatchup_(0)
89   , processingCallbacksGuard_(false)
90 {
91 }
92
93 HHWheelTimer::~HHWheelTimer() {
94 }
95
96 void HHWheelTimer::destroy() {
97   assert(count_ == 0);
98   DelayedDestruction::destroy();
99 }
100
101 void HHWheelTimer::scheduleTimeoutImpl(Callback* callback,
102                                        std::chrono::milliseconds timeout) {
103   int64_t due = timeToWheelTicks(timeout) + nextTick_;
104   int64_t diff = due - nextTick_;
105   CallbackList* list;
106
107   if (diff < 0) {
108     list = &buckets_[0][nextTick_ & WHEEL_MASK];
109   } else if (diff < WHEEL_SIZE) {
110     list = &buckets_[0][due & WHEEL_MASK];
111   } else if (diff < 1 << (2 * WHEEL_BITS)) {
112     list = &buckets_[1][(due >> WHEEL_BITS) & WHEEL_MASK];
113   } else if (diff < 1 << (3 * WHEEL_BITS)) {
114     list = &buckets_[2][(due >> 2 * WHEEL_BITS) & WHEEL_MASK];
115   } else {
116     /* in largest slot */
117     if (diff > LARGEST_SLOT) {
118       diff = LARGEST_SLOT;
119       due = diff + nextTick_;
120     }
121     list = &buckets_[3][(due >> 3 * WHEEL_BITS) & WHEEL_MASK];
122   }
123   list->push_back(*callback);
124 }
125
126 void HHWheelTimer::scheduleTimeout(Callback* callback,
127                                    std::chrono::milliseconds timeout) {
128   // Cancel the callback if it happens to be scheduled already.
129   callback->cancelTimeout();
130
131   callback->context_ = RequestContext::saveContext();
132
133   if (count_ == 0 && !processingCallbacksGuard_) {
134     this->AsyncTimeout::scheduleTimeout(interval_.count());
135   }
136
137   callback->setScheduled(this, timeout);
138   scheduleTimeoutImpl(callback, timeout);
139   count_++;
140 }
141
142 void HHWheelTimer::scheduleTimeout(Callback* callback) {
143   CHECK(std::chrono::milliseconds(-1) != defaultTimeout_)
144       << "Default timeout was not initialized";
145   scheduleTimeout(callback, defaultTimeout_);
146 }
147
148 bool HHWheelTimer::cascadeTimers(int bucket, int tick) {
149   CallbackList cbs;
150   cbs.swap(buckets_[bucket][tick]);
151   while (!cbs.empty()) {
152     auto* cb = &cbs.front();
153     cbs.pop_front();
154     scheduleTimeoutImpl(cb, cb->getTimeRemaining(now_));
155   }
156
157   // If tick is zero, timeoutExpired will cascade the next bucket.
158   return tick == 0;
159 }
160
161 void HHWheelTimer::timeoutExpired() noexcept {
162   // If destroy() is called inside timeoutExpired(), delay actual destruction
163   // until timeoutExpired() returns
164   DestructorGuard dg(this);
165   // If scheduleTimeout is called from a callback in this function, it may
166   // cause inconsistencies in the state of this object. As such, we need
167   // to treat these calls slightly differently.
168   processingCallbacksGuard_ = true;
169   auto reEntryGuard = folly::makeGuard([&] {
170     processingCallbacksGuard_ = false;
171   });
172
173   // timeoutExpired() can only be invoked directly from the event base loop.
174   // It should never be invoked recursively.
175   //
176   milliseconds catchup = now_ + interval_;
177   // If catchup is enabled, we may have missed multiple intervals, use
178   // currentTime() to check exactly.
179   if (++expirationsSinceCatchup_ >= catchupEveryN_) {
180     catchup = std::chrono::duration_cast<milliseconds>(
181       std::chrono::steady_clock::now().time_since_epoch());
182     expirationsSinceCatchup_ = 0;
183   }
184   while (now_ < catchup) {
185     now_ += interval_;
186
187     int idx = nextTick_ & WHEEL_MASK;
188     if (0 == idx) {
189       // Cascade timers
190       if (cascadeTimers(1, (nextTick_ >> WHEEL_BITS) & WHEEL_MASK) &&
191           cascadeTimers(2, (nextTick_ >> (2 * WHEEL_BITS)) & WHEEL_MASK)) {
192         cascadeTimers(3, (nextTick_ >> (3 * WHEEL_BITS)) & WHEEL_MASK);
193       }
194     }
195
196     nextTick_++;
197     CallbackList* cbs = &buckets_[0][idx];
198     while (!cbs->empty()) {
199       auto* cb = &cbs->front();
200       cbs->pop_front();
201       count_--;
202       cb->wheel_ = nullptr;
203       cb->expiration_ = milliseconds(0);
204       auto old_ctx =
205         RequestContext::setContext(cb->context_);
206       cb->timeoutExpired();
207       RequestContext::setContext(old_ctx);
208     }
209   }
210   if (count_ > 0) {
211     this->AsyncTimeout::scheduleTimeout(interval_.count());
212   }
213 }
214
215 size_t HHWheelTimer::cancelAll() {
216   decltype(buckets_) buckets;
217
218 // Work around std::swap() bug in libc++
219 //
220 // http://llvm.org/bugs/show_bug.cgi?id=22106
221 #if FOLLY_USE_LIBCPP
222   for (size_t i = 0; i < WHEEL_BUCKETS; ++i) {
223     for (size_t ii = 0; ii < WHEEL_SIZE; ++ii) {
224       std::swap(buckets_[i][ii], buckets[i][ii]);
225     }
226   }
227 #else
228   std::swap(buckets, buckets_);
229 #endif
230
231   size_t count = 0;
232
233   for (auto& tick : buckets) {
234     for (auto& bucket : tick) {
235       while (!bucket.empty()) {
236         auto& cb = bucket.front();
237         cb.cancelTimeout();
238         cb.callbackCanceled();
239         count++;
240       }
241     }
242   }
243
244   return count;
245 }
246
247 } // folly