netwerk/base/src/EventTokenBucket.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial