Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
2
/* vim: set sw=2 ts=8 et tw=80 : */
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 "nsAutoPtr.h"
8
#include "nsIDocShell.h"
9
#include "mozilla/dom/Document.h"
10
#include "nsIDocumentLoader.h"
11
#include "nsIObserverService.h"
12
#include "nsIXULRuntime.h"
13
#include "nsServiceManagerUtils.h"
14
#include "nsThreadUtils.h"
15
#include "RequestContextService.h"
16
17
#include "mozilla/Atomics.h"
18
#include "mozilla/ClearOnShutdown.h"
19
#include "mozilla/Logging.h"
20
#include "mozilla/Services.h"
21
#include "mozilla/StaticPtr.h"
22
#include "mozilla/TimeStamp.h"
23
24
#include "mozilla/net/NeckoChild.h"
25
#include "mozilla/net/NeckoCommon.h"
26
#include "mozilla/net/PSpdyPush.h"
27
28
#include "../protocol/http/nsHttpHandler.h"
29
30
namespace mozilla {
31
namespace net {
32
33
LazyLogModule gRequestContextLog("RequestContext");
34
#undef LOG
35
#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
36
37
static StaticRefPtr<RequestContextService> gSingleton;
38
39
// This is used to prevent adding tail pending requests after shutdown
40
static bool sShutdown = false;
41
42
// nsIRequestContext
43
class RequestContext final : public nsIRequestContext, public nsITimerCallback {
44
public:
45
NS_DECL_THREADSAFE_ISUPPORTS
46
NS_DECL_NSIREQUESTCONTEXT
47
NS_DECL_NSITIMERCALLBACK
48
49
explicit RequestContext(const uint64_t id);
50
51
private:
52
virtual ~RequestContext();
53
54
void ProcessTailQueue(nsresult aResult);
55
// Reschedules the timer if needed
56
void ScheduleUnblock();
57
// Hard-reschedules the timer
58
void RescheduleUntailTimer(TimeStamp const& now);
59
60
uint64_t mID;
61
Atomic<uint32_t> mBlockingTransactionCount;
62
nsAutoPtr<SpdyPushCache> mSpdyCache;
63
64
typedef nsCOMPtr<nsIRequestTailUnblockCallback> PendingTailRequest;
65
// Number of known opened non-tailed requets
66
uint32_t mNonTailRequests;
67
// Queue of requests that have been tailed, when conditions are met
68
// we call each of them to unblock and drop the reference
69
nsTArray<PendingTailRequest> mTailQueue;
70
// Loosly scheduled timer, never scheduled further to the future than
71
// mUntailAt time
72
nsCOMPtr<nsITimer> mUntailTimer;
73
// Timestamp when the timer is expected to fire,
74
// always less than or equal to mUntailAt
75
TimeStamp mTimerScheduledAt;
76
// Timestamp when we want to actually untail queued requets based on
77
// the number of request count change in the past; iff this timestamp
78
// is set, we tail requests
79
TimeStamp mUntailAt;
80
81
// Timestamp of the navigation start time, set to Now() in BeginLoad().
82
// This is used to progressively lower the maximum delay time so that
83
// we can't get to a situation when a number of repetitive requests
84
// on the page causes forever tailing.
85
TimeStamp mBeginLoadTime;
86
87
// This member is true only between DOMContentLoaded notification and
88
// next document load beginning for this request context.
89
// Top level request contexts are recycled.
90
bool mAfterDOMContentLoaded;
91
};
92
93
NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback)
94
95
RequestContext::RequestContext(const uint64_t aID)
96
: mID(aID),
97
mBlockingTransactionCount(0),
98
mNonTailRequests(0),
99
mAfterDOMContentLoaded(false) {
100
LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
101
}
102
103
RequestContext::~RequestContext() {
104
MOZ_ASSERT(mTailQueue.Length() == 0);
105
106
LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
107
static_cast<uint32_t>(mBlockingTransactionCount)));
108
}
109
110
NS_IMETHODIMP
111
RequestContext::BeginLoad() {
112
MOZ_ASSERT(NS_IsMainThread());
113
114
LOG(("RequestContext::BeginLoad %p", this));
115
116
if (IsNeckoChild()) {
117
// Tailing is not supported on the child process
118
if (gNeckoChild) {
119
gNeckoChild->SendRequestContextLoadBegin(mID);
120
}
121
return NS_OK;
122
}
123
124
mAfterDOMContentLoaded = false;
125
mBeginLoadTime = TimeStamp::NowLoRes();
126
return NS_OK;
127
}
128
129
NS_IMETHODIMP
130
RequestContext::DOMContentLoaded() {
131
MOZ_ASSERT(NS_IsMainThread());
132
133
LOG(("RequestContext::DOMContentLoaded %p", this));
134
135
if (IsNeckoChild()) {
136
// Tailing is not supported on the child process
137
if (gNeckoChild) {
138
gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
139
}
140
return NS_OK;
141
}
142
143
if (mAfterDOMContentLoaded) {
144
// There is a possibility of a duplicate notification
145
return NS_OK;
146
}
147
148
mAfterDOMContentLoaded = true;
149
150
// Conditions for the delay calculation has changed.
151
ScheduleUnblock();
152
return NS_OK;
153
}
154
155
NS_IMETHODIMP
156
RequestContext::GetBlockingTransactionCount(
157
uint32_t* aBlockingTransactionCount) {
158
NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
159
*aBlockingTransactionCount = mBlockingTransactionCount;
160
return NS_OK;
161
}
162
163
NS_IMETHODIMP
164
RequestContext::AddBlockingTransaction() {
165
mBlockingTransactionCount++;
166
LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
167
static_cast<uint32_t>(mBlockingTransactionCount)));
168
return NS_OK;
169
}
170
171
NS_IMETHODIMP
172
RequestContext::RemoveBlockingTransaction(uint32_t* outval) {
173
NS_ENSURE_ARG_POINTER(outval);
174
mBlockingTransactionCount--;
175
LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
176
static_cast<uint32_t>(mBlockingTransactionCount)));
177
*outval = mBlockingTransactionCount;
178
return NS_OK;
179
}
180
181
SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache; }
182
183
void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
184
mSpdyCache = aSpdyPushCache;
185
}
186
187
uint64_t RequestContext::GetID() { return mID; }
188
189
NS_IMETHODIMP
190
RequestContext::AddNonTailRequest() {
191
MOZ_ASSERT(NS_IsMainThread());
192
193
++mNonTailRequests;
194
LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
195
mNonTailRequests));
196
197
ScheduleUnblock();
198
return NS_OK;
199
}
200
201
NS_IMETHODIMP
202
RequestContext::RemoveNonTailRequest() {
203
MOZ_ASSERT(NS_IsMainThread());
204
MOZ_ASSERT(mNonTailRequests > 0);
205
206
LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
207
mNonTailRequests - 1));
208
209
--mNonTailRequests;
210
211
ScheduleUnblock();
212
return NS_OK;
213
}
214
215
void RequestContext::ScheduleUnblock() {
216
MOZ_ASSERT(!IsNeckoChild());
217
MOZ_ASSERT(NS_IsMainThread());
218
219
if (!gHttpHandler) {
220
return;
221
}
222
223
uint32_t quantum =
224
gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
225
uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
226
uint32_t totalMax = gHttpHandler->TailBlockingTotalMax();
227
228
if (!mBeginLoadTime.IsNull()) {
229
// We decrease the maximum delay progressively with the time since the page
230
// load begin. This seems like a reasonable and clear heuristic allowing us
231
// to start loading tailed requests in a deterministic time after the load
232
// has started.
233
234
uint32_t sinceBeginLoad = static_cast<uint32_t>(
235
(TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds());
236
uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax);
237
uint32_t proportion = totalMax // values clamped between 0 and 60'000
238
? (delayMax * tillTotal) / totalMax
239
: 0;
240
delayMax = std::min(delayMax, proportion);
241
}
242
243
CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
244
245
if (!mAfterDOMContentLoaded) {
246
// Before DOMContentLoaded notification we want to make sure that tailed
247
// requests don't start when there is a short delay during which we may
248
// not have any active requests on the page happening.
249
delay += quantum;
250
}
251
252
if (!delay.isValid() || delay.value() > delayMax) {
253
delay = delayMax;
254
}
255
256
LOG(
257
("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
258
"delay=%u after-DCL=%d",
259
this, mNonTailRequests, mTailQueue.Length(), delay.value(),
260
mAfterDOMContentLoaded));
261
262
TimeStamp now = TimeStamp::NowLoRes();
263
mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
264
265
if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
266
LOG(("RequestContext %p timer would fire too late, rescheduling", this));
267
RescheduleUntailTimer(now);
268
}
269
}
270
271
void RequestContext::RescheduleUntailTimer(TimeStamp const& now) {
272
MOZ_ASSERT(mUntailAt >= now);
273
274
if (mUntailTimer) {
275
mUntailTimer->Cancel();
276
}
277
278
if (!mTailQueue.Length()) {
279
mUntailTimer = nullptr;
280
mTimerScheduledAt = TimeStamp();
281
return;
282
}
283
284
TimeDuration interval = mUntailAt - now;
285
if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
286
// When the number of untailed requests goes down,
287
// let's half the interval, since it's likely we would
288
// reschedule for a shorter time again very soon.
289
// This will likely save rescheduling this timer.
290
interval = interval / int64_t(2);
291
mTimerScheduledAt = mUntailAt - interval;
292
} else {
293
mTimerScheduledAt = mUntailAt;
294
}
295
296
uint32_t delay = interval.ToMilliseconds();
297
mUntailTimer = do_CreateInstance("@mozilla.org/timer;1");
298
mUntailTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT);
299
300
LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
301
}
302
303
NS_IMETHODIMP
304
RequestContext::Notify(nsITimer* timer) {
305
MOZ_ASSERT(NS_IsMainThread());
306
MOZ_ASSERT(timer == mUntailTimer);
307
MOZ_ASSERT(!mTimerScheduledAt.IsNull());
308
MOZ_ASSERT(mTailQueue.Length());
309
310
mUntailTimer = nullptr;
311
312
TimeStamp now = TimeStamp::NowLoRes();
313
if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
314
LOG(("RequestContext %p timer fired too soon, rescheduling", this));
315
RescheduleUntailTimer(now);
316
return NS_OK;
317
}
318
319
// Must drop to allow re-engage of the timer
320
mTimerScheduledAt = TimeStamp();
321
322
ProcessTailQueue(NS_OK);
323
324
return NS_OK;
325
}
326
327
NS_IMETHODIMP
328
RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
329
bool* aBlocked) {
330
MOZ_ASSERT(NS_IsMainThread());
331
332
LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
333
this, aRequest, mTailQueue.Length()));
334
335
*aBlocked = false;
336
337
if (sShutdown) {
338
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
339
}
340
341
if (mUntailAt.IsNull()) {
342
LOG((" untail time passed"));
343
return NS_OK;
344
}
345
346
if (mAfterDOMContentLoaded && !mNonTailRequests) {
347
LOG((" after DOMContentLoaded and no untailed requests"));
348
return NS_OK;
349
}
350
351
if (!gHttpHandler) {
352
// Xpcshell tests may not have http handler
353
LOG((" missing gHttpHandler?"));
354
return NS_OK;
355
}
356
357
*aBlocked = true;
358
mTailQueue.AppendElement(aRequest);
359
360
LOG((" request queued"));
361
362
if (!mUntailTimer) {
363
ScheduleUnblock();
364
}
365
366
return NS_OK;
367
}
368
369
NS_IMETHODIMP
370
RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) {
371
MOZ_ASSERT(NS_IsMainThread());
372
373
bool removed = mTailQueue.RemoveElement(aRequest);
374
375
LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
376
aRequest, removed));
377
378
// Stop untail timer if all tail requests are canceled.
379
if (removed && mTailQueue.IsEmpty()) {
380
if (mUntailTimer) {
381
mUntailTimer->Cancel();
382
mUntailTimer = nullptr;
383
}
384
385
// Must drop to allow re-engage of the timer
386
mTimerScheduledAt = TimeStamp();
387
}
388
389
return NS_OK;
390
}
391
392
void RequestContext::ProcessTailQueue(nsresult aResult) {
393
LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
394
this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
395
396
if (mUntailTimer) {
397
mUntailTimer->Cancel();
398
mUntailTimer = nullptr;
399
}
400
401
// Must drop to stop tailing requests
402
mUntailAt = TimeStamp();
403
404
nsTArray<PendingTailRequest> queue;
405
queue.SwapElements(mTailQueue);
406
407
for (const auto& request : queue) {
408
LOG((" untailing %p", request.get()));
409
request->OnTailUnblock(aResult);
410
}
411
}
412
413
NS_IMETHODIMP
414
RequestContext::CancelTailPendingRequests(nsresult aResult) {
415
MOZ_ASSERT(NS_IsMainThread());
416
MOZ_ASSERT(NS_FAILED(aResult));
417
418
ProcessTailQueue(aResult);
419
return NS_OK;
420
}
421
422
// nsIRequestContextService
423
RequestContextService* RequestContextService::sSelf = nullptr;
424
425
NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
426
427
RequestContextService::RequestContextService()
428
: mRCIDNamespace(0), mNextRCID(1) {
429
MOZ_ASSERT(!sSelf, "multiple rcs instances!");
430
MOZ_ASSERT(NS_IsMainThread());
431
sSelf = this;
432
433
nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
434
runtime->GetProcessID(&mRCIDNamespace);
435
}
436
437
RequestContextService::~RequestContextService() {
438
MOZ_ASSERT(NS_IsMainThread());
439
Shutdown();
440
sSelf = nullptr;
441
}
442
443
nsresult RequestContextService::Init() {
444
nsresult rv;
445
446
MOZ_ASSERT(NS_IsMainThread());
447
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
448
if (!obs) {
449
return NS_ERROR_NOT_AVAILABLE;
450
}
451
452
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
453
if (NS_FAILED(rv)) {
454
return rv;
455
}
456
obs->AddObserver(this, "content-document-interactive", false);
457
if (NS_FAILED(rv)) {
458
return rv;
459
}
460
461
return NS_OK;
462
}
463
464
void RequestContextService::Shutdown() {
465
MOZ_ASSERT(NS_IsMainThread());
466
// We need to do this to prevent the requests from being scheduled after
467
// shutdown.
468
for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
469
iter.Data()->CancelTailPendingRequests(NS_ERROR_ABORT);
470
}
471
mTable.Clear();
472
sShutdown = true;
473
}
474
475
/* static */
476
already_AddRefed<nsIRequestContextService>
477
RequestContextService::GetOrCreate() {
478
MOZ_ASSERT(NS_IsMainThread());
479
480
RefPtr<RequestContextService> svc;
481
if (gSingleton) {
482
svc = gSingleton;
483
} else {
484
svc = new RequestContextService();
485
nsresult rv = svc->Init();
486
NS_ENSURE_SUCCESS(rv, nullptr);
487
gSingleton = svc;
488
ClearOnShutdown(&gSingleton);
489
}
490
491
return svc.forget();
492
}
493
494
NS_IMETHODIMP
495
RequestContextService::GetRequestContext(const uint64_t rcID,
496
nsIRequestContext** rc) {
497
MOZ_ASSERT(NS_IsMainThread());
498
NS_ENSURE_ARG_POINTER(rc);
499
*rc = nullptr;
500
501
if (sShutdown) {
502
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
503
}
504
505
if (!mTable.Get(rcID, rc)) {
506
nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
507
mTable.Put(rcID, newSC);
508
newSC.swap(*rc);
509
}
510
511
return NS_OK;
512
}
513
514
NS_IMETHODIMP
515
RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
516
nsIRequestContext** rc) {
517
nsresult rv;
518
519
uint64_t rcID;
520
rv = aLoadGroup->GetRequestContextID(&rcID);
521
if (NS_FAILED(rv)) {
522
return rv;
523
}
524
525
return GetRequestContext(rcID, rc);
526
}
527
528
NS_IMETHODIMP
529
RequestContextService::NewRequestContext(nsIRequestContext** rc) {
530
MOZ_ASSERT(NS_IsMainThread());
531
NS_ENSURE_ARG_POINTER(rc);
532
*rc = nullptr;
533
534
if (sShutdown) {
535
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
536
}
537
538
uint64_t rcID =
539
((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
540
mNextRCID++;
541
542
nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
543
mTable.Put(rcID, newSC);
544
newSC.swap(*rc);
545
546
return NS_OK;
547
}
548
549
NS_IMETHODIMP
550
RequestContextService::RemoveRequestContext(const uint64_t rcID) {
551
if (IsNeckoChild() && gNeckoChild) {
552
gNeckoChild->SendRemoveRequestContext(rcID);
553
}
554
555
MOZ_ASSERT(NS_IsMainThread());
556
mTable.Remove(rcID);
557
return NS_OK;
558
}
559
560
NS_IMETHODIMP
561
RequestContextService::Observe(nsISupports* subject, const char* topic,
562
const char16_t* data_unicode) {
563
MOZ_ASSERT(NS_IsMainThread());
564
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
565
Shutdown();
566
return NS_OK;
567
}
568
569
if (!strcmp("content-document-interactive", topic)) {
570
nsCOMPtr<dom::Document> document(do_QueryInterface(subject));
571
MOZ_ASSERT(document);
572
// We want this be triggered also for iframes, since those track their
573
// own request context ids.
574
if (!document) {
575
return NS_OK;
576
}
577
nsIDocShell* ds = document->GetDocShell();
578
// XML documents don't always have a docshell assigned
579
if (!ds) {
580
return NS_OK;
581
}
582
nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
583
if (!dl) {
584
return NS_OK;
585
}
586
nsCOMPtr<nsILoadGroup> lg;
587
dl->GetLoadGroup(getter_AddRefs(lg));
588
if (!lg) {
589
return NS_OK;
590
}
591
nsCOMPtr<nsIRequestContext> rc;
592
GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
593
if (rc) {
594
rc->DOMContentLoaded();
595
}
596
597
return NS_OK;
598
}
599
600
MOZ_ASSERT(false, "Unexpected observer topic");
601
return NS_OK;
602
}
603
604
} // namespace net
605
} // namespace mozilla
606
607
#undef LOG