Coverage Report

Created: 2018-09-25 14:53

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