michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef NetEventTokenBucket_h__ michael@0: #define NetEventTokenBucket_h__ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDeque.h" michael@0: #include "nsITimer.h" michael@0: michael@0: #include "mozilla/TimeStamp.h" michael@0: michael@0: class nsICancelable; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: /* A token bucket is used to govern the maximum rate a series of events michael@0: can be executed at. For instance if your event was "eat a piece of cake" michael@0: then a token bucket configured to allow "1 piece per day" would spread michael@0: the eating of a 8 piece cake over 8 days even if you tried to eat the michael@0: whole thing up front. In a practical sense it 'costs' 1 token to execute michael@0: an event and tokens are 'earned' at a particular rate as time goes by. michael@0: michael@0: The token bucket can be perfectly smooth or allow a configurable amount of michael@0: burstiness. A bursty token bucket allows you to save up unused credits, while michael@0: a perfectly smooth one would not. A smooth "1 per day" cake token bucket michael@0: would require 9 days to eat that cake if you skipped a slice on day 4 michael@0: (use the token or lose it), while a token bucket configured with a burst michael@0: of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day michael@0: 5) and finish the cake in the usual 8 days. michael@0: michael@0: EventTokenBucket(hz=20, burst=5) creates a token bucket with the following properties: michael@0: michael@0: + events from an infinite stream will be admitted 20 times per second (i.e. michael@0: hz=20 means 1 event per 50 ms). Timers will be used to space things evenly down to michael@0: 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than 200hz will admit michael@0: multiple events with 5ms gaps between them. 10000hz is the maximum rate and 1hz is michael@0: the minimum rate. michael@0: michael@0: + The burst size controls the limit of 'credits' that a token bucket can accumulate michael@0: when idle. For our (20,5) example each event requires 50ms of credit (again, 20hz = 50ms michael@0: per event). a burst size of 5 means that the token bucket can accumulate a michael@0: maximum of 250ms (5 * 50ms) for this bucket. If no events have been admitted for the michael@0: last full second the bucket can still only accumulate 250ms of credit - but that credit michael@0: means that 5 events can be admitted without delay. A burst size of 1 is the minimum. michael@0: The EventTokenBucket is created with maximum credits already applied, but they michael@0: can be cleared with the ClearCredits() method. The maximum burst size is michael@0: 15 minutes worth of events. michael@0: michael@0: + An event is submitted to the token bucket asynchronously through SubmitEvent(). michael@0: The OnTokenBucketAdmitted() method of the submitted event is used as a callback michael@0: when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller michael@0: for use in the case they do not wish to wait for the callback. michael@0: */ michael@0: michael@0: class EventTokenBucket; michael@0: michael@0: class ATokenBucketEvent michael@0: { michael@0: public: michael@0: virtual void OnTokenBucketAdmitted() = 0; michael@0: }; michael@0: michael@0: class TokenBucketCancelable; michael@0: michael@0: class EventTokenBucket : public nsITimerCallback michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: // This should be constructed on the main thread michael@0: EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize); michael@0: virtual ~EventTokenBucket(); michael@0: michael@0: // These public methods are all meant to be called from the socket thread michael@0: void ClearCredits(); michael@0: uint32_t BurstEventsAvailable(); michael@0: uint32_t QueuedEvents(); michael@0: michael@0: // a paused token bucket will not process any events, but it will accumulate michael@0: // credits. ClearCredits can be used before unpausing if desired. michael@0: void Pause(); michael@0: void UnPause(); michael@0: void Stop() { mStopped = true; } michael@0: michael@0: // The returned cancelable event can only be canceled from the socket thread michael@0: nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable); michael@0: michael@0: private: michael@0: friend class RunNotifyEvent; michael@0: friend class SetTimerEvent; michael@0: michael@0: bool TryImmediateDispatch(TokenBucketCancelable *event); michael@0: void SetRate(uint32_t eventsPerSecond, uint32_t burstSize); michael@0: michael@0: void DispatchEvents(); michael@0: void UpdateTimer(); michael@0: void UpdateCredits(); michael@0: michael@0: const static uint64_t kUsecPerSec = 1000000; michael@0: const static uint64_t kUsecPerMsec = 1000; michael@0: const static uint64_t kMaxHz = 10000; michael@0: michael@0: uint64_t mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond) michael@0: uint64_t mMaxCredit; // usec mCredit limit (from busrtSize) michael@0: uint64_t mCredit; // usec of accumulated credit. michael@0: michael@0: bool mPaused; michael@0: bool mStopped; michael@0: nsDeque mEvents; michael@0: bool mTimerArmed; michael@0: TimeStamp mLastUpdate; michael@0: michael@0: // The timer is created on the main thread, but is armed and executes Notify() michael@0: // callbacks on the socket thread in order to maintain low latency of event michael@0: // delivery. michael@0: nsCOMPtr mTimer; michael@0: michael@0: #ifdef XP_WIN michael@0: // Windows timers are 15ms granularity by default. When we have active events michael@0: // that need to be dispatched at 50ms or less granularity we change the OS michael@0: // granularity to 1ms. 90 seconds after that need has elapsed we will change it michael@0: // back michael@0: const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec; michael@0: michael@0: void FineGrainTimers(); // get 1ms granularity michael@0: void NormalTimers(); // reset to default granularity michael@0: void WantNormalTimers(); // reset after 90 seconds if not needed in interim michael@0: void FineGrainResetTimerNotify(); // delayed callback to reset michael@0: michael@0: TimeStamp mLastFineGrainTimerUse; michael@0: bool mFineGrainTimerInUse; michael@0: bool mFineGrainResetTimerArmed; michael@0: nsCOMPtr mFineGrainResetTimer; michael@0: #endif michael@0: }; michael@0: michael@0: } // ::mozilla::net michael@0: } // ::mozilla michael@0: michael@0: #endif