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
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 | #ifndef NetEventTokenBucket_h__ |
michael@0 | 8 | #define NetEventTokenBucket_h__ |
michael@0 | 9 | |
michael@0 | 10 | #include "nsCOMPtr.h" |
michael@0 | 11 | #include "nsDeque.h" |
michael@0 | 12 | #include "nsITimer.h" |
michael@0 | 13 | |
michael@0 | 14 | #include "mozilla/TimeStamp.h" |
michael@0 | 15 | |
michael@0 | 16 | class nsICancelable; |
michael@0 | 17 | |
michael@0 | 18 | namespace mozilla { |
michael@0 | 19 | namespace net { |
michael@0 | 20 | |
michael@0 | 21 | /* A token bucket is used to govern the maximum rate a series of events |
michael@0 | 22 | can be executed at. For instance if your event was "eat a piece of cake" |
michael@0 | 23 | then a token bucket configured to allow "1 piece per day" would spread |
michael@0 | 24 | the eating of a 8 piece cake over 8 days even if you tried to eat the |
michael@0 | 25 | whole thing up front. In a practical sense it 'costs' 1 token to execute |
michael@0 | 26 | an event and tokens are 'earned' at a particular rate as time goes by. |
michael@0 | 27 | |
michael@0 | 28 | The token bucket can be perfectly smooth or allow a configurable amount of |
michael@0 | 29 | burstiness. A bursty token bucket allows you to save up unused credits, while |
michael@0 | 30 | a perfectly smooth one would not. A smooth "1 per day" cake token bucket |
michael@0 | 31 | would require 9 days to eat that cake if you skipped a slice on day 4 |
michael@0 | 32 | (use the token or lose it), while a token bucket configured with a burst |
michael@0 | 33 | of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day |
michael@0 | 34 | 5) and finish the cake in the usual 8 days. |
michael@0 | 35 | |
michael@0 | 36 | EventTokenBucket(hz=20, burst=5) creates a token bucket with the following properties: |
michael@0 | 37 | |
michael@0 | 38 | + events from an infinite stream will be admitted 20 times per second (i.e. |
michael@0 | 39 | hz=20 means 1 event per 50 ms). Timers will be used to space things evenly down to |
michael@0 | 40 | 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than 200hz will admit |
michael@0 | 41 | multiple events with 5ms gaps between them. 10000hz is the maximum rate and 1hz is |
michael@0 | 42 | the minimum rate. |
michael@0 | 43 | |
michael@0 | 44 | + The burst size controls the limit of 'credits' that a token bucket can accumulate |
michael@0 | 45 | when idle. For our (20,5) example each event requires 50ms of credit (again, 20hz = 50ms |
michael@0 | 46 | per event). a burst size of 5 means that the token bucket can accumulate a |
michael@0 | 47 | maximum of 250ms (5 * 50ms) for this bucket. If no events have been admitted for the |
michael@0 | 48 | last full second the bucket can still only accumulate 250ms of credit - but that credit |
michael@0 | 49 | means that 5 events can be admitted without delay. A burst size of 1 is the minimum. |
michael@0 | 50 | The EventTokenBucket is created with maximum credits already applied, but they |
michael@0 | 51 | can be cleared with the ClearCredits() method. The maximum burst size is |
michael@0 | 52 | 15 minutes worth of events. |
michael@0 | 53 | |
michael@0 | 54 | + An event is submitted to the token bucket asynchronously through SubmitEvent(). |
michael@0 | 55 | The OnTokenBucketAdmitted() method of the submitted event is used as a callback |
michael@0 | 56 | when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller |
michael@0 | 57 | for use in the case they do not wish to wait for the callback. |
michael@0 | 58 | */ |
michael@0 | 59 | |
michael@0 | 60 | class EventTokenBucket; |
michael@0 | 61 | |
michael@0 | 62 | class ATokenBucketEvent |
michael@0 | 63 | { |
michael@0 | 64 | public: |
michael@0 | 65 | virtual void OnTokenBucketAdmitted() = 0; |
michael@0 | 66 | }; |
michael@0 | 67 | |
michael@0 | 68 | class TokenBucketCancelable; |
michael@0 | 69 | |
michael@0 | 70 | class EventTokenBucket : public nsITimerCallback |
michael@0 | 71 | { |
michael@0 | 72 | public: |
michael@0 | 73 | NS_DECL_THREADSAFE_ISUPPORTS |
michael@0 | 74 | NS_DECL_NSITIMERCALLBACK |
michael@0 | 75 | |
michael@0 | 76 | // This should be constructed on the main thread |
michael@0 | 77 | EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize); |
michael@0 | 78 | virtual ~EventTokenBucket(); |
michael@0 | 79 | |
michael@0 | 80 | // These public methods are all meant to be called from the socket thread |
michael@0 | 81 | void ClearCredits(); |
michael@0 | 82 | uint32_t BurstEventsAvailable(); |
michael@0 | 83 | uint32_t QueuedEvents(); |
michael@0 | 84 | |
michael@0 | 85 | // a paused token bucket will not process any events, but it will accumulate |
michael@0 | 86 | // credits. ClearCredits can be used before unpausing if desired. |
michael@0 | 87 | void Pause(); |
michael@0 | 88 | void UnPause(); |
michael@0 | 89 | void Stop() { mStopped = true; } |
michael@0 | 90 | |
michael@0 | 91 | // The returned cancelable event can only be canceled from the socket thread |
michael@0 | 92 | nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable); |
michael@0 | 93 | |
michael@0 | 94 | private: |
michael@0 | 95 | friend class RunNotifyEvent; |
michael@0 | 96 | friend class SetTimerEvent; |
michael@0 | 97 | |
michael@0 | 98 | bool TryImmediateDispatch(TokenBucketCancelable *event); |
michael@0 | 99 | void SetRate(uint32_t eventsPerSecond, uint32_t burstSize); |
michael@0 | 100 | |
michael@0 | 101 | void DispatchEvents(); |
michael@0 | 102 | void UpdateTimer(); |
michael@0 | 103 | void UpdateCredits(); |
michael@0 | 104 | |
michael@0 | 105 | const static uint64_t kUsecPerSec = 1000000; |
michael@0 | 106 | const static uint64_t kUsecPerMsec = 1000; |
michael@0 | 107 | const static uint64_t kMaxHz = 10000; |
michael@0 | 108 | |
michael@0 | 109 | uint64_t mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond) |
michael@0 | 110 | uint64_t mMaxCredit; // usec mCredit limit (from busrtSize) |
michael@0 | 111 | uint64_t mCredit; // usec of accumulated credit. |
michael@0 | 112 | |
michael@0 | 113 | bool mPaused; |
michael@0 | 114 | bool mStopped; |
michael@0 | 115 | nsDeque mEvents; |
michael@0 | 116 | bool mTimerArmed; |
michael@0 | 117 | TimeStamp mLastUpdate; |
michael@0 | 118 | |
michael@0 | 119 | // The timer is created on the main thread, but is armed and executes Notify() |
michael@0 | 120 | // callbacks on the socket thread in order to maintain low latency of event |
michael@0 | 121 | // delivery. |
michael@0 | 122 | nsCOMPtr<nsITimer> mTimer; |
michael@0 | 123 | |
michael@0 | 124 | #ifdef XP_WIN |
michael@0 | 125 | // Windows timers are 15ms granularity by default. When we have active events |
michael@0 | 126 | // that need to be dispatched at 50ms or less granularity we change the OS |
michael@0 | 127 | // granularity to 1ms. 90 seconds after that need has elapsed we will change it |
michael@0 | 128 | // back |
michael@0 | 129 | const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec; |
michael@0 | 130 | |
michael@0 | 131 | void FineGrainTimers(); // get 1ms granularity |
michael@0 | 132 | void NormalTimers(); // reset to default granularity |
michael@0 | 133 | void WantNormalTimers(); // reset after 90 seconds if not needed in interim |
michael@0 | 134 | void FineGrainResetTimerNotify(); // delayed callback to reset |
michael@0 | 135 | |
michael@0 | 136 | TimeStamp mLastFineGrainTimerUse; |
michael@0 | 137 | bool mFineGrainTimerInUse; |
michael@0 | 138 | bool mFineGrainResetTimerArmed; |
michael@0 | 139 | nsCOMPtr<nsITimer> mFineGrainResetTimer; |
michael@0 | 140 | #endif |
michael@0 | 141 | }; |
michael@0 | 142 | |
michael@0 | 143 | } // ::mozilla::net |
michael@0 | 144 | } // ::mozilla |
michael@0 | 145 | |
michael@0 | 146 | #endif |