|
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 "nsNetUtil.h" |
|
11 #include "nsSocketTransportService2.h" |
|
12 |
|
13 #ifdef DEBUG |
|
14 #include "MainThreadUtils.h" |
|
15 #endif |
|
16 |
|
17 #ifdef XP_WIN |
|
18 #include <windows.h> |
|
19 #include <mmsystem.h> |
|
20 #endif |
|
21 |
|
22 extern PRThread *gSocketThread; |
|
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 TokenBucketCancelable(class ATokenBucketEvent *event); |
|
38 virtual ~TokenBucketCancelable() {} |
|
39 void Fire(); |
|
40 |
|
41 private: |
|
42 friend class EventTokenBucket; |
|
43 ATokenBucketEvent *mEvent; |
|
44 }; |
|
45 |
|
46 NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) |
|
47 |
|
48 TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event) |
|
49 : mEvent(event) |
|
50 { |
|
51 } |
|
52 |
|
53 NS_IMETHODIMP |
|
54 TokenBucketCancelable::Cancel(nsresult reason) |
|
55 { |
|
56 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
57 mEvent = nullptr; |
|
58 return NS_OK; |
|
59 } |
|
60 |
|
61 void |
|
62 TokenBucketCancelable::Fire() |
|
63 { |
|
64 if (!mEvent) |
|
65 return; |
|
66 |
|
67 ATokenBucketEvent *event = mEvent; |
|
68 mEvent = nullptr; |
|
69 event->OnTokenBucketAdmitted(); |
|
70 } |
|
71 |
|
72 //////////////////////////////////////////// |
|
73 // EventTokenBucket |
|
74 //////////////////////////////////////////// |
|
75 |
|
76 NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback) |
|
77 |
|
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(); |
|
94 |
|
95 MOZ_ASSERT(NS_IsMainThread()); |
|
96 |
|
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 } |
|
108 |
|
109 EventTokenBucket::~EventTokenBucket() |
|
110 { |
|
111 SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n", |
|
112 this, mEvents.GetSize())); |
|
113 |
|
114 MOZ_COUNT_DTOR(EventTokenBucket); |
|
115 if (mTimer && mTimerArmed) |
|
116 mTimer->Cancel(); |
|
117 |
|
118 #ifdef XP_WIN |
|
119 NormalTimers(); |
|
120 if (mFineGrainResetTimerArmed) { |
|
121 mFineGrainResetTimerArmed = false; |
|
122 mFineGrainResetTimer->Cancel(); |
|
123 } |
|
124 #endif |
|
125 |
|
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 } |
|
133 |
|
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)); |
|
140 |
|
141 if (eventsPerSecond > kMaxHz) { |
|
142 eventsPerSecond = kMaxHz; |
|
143 SOCKET_LOG((" eventsPerSecond out of range\n")); |
|
144 } |
|
145 |
|
146 if (!eventsPerSecond) { |
|
147 eventsPerSecond = 1; |
|
148 SOCKET_LOG((" eventsPerSecond out of range\n")); |
|
149 } |
|
150 |
|
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 } |
|
160 |
|
161 void |
|
162 EventTokenBucket::ClearCredits() |
|
163 { |
|
164 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
165 SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); |
|
166 mCredit = 0; |
|
167 } |
|
168 |
|
169 uint32_t |
|
170 EventTokenBucket::BurstEventsAvailable() |
|
171 { |
|
172 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
173 return static_cast<uint32_t>(mCredit / mUnitCost); |
|
174 } |
|
175 |
|
176 uint32_t |
|
177 EventTokenBucket::QueuedEvents() |
|
178 { |
|
179 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
180 return mEvents.GetSize(); |
|
181 } |
|
182 |
|
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; |
|
190 |
|
191 mPaused = true; |
|
192 if (mTimerArmed) { |
|
193 mTimer->Cancel(); |
|
194 mTimerArmed = false; |
|
195 } |
|
196 } |
|
197 |
|
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; |
|
205 |
|
206 mPaused = false; |
|
207 DispatchEvents(); |
|
208 UpdateTimer(); |
|
209 } |
|
210 |
|
211 nsresult |
|
212 EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable) |
|
213 { |
|
214 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
215 SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this)); |
|
216 |
|
217 if (mStopped || !mTimer) |
|
218 return NS_ERROR_FAILURE; |
|
219 |
|
220 UpdateCredits(); |
|
221 |
|
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() |
|
225 |
|
226 NS_ADDREF(*cancelable = cancelEvent.get()); |
|
227 |
|
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 } |
|
237 |
|
238 return NS_OK; |
|
239 } |
|
240 |
|
241 bool |
|
242 EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable) |
|
243 { |
|
244 if (mCredit < mUnitCost) |
|
245 return false; |
|
246 |
|
247 mCredit -= mUnitCost; |
|
248 cancelable->Fire(); |
|
249 return true; |
|
250 } |
|
251 |
|
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; |
|
259 |
|
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 } |
|
271 |
|
272 #ifdef XP_WIN |
|
273 if (!mEvents.GetSize()) |
|
274 WantNormalTimers(); |
|
275 #endif |
|
276 } |
|
277 |
|
278 void |
|
279 EventTokenBucket::UpdateTimer() |
|
280 { |
|
281 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
282 if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) |
|
283 return; |
|
284 |
|
285 if (mCredit >= mUnitCost) |
|
286 return; |
|
287 |
|
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; |
|
294 |
|
295 if (msecWait < 4) // minimum wait |
|
296 msecWait = 4; |
|
297 else if (msecWait > 60000) // maximum wait |
|
298 msecWait = 60000; |
|
299 |
|
300 #ifdef XP_WIN |
|
301 FineGrainTimers(); |
|
302 #endif |
|
303 |
|
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 } |
|
310 |
|
311 NS_IMETHODIMP |
|
312 EventTokenBucket::Notify(nsITimer *timer) |
|
313 { |
|
314 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
315 |
|
316 #ifdef XP_WIN |
|
317 if (timer == mFineGrainResetTimer) { |
|
318 FineGrainResetTimerNotify(); |
|
319 return NS_OK; |
|
320 } |
|
321 #endif |
|
322 |
|
323 SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this)); |
|
324 mTimerArmed = false; |
|
325 if (mStopped) |
|
326 return NS_OK; |
|
327 |
|
328 UpdateCredits(); |
|
329 DispatchEvents(); |
|
330 UpdateTimer(); |
|
331 |
|
332 return NS_OK; |
|
333 } |
|
334 |
|
335 void |
|
336 EventTokenBucket::UpdateCredits() |
|
337 { |
|
338 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
339 |
|
340 TimeStamp now = TimeStamp::Now(); |
|
341 TimeDuration elapsed = now - mLastUpdate; |
|
342 mLastUpdate = now; |
|
343 |
|
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 } |
|
350 |
|
351 #ifdef XP_WIN |
|
352 void |
|
353 EventTokenBucket::FineGrainTimers() |
|
354 { |
|
355 SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n", |
|
356 this, mFineGrainTimerInUse)); |
|
357 |
|
358 mLastFineGrainTimerUse = TimeStamp::Now(); |
|
359 |
|
360 if (mFineGrainTimerInUse) |
|
361 return; |
|
362 |
|
363 if (mUnitCost > kCostFineGrainThreshold) |
|
364 return; |
|
365 |
|
366 SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", |
|
367 this)); |
|
368 |
|
369 mFineGrainTimerInUse = true; |
|
370 timeBeginPeriod(1); |
|
371 } |
|
372 |
|
373 void |
|
374 EventTokenBucket::NormalTimers() |
|
375 { |
|
376 if (!mFineGrainTimerInUse) |
|
377 return; |
|
378 mFineGrainTimerInUse = false; |
|
379 |
|
380 SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this)); |
|
381 timeEndPeriod(1); |
|
382 } |
|
383 |
|
384 void |
|
385 EventTokenBucket::WantNormalTimers() |
|
386 { |
|
387 if (!mFineGrainTimerInUse) |
|
388 return; |
|
389 if (mFineGrainResetTimerArmed) |
|
390 return; |
|
391 |
|
392 TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse); |
|
393 static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5); |
|
394 |
|
395 if (elapsed >= fiveSeconds) { |
|
396 NormalTimers(); |
|
397 return; |
|
398 } |
|
399 |
|
400 if (!mFineGrainResetTimer) |
|
401 mFineGrainResetTimer = do_CreateInstance("@mozilla.org/timer;1"); |
|
402 |
|
403 // if we can't delay the reset, just do it now |
|
404 if (!mFineGrainResetTimer) { |
|
405 NormalTimers(); |
|
406 return; |
|
407 } |
|
408 |
|
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)); |
|
413 |
|
414 mFineGrainResetTimer->InitWithCallback( |
|
415 this, |
|
416 static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100, |
|
417 nsITimer::TYPE_ONE_SHOT); |
|
418 mFineGrainResetTimerArmed = true; |
|
419 } |
|
420 |
|
421 void |
|
422 EventTokenBucket::FineGrainResetTimerNotify() |
|
423 { |
|
424 SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n", |
|
425 this, mEvents.GetSize())); |
|
426 mFineGrainResetTimerArmed = false; |
|
427 |
|
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 } |
|
433 |
|
434 #endif |
|
435 |
|
436 } // mozilla::net |
|
437 } // mozilla |