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