netwerk/base/src/EventTokenBucket.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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

mercurial