proxygen
AsyncTimeoutSet.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
11 
12 #include <folly/ScopeGuard.h>
13 #include <folly/io/async/Request.h>
14 
15 using std::chrono::milliseconds;
16 
17 namespace proxygen {
18 
20  public:
21  std::chrono::milliseconds millisecondsSinceEpoch() override {
23  }
24 };
25 
27  static SimpleTimeoutClock timeoutClock;
28 
29  return timeoutClock;
30 }
31 
33  if (isScheduled()) {
34  cancelTimeout();
35  }
36 }
37 
39  Callback* prev) {
40  assert(timeoutSet_ == nullptr);
41  assert(prev_ == nullptr);
42  assert(next_ == nullptr);
43  assert(!timePointInitialized(expiration_));
44 
45  timeoutSet_ = timeoutSet;
46  prev_ = prev;
47  next_ = nullptr;
48  expiration_ = timeoutSet->timeoutClock_.millisecondsSinceEpoch() +
49  timeoutSet_->getInterval();
50 }
51 
53  if (next_ == nullptr) {
54  assert(timeoutSet_->tail_ == this);
55  timeoutSet_->tail_ = prev_;
56  } else {
57  assert(timeoutSet_->tail_ != this);
58  next_->prev_ = prev_;
59  }
60 
61  if (prev_ == nullptr) {
62  assert(timeoutSet_->head_ == this);
63  timeoutSet_->head_ = next_;
64  timeoutSet_->headChanged();
65  } else {
66  assert(timeoutSet_->head_ != this);
67  prev_->next_ = next_;
68  }
69 
70  timeoutSet_ = nullptr;
71  prev_ = nullptr;
72  next_ = nullptr;
73  expiration_ = {};
74 }
75 
77  milliseconds intervalMS,
78  milliseconds atMostEveryN,
79  TimeoutClock* timeoutClock)
80  : folly::AsyncTimeout(timeoutManager),
81  timeoutClock_(timeoutClock ? *timeoutClock : getTimeoutClock()),
82  head_(nullptr),
83  tail_(nullptr),
84  interval_(intervalMS),
85  atMostEveryN_(atMostEveryN) {
86 }
87 
89  InternalEnum internal,
90  milliseconds intervalMS,
91  milliseconds atMostEveryN)
92  : folly::AsyncTimeout(timeoutManager, internal),
93  timeoutClock_(getTimeoutClock()),
94  head_(nullptr),
95  tail_(nullptr),
96  interval_(intervalMS),
97  atMostEveryN_(atMostEveryN) {
98 }
99 
101  // DelayedDestruction should ensure that we are never destroyed while inside
102  // a call to timeoutExpired().
103  assert(!inTimeoutExpired_);
104 
105  // destroy() should have already cleared out the timeout list.
106  // It's a bug if anyone tries to keep using the AsyncTimeoutSet after
107  // calling destroy, so no new timeouts may have been scheduled since then.
108  assert(head_ == nullptr);
109  assert(tail_ == nullptr);
110 }
111 
113  // If there are any timeout callbacks pending, get rid of them without ever
114  // invoking them. This is somewhat undesirable from the callback's
115  // perspective (how is it supposed to know that it will never get invoked?).
116  // Most users probably only want to destroy a AsyncTimeoutSet when it has no
117  // callbacks remaining. Otherwise they need to implement their own code to
118  // take care of cleaning up the callbacks that will never be invoked.
119 
120  // cancel from tail to head, to avoid extra calls to headChanged
121  while (tail_ != nullptr) {
122  tail_->cancelTimeout();
123  }
124 
126 }
127 
129  // Cancel the callback if it happens to be scheduled already.
130  callback->cancelTimeout();
131  assert(callback->prev_ == nullptr);
132  assert(callback->next_ == nullptr);
133 
135 
136  Callback* old_tail = tail_;
137  if (head_ == nullptr) {
138  // We don't have any timeouts scheduled already. We have to schedule
139  // ourself.
140  assert(tail_ == nullptr);
141  assert(!isScheduled());
142  if (!inTimeoutExpired_) {
143  this->folly::AsyncTimeout::scheduleTimeout(interval_.count());
144  }
145  head_ = callback;
146  tail_ = callback;
147  } else {
148  assert(inTimeoutExpired_ || isScheduled());
149  assert(tail_->next_ == nullptr);
150  tail_->next_ = callback;
151  tail_ = callback;
152  }
153 
154  // callback->prev_ = tail_;
155  callback->setScheduled(this, old_tail);
156 }
157 
159  if (inTimeoutExpired_) {
160  // timeoutExpired() will always update the scheduling correctly before it
161  // returns. No need to change the state now, since we are just going to
162  // change it again later.
163  return;
164  }
165 
166  if (!head_) {
168  } else {
169  milliseconds delta =
170  head_->getTimeRemaining(timeoutClock_.millisecondsSinceEpoch());
171  this->folly::AsyncTimeout::scheduleTimeout(delta.count());
172  }
173 }
174 
176  // If destroy() is called inside timeoutExpired(), delay actual destruction
177  // until timeoutExpired() returns
178  DestructorGuard dg(this);
179 
180  // timeoutExpired() can only be invoked directly from the event base loop.
181  // It should never be invoked recursively.
182  //
183  // Set inTimeoutExpired_ to true, so that we won't bother rescheduling the
184  // main AsyncTimeout inside timeoutExpired(). We'll always make sure this
185  // is up-to-date before we return. This simply prevents us from
186  // unnecessarily modifying the main timeout heap multiple times before we
187  // return.
188  assert(!inTimeoutExpired_);
189  inTimeoutExpired_ = true;
190  SCOPE_EXIT { inTimeoutExpired_ = false; };
191 
192  // Get the current time.
193  // For now we only compute the current time at the start of the loop.
194  // If a callback takes a very long time to execute its timeoutExpired()
195  // method, this value could potentially get stale.
196  //
197  // However, this should be rare, and it doesn't seem worth the overhead of
198  // recomputing the current time each time around the loop. If the value does
199  // go stale, we won't invoke as many callbacks as we could. They will have
200  // to wait until the next call to timeoutExpired(). However, we could also
201  // end up rescheduling the next timeoutExpired() call a bit late if now gets
202  // stale. If we find that this becomes a problem in practice we could be
203  // more smart about when we recompute the current time.
204  auto now = timeoutClock_.millisecondsSinceEpoch();
205 
206  while (head_ != nullptr) {
207  milliseconds delta = head_->getTimeRemaining(now);
208  if (delta > milliseconds(0)) {
209  if (delta < atMostEveryN_) {
210  delta = atMostEveryN_;
211  }
212  this->folly::AsyncTimeout::scheduleTimeout(delta.count());
213  break;
214  }
215 
216  // Remember the callback to invoke, since calling cancelTimeout()
217  // on it will modify head_.
218  Callback* cb = head_;
219  head_->cancelTimeout();
220  auto old_ctx =
222  cb->timeoutExpired();
224  }
225 }
226 
227 }
static std::shared_ptr< RequestContext > setContext(std::shared_ptr< RequestContext > ctx)
Definition: Request.cpp:227
void scheduleTimeout(Callback *callback)
AsyncTimeoutSet::TimeoutClock & getTimeoutClock()
std::chrono::steady_clock::time_point now()
std::shared_ptr< folly::RequestContext > context_
#define SCOPE_EXIT
Definition: ScopeGuard.h:274
void timeoutExpired() noexceptoverride
virtual void timeoutExpired() noexcept=0
requires E e noexcept(noexcept(s.error(std::move(e))))
virtual std::chrono::milliseconds millisecondsSinceEpoch()=0
static void destroy()
static std::shared_ptr< RequestContext > saveContext()
Definition: Request.h:196
std::chrono::milliseconds millisecondsSinceEpoch() override
bool scheduleTimeout(uint32_t milliseconds)
void setScheduled(AsyncTimeoutSet *timeoutSet, Callback *prev)
AsyncTimeoutSet(folly::TimeoutManager *timeoutManager, std::chrono::milliseconds intervalMS, std::chrono::milliseconds atMostEveryN=std::chrono::milliseconds(0), TimeoutClock *timeoutClock=nullptr)
std::chrono::milliseconds millisecondsSinceEpoch()
Definition: Time.h:56
bool timePointInitialized(const T &time)
Definition: Time.h:35