Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
7 #include "EventTokenBucket.h"
9 #include "nsICancelable.h"
10 #include "nsNetUtil.h"
11 #include "nsSocketTransportService2.h"
13 #ifdef DEBUG
14 #include "MainThreadUtils.h"
15 #endif
17 #ifdef XP_WIN
18 #include <windows.h>
19 #include <mmsystem.h>
20 #endif
22 extern PRThread *gSocketThread;
24 namespace mozilla {
25 namespace net {
27 ////////////////////////////////////////////
28 // EventTokenBucketCancelable
29 ////////////////////////////////////////////
31 class TokenBucketCancelable : public nsICancelable
32 {
33 public:
34 NS_DECL_THREADSAFE_ISUPPORTS
35 NS_DECL_NSICANCELABLE
37 TokenBucketCancelable(class ATokenBucketEvent *event);
38 virtual ~TokenBucketCancelable() {}
39 void Fire();
41 private:
42 friend class EventTokenBucket;
43 ATokenBucketEvent *mEvent;
44 };
46 NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable)
48 TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event)
49 : mEvent(event)
50 {
51 }
53 NS_IMETHODIMP
54 TokenBucketCancelable::Cancel(nsresult reason)
55 {
56 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
57 mEvent = nullptr;
58 return NS_OK;
59 }
61 void
62 TokenBucketCancelable::Fire()
63 {
64 if (!mEvent)
65 return;
67 ATokenBucketEvent *event = mEvent;
68 mEvent = nullptr;
69 event->OnTokenBucketAdmitted();
70 }
72 ////////////////////////////////////////////
73 // EventTokenBucket
74 ////////////////////////////////////////////
76 NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback)
78 // by default 1hz with no burst
79 EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond,
80 uint32_t burstSize)
81 : mUnitCost(kUsecPerSec)
82 , mMaxCredit(kUsecPerSec)
83 , mCredit(kUsecPerSec)
84 , mPaused(false)
85 , mStopped(false)
86 , mTimerArmed(false)
87 #ifdef XP_WIN
88 , mFineGrainTimerInUse(false)
89 , mFineGrainResetTimerArmed(false)
90 #endif
91 {
92 MOZ_COUNT_CTOR(EventTokenBucket);
93 mLastUpdate = TimeStamp::Now();
95 MOZ_ASSERT(NS_IsMainThread());
97 nsresult rv;
98 nsCOMPtr<nsIEventTarget> sts;
99 nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
100 if (NS_SUCCEEDED(rv))
101 sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
102 if (NS_SUCCEEDED(rv))
103 mTimer = do_CreateInstance("@mozilla.org/timer;1");
104 if (mTimer)
105 mTimer->SetTarget(sts);
106 SetRate(eventsPerSecond, burstSize);
107 }
109 EventTokenBucket::~EventTokenBucket()
110 {
111 SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n",
112 this, mEvents.GetSize()));
114 MOZ_COUNT_DTOR(EventTokenBucket);
115 if (mTimer && mTimerArmed)
116 mTimer->Cancel();
118 #ifdef XP_WIN
119 NormalTimers();
120 if (mFineGrainResetTimerArmed) {
121 mFineGrainResetTimerArmed = false;
122 mFineGrainResetTimer->Cancel();
123 }
124 #endif
126 // Complete any queued events to prevent hangs
127 while (mEvents.GetSize()) {
128 nsRefPtr<TokenBucketCancelable> cancelable =
129 dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
130 cancelable->Fire();
131 }
132 }
134 void
135 EventTokenBucket::SetRate(uint32_t eventsPerSecond,
136 uint32_t burstSize)
137 {
138 SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n",
139 this, eventsPerSecond, burstSize));
141 if (eventsPerSecond > kMaxHz) {
142 eventsPerSecond = kMaxHz;
143 SOCKET_LOG((" eventsPerSecond out of range\n"));
144 }
146 if (!eventsPerSecond) {
147 eventsPerSecond = 1;
148 SOCKET_LOG((" eventsPerSecond out of range\n"));
149 }
151 mUnitCost = kUsecPerSec / eventsPerSecond;
152 mMaxCredit = mUnitCost * burstSize;
153 if (mMaxCredit > kUsecPerSec * 60 * 15) {
154 SOCKET_LOG((" burstSize out of range\n"));
155 mMaxCredit = kUsecPerSec * 60 * 15;
156 }
157 mCredit = mMaxCredit;
158 mLastUpdate = TimeStamp::Now();
159 }
161 void
162 EventTokenBucket::ClearCredits()
163 {
164 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
165 SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this));
166 mCredit = 0;
167 }
169 uint32_t
170 EventTokenBucket::BurstEventsAvailable()
171 {
172 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
173 return static_cast<uint32_t>(mCredit / mUnitCost);
174 }
176 uint32_t
177 EventTokenBucket::QueuedEvents()
178 {
179 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
180 return mEvents.GetSize();
181 }
183 void
184 EventTokenBucket::Pause()
185 {
186 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
187 SOCKET_LOG(("EventTokenBucket::Pause %p\n", this));
188 if (mPaused || mStopped)
189 return;
191 mPaused = true;
192 if (mTimerArmed) {
193 mTimer->Cancel();
194 mTimerArmed = false;
195 }
196 }
198 void
199 EventTokenBucket::UnPause()
200 {
201 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
202 SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this));
203 if (!mPaused || mStopped)
204 return;
206 mPaused = false;
207 DispatchEvents();
208 UpdateTimer();
209 }
211 nsresult
212 EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable)
213 {
214 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
215 SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this));
217 if (mStopped || !mTimer)
218 return NS_ERROR_FAILURE;
220 UpdateCredits();
222 nsRefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event);
223 // When this function exits the cancelEvent needs 2 references, one for the
224 // mEvents queue and one for the caller of SubmitEvent()
226 NS_ADDREF(*cancelable = cancelEvent.get());
228 if (mPaused || !TryImmediateDispatch(cancelEvent.get())) {
229 // queue it
230 SOCKET_LOG((" queued\n"));
231 mEvents.Push(cancelEvent.forget().take());
232 UpdateTimer();
233 }
234 else {
235 SOCKET_LOG((" dispatched synchronously\n"));
236 }
238 return NS_OK;
239 }
241 bool
242 EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable)
243 {
244 if (mCredit < mUnitCost)
245 return false;
247 mCredit -= mUnitCost;
248 cancelable->Fire();
249 return true;
250 }
252 void
253 EventTokenBucket::DispatchEvents()
254 {
255 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
256 SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused));
257 if (mPaused || mStopped)
258 return;
260 while (mEvents.GetSize() && mUnitCost <= mCredit) {
261 nsRefPtr<TokenBucketCancelable> cancelable =
262 dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
263 if (cancelable->mEvent) {
264 SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] "
265 "Dispatching queue token bucket event cost=%lu credit=%lu\n",
266 this, mUnitCost, mCredit));
267 mCredit -= mUnitCost;
268 cancelable->Fire();
269 }
270 }
272 #ifdef XP_WIN
273 if (!mEvents.GetSize())
274 WantNormalTimers();
275 #endif
276 }
278 void
279 EventTokenBucket::UpdateTimer()
280 {
281 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
282 if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer)
283 return;
285 if (mCredit >= mUnitCost)
286 return;
288 // determine the time needed to wait to accumulate enough credits to admit
289 // one more event and set the timer for that point. Always round it
290 // up because firing early doesn't help.
291 //
292 uint64_t deficit = mUnitCost - mCredit;
293 uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec;
295 if (msecWait < 4) // minimum wait
296 msecWait = 4;
297 else if (msecWait > 60000) // maximum wait
298 msecWait = 60000;
300 #ifdef XP_WIN
301 FineGrainTimers();
302 #endif
304 SOCKET_LOG(("EventTokenBucket::UpdateTimer %p for %dms\n",
305 this, msecWait));
306 nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait),
307 nsITimer::TYPE_ONE_SHOT);
308 mTimerArmed = NS_SUCCEEDED(rv);
309 }
311 NS_IMETHODIMP
312 EventTokenBucket::Notify(nsITimer *timer)
313 {
314 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
316 #ifdef XP_WIN
317 if (timer == mFineGrainResetTimer) {
318 FineGrainResetTimerNotify();
319 return NS_OK;
320 }
321 #endif
323 SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this));
324 mTimerArmed = false;
325 if (mStopped)
326 return NS_OK;
328 UpdateCredits();
329 DispatchEvents();
330 UpdateTimer();
332 return NS_OK;
333 }
335 void
336 EventTokenBucket::UpdateCredits()
337 {
338 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
340 TimeStamp now = TimeStamp::Now();
341 TimeDuration elapsed = now - mLastUpdate;
342 mLastUpdate = now;
344 mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds());
345 if (mCredit > mMaxCredit)
346 mCredit = mMaxCredit;
347 SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %lu (%lu each.. %3.2f)\n",
348 this, mCredit, mUnitCost, (double)mCredit / mUnitCost));
349 }
351 #ifdef XP_WIN
352 void
353 EventTokenBucket::FineGrainTimers()
354 {
355 SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n",
356 this, mFineGrainTimerInUse));
358 mLastFineGrainTimerUse = TimeStamp::Now();
360 if (mFineGrainTimerInUse)
361 return;
363 if (mUnitCost > kCostFineGrainThreshold)
364 return;
366 SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n",
367 this));
369 mFineGrainTimerInUse = true;
370 timeBeginPeriod(1);
371 }
373 void
374 EventTokenBucket::NormalTimers()
375 {
376 if (!mFineGrainTimerInUse)
377 return;
378 mFineGrainTimerInUse = false;
380 SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this));
381 timeEndPeriod(1);
382 }
384 void
385 EventTokenBucket::WantNormalTimers()
386 {
387 if (!mFineGrainTimerInUse)
388 return;
389 if (mFineGrainResetTimerArmed)
390 return;
392 TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse);
393 static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5);
395 if (elapsed >= fiveSeconds) {
396 NormalTimers();
397 return;
398 }
400 if (!mFineGrainResetTimer)
401 mFineGrainResetTimer = do_CreateInstance("@mozilla.org/timer;1");
403 // if we can't delay the reset, just do it now
404 if (!mFineGrainResetTimer) {
405 NormalTimers();
406 return;
407 }
409 // pad the callback out 100ms to avoid having to round trip this again if the
410 // timer calls back just a tad early.
411 SOCKET_LOG(("EventTokenBucket::WantNormalTimers %p "
412 "Will reset timer granularity after delay", this));
414 mFineGrainResetTimer->InitWithCallback(
415 this,
416 static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100,
417 nsITimer::TYPE_ONE_SHOT);
418 mFineGrainResetTimerArmed = true;
419 }
421 void
422 EventTokenBucket::FineGrainResetTimerNotify()
423 {
424 SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n",
425 this, mEvents.GetSize()));
426 mFineGrainResetTimerArmed = false;
428 // If we are currently processing events then wait for the queue to drain
429 // before trying to reset back to normal timers again
430 if (!mEvents.GetSize())
431 WantNormalTimers();
432 }
434 #endif
436 } // mozilla::net
437 } // mozilla