/src/mozilla-central/netwerk/base/EventTokenBucket.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "EventTokenBucket.h" |
8 | | |
9 | | #include "nsICancelable.h" |
10 | | #include "nsIIOService.h" |
11 | | #include "nsNetCID.h" |
12 | | #include "nsNetUtil.h" |
13 | | #include "nsServiceManagerUtils.h" |
14 | | #include "nsSocketTransportService2.h" |
15 | | #ifdef DEBUG |
16 | | #include "MainThreadUtils.h" |
17 | | #endif |
18 | | |
19 | | #ifdef XP_WIN |
20 | | #include <windows.h> |
21 | | #include <mmsystem.h> |
22 | | #endif |
23 | | |
24 | | namespace mozilla { |
25 | | namespace net { |
26 | | |
27 | | //////////////////////////////////////////// |
28 | | // EventTokenBucketCancelable |
29 | | //////////////////////////////////////////// |
30 | | |
31 | | class TokenBucketCancelable : public nsICancelable |
32 | | { |
33 | | public: |
34 | | NS_DECL_THREADSAFE_ISUPPORTS |
35 | | NS_DECL_NSICANCELABLE |
36 | | |
37 | | explicit TokenBucketCancelable(class ATokenBucketEvent *event); |
38 | | void Fire(); |
39 | | |
40 | | private: |
41 | 0 | virtual ~TokenBucketCancelable() = default; |
42 | | |
43 | | friend class EventTokenBucket; |
44 | | ATokenBucketEvent *mEvent; |
45 | | }; |
46 | | |
47 | | NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) |
48 | | |
49 | | TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event) |
50 | | : mEvent(event) |
51 | 0 | { |
52 | 0 | } |
53 | | |
54 | | NS_IMETHODIMP |
55 | | TokenBucketCancelable::Cancel(nsresult reason) |
56 | 0 | { |
57 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
58 | 0 | mEvent = nullptr; |
59 | 0 | return NS_OK; |
60 | 0 | } |
61 | | |
62 | | void |
63 | | TokenBucketCancelable::Fire() |
64 | 0 | { |
65 | 0 | if (!mEvent) |
66 | 0 | return; |
67 | 0 | |
68 | 0 | ATokenBucketEvent *event = mEvent; |
69 | 0 | mEvent = nullptr; |
70 | 0 | event->OnTokenBucketAdmitted(); |
71 | 0 | } |
72 | | |
73 | | //////////////////////////////////////////// |
74 | | // EventTokenBucket |
75 | | //////////////////////////////////////////// |
76 | | |
77 | | NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback, nsINamed) |
78 | | |
79 | | // by default 1hz with no burst |
80 | | EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, |
81 | | uint32_t burstSize) |
82 | | : mUnitCost(kUsecPerSec) |
83 | | , mMaxCredit(kUsecPerSec) |
84 | | , mCredit(kUsecPerSec) |
85 | | , mPaused(false) |
86 | | , mStopped(false) |
87 | | , mTimerArmed(false) |
88 | | #ifdef XP_WIN |
89 | | , mFineGrainTimerInUse(false) |
90 | | , mFineGrainResetTimerArmed(false) |
91 | | #endif |
92 | 1 | { |
93 | 1 | mLastUpdate = TimeStamp::Now(); |
94 | 1 | |
95 | 1 | MOZ_ASSERT(NS_IsMainThread()); |
96 | 1 | |
97 | 1 | nsresult rv; |
98 | 1 | nsCOMPtr<nsIEventTarget> sts; |
99 | 1 | nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); |
100 | 1 | if (NS_SUCCEEDED(rv)) |
101 | 1 | sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); |
102 | 1 | if (NS_SUCCEEDED(rv)) |
103 | 1 | mTimer = NS_NewTimer(sts); |
104 | 1 | SetRate(eventsPerSecond, burstSize); |
105 | 1 | } |
106 | | |
107 | | EventTokenBucket::~EventTokenBucket() |
108 | 0 | { |
109 | 0 | SOCKET_LOG(("EventTokenBucket::dtor %p events=%zu\n", |
110 | 0 | this, mEvents.GetSize())); |
111 | 0 |
|
112 | 0 | CleanupTimers(); |
113 | 0 |
|
114 | 0 | // Complete any queued events to prevent hangs |
115 | 0 | while (mEvents.GetSize()) { |
116 | 0 | RefPtr<TokenBucketCancelable> cancelable = |
117 | 0 | dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront())); |
118 | 0 | cancelable->Fire(); |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | void |
123 | | EventTokenBucket::CleanupTimers() |
124 | 0 | { |
125 | 0 | if (mTimer && mTimerArmed) { |
126 | 0 | mTimer->Cancel(); |
127 | 0 | } |
128 | 0 | mTimer = nullptr; |
129 | 0 | mTimerArmed = false; |
130 | 0 |
|
131 | | #ifdef XP_WIN |
132 | | NormalTimers(); |
133 | | if (mFineGrainResetTimer && mFineGrainResetTimerArmed) { |
134 | | mFineGrainResetTimer->Cancel(); |
135 | | } |
136 | | mFineGrainResetTimer = nullptr; |
137 | | mFineGrainResetTimerArmed = false; |
138 | | #endif |
139 | | } |
140 | | |
141 | | void |
142 | | EventTokenBucket::SetRate(uint32_t eventsPerSecond, |
143 | | uint32_t burstSize) |
144 | 1 | { |
145 | 1 | SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", |
146 | 1 | this, eventsPerSecond, burstSize)); |
147 | 1 | |
148 | 1 | if (eventsPerSecond > kMaxHz) { |
149 | 0 | eventsPerSecond = kMaxHz; |
150 | 0 | SOCKET_LOG((" eventsPerSecond out of range\n")); |
151 | 0 | } |
152 | 1 | |
153 | 1 | if (!eventsPerSecond) { |
154 | 0 | eventsPerSecond = 1; |
155 | 0 | SOCKET_LOG((" eventsPerSecond out of range\n")); |
156 | 0 | } |
157 | 1 | |
158 | 1 | mUnitCost = kUsecPerSec / eventsPerSecond; |
159 | 1 | mMaxCredit = mUnitCost * burstSize; |
160 | 1 | if (mMaxCredit > kUsecPerSec * 60 * 15) { |
161 | 0 | SOCKET_LOG((" burstSize out of range\n")); |
162 | 0 | mMaxCredit = kUsecPerSec * 60 * 15; |
163 | 0 | } |
164 | 1 | mCredit = mMaxCredit; |
165 | 1 | mLastUpdate = TimeStamp::Now(); |
166 | 1 | } |
167 | | |
168 | | void |
169 | | EventTokenBucket::ClearCredits() |
170 | 0 | { |
171 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
172 | 0 | SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); |
173 | 0 | mCredit = 0; |
174 | 0 | } |
175 | | |
176 | | uint32_t |
177 | | EventTokenBucket::BurstEventsAvailable() |
178 | 0 | { |
179 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
180 | 0 | return static_cast<uint32_t>(mCredit / mUnitCost); |
181 | 0 | } |
182 | | |
183 | | uint32_t |
184 | | EventTokenBucket::QueuedEvents() |
185 | 0 | { |
186 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
187 | 0 | return mEvents.GetSize(); |
188 | 0 | } |
189 | | |
190 | | void |
191 | | EventTokenBucket::Pause() |
192 | 0 | { |
193 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
194 | 0 | SOCKET_LOG(("EventTokenBucket::Pause %p\n", this)); |
195 | 0 | if (mPaused || mStopped) |
196 | 0 | return; |
197 | 0 | |
198 | 0 | mPaused = true; |
199 | 0 | if (mTimerArmed) { |
200 | 0 | mTimer->Cancel(); |
201 | 0 | mTimerArmed = false; |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | void |
206 | | EventTokenBucket::UnPause() |
207 | 0 | { |
208 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
209 | 0 | SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this)); |
210 | 0 | if (!mPaused || mStopped) |
211 | 0 | return; |
212 | 0 | |
213 | 0 | mPaused = false; |
214 | 0 | DispatchEvents(); |
215 | 0 | UpdateTimer(); |
216 | 0 | } |
217 | | |
218 | | void |
219 | | EventTokenBucket::Stop() |
220 | 0 | { |
221 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
222 | 0 | SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed)); |
223 | 0 | mStopped = true; |
224 | 0 | CleanupTimers(); |
225 | 0 |
|
226 | 0 | // Complete any queued events to prevent hangs |
227 | 0 | while (mEvents.GetSize()) { |
228 | 0 | RefPtr<TokenBucketCancelable> cancelable = |
229 | 0 | dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront())); |
230 | 0 | cancelable->Fire(); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | nsresult |
235 | | EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable) |
236 | 0 | { |
237 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
238 | 0 | SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this)); |
239 | 0 |
|
240 | 0 | if (mStopped || !mTimer) |
241 | 0 | return NS_ERROR_FAILURE; |
242 | 0 | |
243 | 0 | UpdateCredits(); |
244 | 0 |
|
245 | 0 | RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event); |
246 | 0 | // When this function exits the cancelEvent needs 2 references, one for the |
247 | 0 | // mEvents queue and one for the caller of SubmitEvent() |
248 | 0 |
|
249 | 0 | NS_ADDREF(*cancelable = cancelEvent.get()); |
250 | 0 |
|
251 | 0 | if (mPaused || !TryImmediateDispatch(cancelEvent.get())) { |
252 | 0 | // queue it |
253 | 0 | SOCKET_LOG((" queued\n")); |
254 | 0 | mEvents.Push(cancelEvent.forget().take()); |
255 | 0 | UpdateTimer(); |
256 | 0 | } |
257 | 0 | else { |
258 | 0 | SOCKET_LOG((" dispatched synchronously\n")); |
259 | 0 | } |
260 | 0 |
|
261 | 0 | return NS_OK; |
262 | 0 | } |
263 | | |
264 | | bool |
265 | | EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable) |
266 | 0 | { |
267 | 0 | if (mCredit < mUnitCost) |
268 | 0 | return false; |
269 | 0 | |
270 | 0 | mCredit -= mUnitCost; |
271 | 0 | cancelable->Fire(); |
272 | 0 | return true; |
273 | 0 | } |
274 | | |
275 | | void |
276 | | EventTokenBucket::DispatchEvents() |
277 | 0 | { |
278 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
279 | 0 | SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused)); |
280 | 0 | if (mPaused || mStopped) |
281 | 0 | return; |
282 | 0 | |
283 | 0 | while (mEvents.GetSize() && mUnitCost <= mCredit) { |
284 | 0 | RefPtr<TokenBucketCancelable> cancelable = |
285 | 0 | dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront())); |
286 | 0 | if (cancelable->mEvent) { |
287 | 0 | SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] " |
288 | 0 | "Dispatching queue token bucket event cost=%" PRIu64 " credit=%" PRIu64 "\n", |
289 | 0 | this, mUnitCost, mCredit)); |
290 | 0 | mCredit -= mUnitCost; |
291 | 0 | cancelable->Fire(); |
292 | 0 | } |
293 | 0 | } |
294 | 0 |
|
295 | | #ifdef XP_WIN |
296 | | if (!mEvents.GetSize()) |
297 | | WantNormalTimers(); |
298 | | #endif |
299 | | } |
300 | | |
301 | | void |
302 | | EventTokenBucket::UpdateTimer() |
303 | 0 | { |
304 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
305 | 0 | if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) |
306 | 0 | return; |
307 | 0 | |
308 | 0 | if (mCredit >= mUnitCost) |
309 | 0 | return; |
310 | 0 | |
311 | 0 | // determine the time needed to wait to accumulate enough credits to admit |
312 | 0 | // one more event and set the timer for that point. Always round it |
313 | 0 | // up because firing early doesn't help. |
314 | 0 | // |
315 | 0 | uint64_t deficit = mUnitCost - mCredit; |
316 | 0 | uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec; |
317 | 0 |
|
318 | 0 | if (msecWait < 4) // minimum wait |
319 | 0 | msecWait = 4; |
320 | 0 | else if (msecWait > 60000) // maximum wait |
321 | 0 | msecWait = 60000; |
322 | 0 |
|
323 | | #ifdef XP_WIN |
324 | | FineGrainTimers(); |
325 | | #endif |
326 | |
|
327 | 0 | SOCKET_LOG(("EventTokenBucket::UpdateTimer %p for %" PRIu64 "ms\n", |
328 | 0 | this, msecWait)); |
329 | 0 | nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait), |
330 | 0 | nsITimer::TYPE_ONE_SHOT); |
331 | 0 | mTimerArmed = NS_SUCCEEDED(rv); |
332 | 0 | } |
333 | | |
334 | | NS_IMETHODIMP |
335 | | EventTokenBucket::Notify(nsITimer *timer) |
336 | 0 | { |
337 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
338 | 0 |
|
339 | | #ifdef XP_WIN |
340 | | if (timer == mFineGrainResetTimer) { |
341 | | FineGrainResetTimerNotify(); |
342 | | return NS_OK; |
343 | | } |
344 | | #endif |
345 | |
|
346 | 0 | SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this)); |
347 | 0 | mTimerArmed = false; |
348 | 0 | if (mStopped) |
349 | 0 | return NS_OK; |
350 | 0 | |
351 | 0 | UpdateCredits(); |
352 | 0 | DispatchEvents(); |
353 | 0 | UpdateTimer(); |
354 | 0 |
|
355 | 0 | return NS_OK; |
356 | 0 | } |
357 | | |
358 | | NS_IMETHODIMP |
359 | | EventTokenBucket::GetName(nsACString& aName) |
360 | 0 | { |
361 | 0 | aName.AssignLiteral("EventTokenBucket"); |
362 | 0 | return NS_OK; |
363 | 0 | } |
364 | | |
365 | | void |
366 | | EventTokenBucket::UpdateCredits() |
367 | 0 | { |
368 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
369 | 0 |
|
370 | 0 | TimeStamp now = TimeStamp::Now(); |
371 | 0 | TimeDuration elapsed = now - mLastUpdate; |
372 | 0 | mLastUpdate = now; |
373 | 0 |
|
374 | 0 | mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds()); |
375 | 0 | if (mCredit > mMaxCredit) |
376 | 0 | mCredit = mMaxCredit; |
377 | 0 | SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %" PRIu64 " (%" PRIu64 " each.. %3.2f)\n", |
378 | 0 | this, mCredit, mUnitCost, (double)mCredit / mUnitCost)); |
379 | 0 | } |
380 | | |
381 | | #ifdef XP_WIN |
382 | | void |
383 | | EventTokenBucket::FineGrainTimers() |
384 | | { |
385 | | SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n", |
386 | | this, mFineGrainTimerInUse)); |
387 | | |
388 | | mLastFineGrainTimerUse = TimeStamp::Now(); |
389 | | |
390 | | if (mFineGrainTimerInUse) |
391 | | return; |
392 | | |
393 | | if (mUnitCost > kCostFineGrainThreshold) |
394 | | return; |
395 | | |
396 | | SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", |
397 | | this)); |
398 | | |
399 | | mFineGrainTimerInUse = true; |
400 | | timeBeginPeriod(1); |
401 | | } |
402 | | |
403 | | void |
404 | | EventTokenBucket::NormalTimers() |
405 | | { |
406 | | if (!mFineGrainTimerInUse) |
407 | | return; |
408 | | mFineGrainTimerInUse = false; |
409 | | |
410 | | SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this)); |
411 | | timeEndPeriod(1); |
412 | | } |
413 | | |
414 | | void |
415 | | EventTokenBucket::WantNormalTimers() |
416 | | { |
417 | | if (!mFineGrainTimerInUse) |
418 | | return; |
419 | | if (mFineGrainResetTimerArmed) |
420 | | return; |
421 | | |
422 | | TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse); |
423 | | static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5); |
424 | | |
425 | | if (elapsed >= fiveSeconds) { |
426 | | NormalTimers(); |
427 | | return; |
428 | | } |
429 | | |
430 | | if (!mFineGrainResetTimer) |
431 | | mFineGrainResetTimer = NS_NewTimer(); |
432 | | |
433 | | // if we can't delay the reset, just do it now |
434 | | if (!mFineGrainResetTimer) { |
435 | | NormalTimers(); |
436 | | return; |
437 | | } |
438 | | |
439 | | // pad the callback out 100ms to avoid having to round trip this again if the |
440 | | // timer calls back just a tad early. |
441 | | SOCKET_LOG(("EventTokenBucket::WantNormalTimers %p " |
442 | | "Will reset timer granularity after delay", this)); |
443 | | |
444 | | mFineGrainResetTimer->InitWithCallback( |
445 | | this, |
446 | | static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100, |
447 | | nsITimer::TYPE_ONE_SHOT); |
448 | | mFineGrainResetTimerArmed = true; |
449 | | } |
450 | | |
451 | | void |
452 | | EventTokenBucket::FineGrainResetTimerNotify() |
453 | | { |
454 | | SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n", |
455 | | this, mEvents.GetSize())); |
456 | | mFineGrainResetTimerArmed = false; |
457 | | |
458 | | // If we are currently processing events then wait for the queue to drain |
459 | | // before trying to reset back to normal timers again |
460 | | if (!mEvents.GetSize()) |
461 | | WantNormalTimers(); |
462 | | } |
463 | | |
464 | | #endif |
465 | | |
466 | | } // namespace net |
467 | | } // namespace mozilla |