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/. */
7 #include "nsIDocShell.h"
8 #include "mozilla/dom/Document.h"
9 #include "nsComponentManagerUtils.h"
10 #include "nsIDocumentLoader.h"
11 #include "nsIObserverService.h"
13 #include "nsIXULRuntime.h"
14 #include "nsServiceManagerUtils.h"
15 #include "nsThreadUtils.h"
16 #include "RequestContextService.h"
18 #include "mozilla/Atomics.h"
19 #include "mozilla/ClearOnShutdown.h"
20 #include "mozilla/Logging.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/StaticPtr.h"
23 #include "mozilla/TimeStamp.h"
25 #include "mozilla/net/NeckoChild.h"
26 #include "mozilla/net/NeckoCommon.h"
27 #include "mozilla/net/PSpdyPush.h"
29 #include "../protocol/http/nsHttpHandler.h"
34 LazyLogModule
gRequestContextLog("RequestContext");
36 #define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
38 static StaticRefPtr
<RequestContextService
> gSingleton
;
40 // This is used to prevent adding tail pending requests after shutdown
41 static bool sShutdown
= false;
44 class RequestContext final
: public nsIRequestContext
,
45 public nsITimerCallback
,
48 NS_DECL_THREADSAFE_ISUPPORTS
49 NS_DECL_NSIREQUESTCONTEXT
50 NS_DECL_NSITIMERCALLBACK
53 explicit RequestContext(const uint64_t id
);
56 virtual ~RequestContext();
58 void ProcessTailQueue(nsresult aResult
);
59 // Reschedules the timer if needed
60 void ScheduleUnblock();
61 // Hard-reschedules the timer
62 void RescheduleUntailTimer(TimeStamp
const& now
);
65 Atomic
<uint32_t> mBlockingTransactionCount
;
66 UniquePtr
<SpdyPushCache
> mSpdyCache
;
68 using PendingTailRequest
= nsCOMPtr
<nsIRequestTailUnblockCallback
>;
69 // Number of known opened non-tailed requets
70 uint32_t mNonTailRequests
;
71 // Queue of requests that have been tailed, when conditions are met
72 // we call each of them to unblock and drop the reference
73 nsTArray
<PendingTailRequest
> mTailQueue
;
74 // Loosly scheduled timer, never scheduled further to the future than
76 nsCOMPtr
<nsITimer
> mUntailTimer
;
77 // Timestamp when the timer is expected to fire,
78 // always less than or equal to mUntailAt
79 TimeStamp mTimerScheduledAt
;
80 // Timestamp when we want to actually untail queued requets based on
81 // the number of request count change in the past; iff this timestamp
82 // is set, we tail requests
85 // Timestamp of the navigation start time, set to Now() in BeginLoad().
86 // This is used to progressively lower the maximum delay time so that
87 // we can't get to a situation when a number of repetitive requests
88 // on the page causes forever tailing.
89 TimeStamp mBeginLoadTime
;
91 // This member is true only between DOMContentLoaded notification and
92 // next document load beginning for this request context.
93 // Top level request contexts are recycled.
94 bool mAfterDOMContentLoaded
;
97 NS_IMPL_ISUPPORTS(RequestContext
, nsIRequestContext
, nsITimerCallback
, nsINamed
)
99 RequestContext::RequestContext(const uint64_t aID
)
101 mBlockingTransactionCount(0),
103 mAfterDOMContentLoaded(false) {
104 LOG(("RequestContext::RequestContext this=%p id=%" PRIx64
, this, mID
));
107 RequestContext::~RequestContext() {
108 MOZ_ASSERT(mTailQueue
.Length() == 0);
110 LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
111 static_cast<uint32_t>(mBlockingTransactionCount
)));
115 RequestContext::BeginLoad() {
116 MOZ_ASSERT(NS_IsMainThread());
118 LOG(("RequestContext::BeginLoad %p", this));
120 if (IsNeckoChild()) {
121 // Tailing is not supported on the child process
123 gNeckoChild
->SendRequestContextLoadBegin(mID
);
128 mAfterDOMContentLoaded
= false;
129 mBeginLoadTime
= TimeStamp::NowLoRes();
134 RequestContext::DOMContentLoaded() {
135 MOZ_ASSERT(NS_IsMainThread());
137 LOG(("RequestContext::DOMContentLoaded %p", this));
139 if (IsNeckoChild()) {
140 // Tailing is not supported on the child process
142 gNeckoChild
->SendRequestContextAfterDOMContentLoaded(mID
);
147 if (mAfterDOMContentLoaded
) {
148 // There is a possibility of a duplicate notification
152 mAfterDOMContentLoaded
= true;
154 // Conditions for the delay calculation has changed.
160 RequestContext::GetBlockingTransactionCount(
161 uint32_t* aBlockingTransactionCount
) {
162 NS_ENSURE_ARG_POINTER(aBlockingTransactionCount
);
163 *aBlockingTransactionCount
= mBlockingTransactionCount
;
168 RequestContext::AddBlockingTransaction() {
169 mBlockingTransactionCount
++;
170 LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
171 static_cast<uint32_t>(mBlockingTransactionCount
)));
176 RequestContext::RemoveBlockingTransaction(uint32_t* outval
) {
177 NS_ENSURE_ARG_POINTER(outval
);
178 mBlockingTransactionCount
--;
179 LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
180 static_cast<uint32_t>(mBlockingTransactionCount
)));
181 *outval
= mBlockingTransactionCount
;
185 SpdyPushCache
* RequestContext::GetSpdyPushCache() { return mSpdyCache
.get(); }
187 void RequestContext::SetSpdyPushCache(SpdyPushCache
* aSpdyPushCache
) {
188 mSpdyCache
= WrapUnique(aSpdyPushCache
);
191 uint64_t RequestContext::GetID() { return mID
; }
194 RequestContext::AddNonTailRequest() {
195 MOZ_ASSERT(NS_IsMainThread());
198 LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
206 RequestContext::RemoveNonTailRequest() {
207 MOZ_ASSERT(NS_IsMainThread());
208 MOZ_ASSERT(mNonTailRequests
> 0);
210 LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
211 mNonTailRequests
- 1));
219 void RequestContext::ScheduleUnblock() {
220 MOZ_ASSERT(!IsNeckoChild());
221 MOZ_ASSERT(NS_IsMainThread());
228 gHttpHandler
->TailBlockingDelayQuantum(mAfterDOMContentLoaded
);
229 uint32_t delayMax
= gHttpHandler
->TailBlockingDelayMax();
230 uint32_t totalMax
= gHttpHandler
->TailBlockingTotalMax();
232 if (!mBeginLoadTime
.IsNull()) {
233 // We decrease the maximum delay progressively with the time since the page
234 // load begin. This seems like a reasonable and clear heuristic allowing us
235 // to start loading tailed requests in a deterministic time after the load
238 uint32_t sinceBeginLoad
= static_cast<uint32_t>(
239 (TimeStamp::NowLoRes() - mBeginLoadTime
).ToMilliseconds());
240 uint32_t tillTotal
= totalMax
- std::min(sinceBeginLoad
, totalMax
);
241 uint32_t proportion
= totalMax
// values clamped between 0 and 60'000
242 ? (delayMax
* tillTotal
) / totalMax
244 delayMax
= std::min(delayMax
, proportion
);
247 CheckedInt
<uint32_t> delay
= quantum
* mNonTailRequests
;
249 if (!mAfterDOMContentLoaded
) {
250 // Before DOMContentLoaded notification we want to make sure that tailed
251 // requests don't start when there is a short delay during which we may
252 // not have any active requests on the page happening.
256 if (!delay
.isValid() || delay
.value() > delayMax
) {
261 ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
262 "delay=%u after-DCL=%d",
263 this, mNonTailRequests
, mTailQueue
.Length(), delay
.value(),
264 mAfterDOMContentLoaded
));
266 TimeStamp now
= TimeStamp::NowLoRes();
267 mUntailAt
= now
+ TimeDuration::FromMilliseconds(delay
.value());
269 if (mTimerScheduledAt
.IsNull() || mUntailAt
< mTimerScheduledAt
) {
270 LOG(("RequestContext %p timer would fire too late, rescheduling", this));
271 RescheduleUntailTimer(now
);
275 void RequestContext::RescheduleUntailTimer(TimeStamp
const& now
) {
276 MOZ_ASSERT(mUntailAt
>= now
);
279 mUntailTimer
->Cancel();
282 if (!mTailQueue
.Length()) {
283 mUntailTimer
= nullptr;
284 mTimerScheduledAt
= TimeStamp();
288 TimeDuration interval
= mUntailAt
- now
;
289 if (!mTimerScheduledAt
.IsNull() && mUntailAt
< mTimerScheduledAt
) {
290 // When the number of untailed requests goes down,
291 // let's half the interval, since it's likely we would
292 // reschedule for a shorter time again very soon.
293 // This will likely save rescheduling this timer.
294 interval
= interval
/ int64_t(2);
295 mTimerScheduledAt
= mUntailAt
- interval
;
297 mTimerScheduledAt
= mUntailAt
;
300 uint32_t delay
= interval
.ToMilliseconds();
302 NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer
), this, delay
,
303 nsITimer::TYPE_ONE_SHOT
, nullptr);
305 NS_WARNING("Could not reschedule untail timer");
308 LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay
));
312 RequestContext::Notify(nsITimer
* timer
) {
313 MOZ_ASSERT(NS_IsMainThread());
314 MOZ_ASSERT(timer
== mUntailTimer
);
315 MOZ_ASSERT(!mTimerScheduledAt
.IsNull());
316 MOZ_ASSERT(mTailQueue
.Length());
318 mUntailTimer
= nullptr;
320 TimeStamp now
= TimeStamp::NowLoRes();
321 if (mUntailAt
> mTimerScheduledAt
&& mUntailAt
> now
) {
322 LOG(("RequestContext %p timer fired too soon, rescheduling", this));
323 RescheduleUntailTimer(now
);
327 // Must drop to allow re-engage of the timer
328 mTimerScheduledAt
= TimeStamp();
330 ProcessTailQueue(NS_OK
);
336 RequestContext::GetName(nsACString
& aName
) {
337 aName
.AssignLiteral("RequestContext");
342 RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback
* aRequest
,
344 MOZ_ASSERT(NS_IsMainThread());
346 LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
347 this, aRequest
, mTailQueue
.Length()));
352 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
355 if (mUntailAt
.IsNull()) {
356 LOG((" untail time passed"));
360 if (mAfterDOMContentLoaded
&& !mNonTailRequests
) {
361 LOG((" after DOMContentLoaded and no untailed requests"));
366 // Xpcshell tests may not have http handler
367 LOG((" missing gHttpHandler?"));
372 mTailQueue
.AppendElement(aRequest
);
374 LOG((" request queued"));
384 RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback
* aRequest
) {
385 MOZ_ASSERT(NS_IsMainThread());
387 bool removed
= mTailQueue
.RemoveElement(aRequest
);
389 LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
392 // Stop untail timer if all tail requests are canceled.
393 if (removed
&& mTailQueue
.IsEmpty()) {
395 mUntailTimer
->Cancel();
396 mUntailTimer
= nullptr;
399 // Must drop to allow re-engage of the timer
400 mTimerScheduledAt
= TimeStamp();
406 void RequestContext::ProcessTailQueue(nsresult aResult
) {
407 LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32
,
408 this, mTailQueue
.Length(), static_cast<uint32_t>(aResult
)));
411 mUntailTimer
->Cancel();
412 mUntailTimer
= nullptr;
415 // Must drop to stop tailing requests
416 mUntailAt
= TimeStamp();
418 nsTArray
<PendingTailRequest
> queue
= std::move(mTailQueue
);
420 for (const auto& request
: queue
) {
421 LOG((" untailing %p", request
.get()));
422 request
->OnTailUnblock(aResult
);
427 RequestContext::CancelTailPendingRequests(nsresult aResult
) {
428 MOZ_ASSERT(NS_IsMainThread());
429 MOZ_ASSERT(NS_FAILED(aResult
));
431 ProcessTailQueue(aResult
);
435 // nsIRequestContextService
436 RequestContextService
* RequestContextService::sSelf
= nullptr;
438 NS_IMPL_ISUPPORTS(RequestContextService
, nsIRequestContextService
, nsIObserver
)
440 RequestContextService::RequestContextService() {
441 MOZ_ASSERT(!sSelf
, "multiple rcs instances!");
442 MOZ_ASSERT(NS_IsMainThread());
445 nsCOMPtr
<nsIXULRuntime
> runtime
= do_GetService("@mozilla.org/xre/runtime;1");
446 runtime
->GetProcessID(&mRCIDNamespace
);
449 RequestContextService::~RequestContextService() {
450 MOZ_ASSERT(NS_IsMainThread());
455 nsresult
RequestContextService::Init() {
458 MOZ_ASSERT(NS_IsMainThread());
459 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
461 return NS_ERROR_NOT_AVAILABLE
;
464 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
468 obs
->AddObserver(this, "content-document-interactive", false);
476 void RequestContextService::Shutdown() {
477 MOZ_ASSERT(NS_IsMainThread());
478 // We need to do this to prevent the requests from being scheduled after
480 for (const auto& data
: mTable
.Values()) {
481 data
->CancelTailPendingRequests(NS_ERROR_ABORT
);
488 already_AddRefed
<nsIRequestContextService
>
489 RequestContextService::GetOrCreate() {
490 MOZ_ASSERT(NS_IsMainThread());
496 RefPtr
<RequestContextService
> svc
;
500 svc
= new RequestContextService();
501 nsresult rv
= svc
->Init();
502 NS_ENSURE_SUCCESS(rv
, nullptr);
504 ClearOnShutdown(&gSingleton
);
511 RequestContextService::GetRequestContext(const uint64_t rcID
,
512 nsIRequestContext
** rc
) {
513 MOZ_ASSERT(NS_IsMainThread());
514 NS_ENSURE_ARG_POINTER(rc
);
518 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
522 return NS_ERROR_INVALID_ARG
;
525 *rc
= do_AddRef(mTable
.LookupOrInsertWith(rcID
, [&] {
526 nsCOMPtr
<nsIRequestContext
> newSC
= new RequestContext(rcID
);
534 RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup
* aLoadGroup
,
535 nsIRequestContext
** rc
) {
539 rv
= aLoadGroup
->GetRequestContextID(&rcID
);
544 return GetRequestContext(rcID
, rc
);
548 RequestContextService::NewRequestContext(nsIRequestContext
** rc
) {
549 MOZ_ASSERT(NS_IsMainThread());
550 NS_ENSURE_ARG_POINTER(rc
);
554 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
558 ((static_cast<uint64_t>(mRCIDNamespace
) << 32) & 0xFFFFFFFF00000000LL
) |
561 nsCOMPtr
<nsIRequestContext
> newSC
= new RequestContext(rcID
);
562 mTable
.InsertOrUpdate(rcID
, newSC
);
569 RequestContextService::RemoveRequestContext(const uint64_t rcID
) {
570 MOZ_ASSERT(NS_IsMainThread());
576 RequestContextService::Observe(nsISupports
* subject
, const char* topic
,
577 const char16_t
* data_unicode
) {
578 MOZ_ASSERT(NS_IsMainThread());
579 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID
, topic
)) {
584 if (!strcmp("content-document-interactive", topic
)) {
585 nsCOMPtr
<dom::Document
> document(do_QueryInterface(subject
));
586 MOZ_ASSERT(document
);
587 // We want this be triggered also for iframes, since those track their
588 // own request context ids.
592 nsIDocShell
* ds
= document
->GetDocShell();
593 // XML documents don't always have a docshell assigned
597 nsCOMPtr
<nsIDocumentLoader
> dl(do_QueryInterface(ds
));
601 nsCOMPtr
<nsILoadGroup
> lg
;
602 dl
->GetLoadGroup(getter_AddRefs(lg
));
606 nsCOMPtr
<nsIRequestContext
> rc
;
607 GetRequestContextFromLoadGroup(lg
, getter_AddRefs(rc
));
609 rc
->DOMContentLoaded();
615 MOZ_ASSERT(false, "Unexpected observer topic");
620 } // namespace mozilla