Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / netwerk / base / RequestContextService.cpp
blob72331bdc1e04c83500f53b95ab7f10a913c82c5d
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"
12 #include "nsITimer.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"
31 namespace mozilla {
32 namespace net {
34 LazyLogModule gRequestContextLog("RequestContext");
35 #undef LOG
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;
43 // nsIRequestContext
44 class RequestContext final : public nsIRequestContext,
45 public nsITimerCallback,
46 public nsINamed {
47 public:
48 NS_DECL_THREADSAFE_ISUPPORTS
49 NS_DECL_NSIREQUESTCONTEXT
50 NS_DECL_NSITIMERCALLBACK
51 NS_DECL_NSINAMED
53 explicit RequestContext(const uint64_t id);
55 private:
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);
64 uint64_t mID;
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
75 // mUntailAt time
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
83 TimeStamp mUntailAt;
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)
100 : mID(aID),
101 mBlockingTransactionCount(0),
102 mNonTailRequests(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)));
114 NS_IMETHODIMP
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
122 if (gNeckoChild) {
123 gNeckoChild->SendRequestContextLoadBegin(mID);
125 return NS_OK;
128 mAfterDOMContentLoaded = false;
129 mBeginLoadTime = TimeStamp::NowLoRes();
130 return NS_OK;
133 NS_IMETHODIMP
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
141 if (gNeckoChild) {
142 gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
144 return NS_OK;
147 if (mAfterDOMContentLoaded) {
148 // There is a possibility of a duplicate notification
149 return NS_OK;
152 mAfterDOMContentLoaded = true;
154 // Conditions for the delay calculation has changed.
155 ScheduleUnblock();
156 return NS_OK;
159 NS_IMETHODIMP
160 RequestContext::GetBlockingTransactionCount(
161 uint32_t* aBlockingTransactionCount) {
162 NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
163 *aBlockingTransactionCount = mBlockingTransactionCount;
164 return NS_OK;
167 NS_IMETHODIMP
168 RequestContext::AddBlockingTransaction() {
169 mBlockingTransactionCount++;
170 LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
171 static_cast<uint32_t>(mBlockingTransactionCount)));
172 return NS_OK;
175 NS_IMETHODIMP
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;
182 return NS_OK;
185 SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); }
187 void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
188 mSpdyCache = WrapUnique(aSpdyPushCache);
191 uint64_t RequestContext::GetID() { return mID; }
193 NS_IMETHODIMP
194 RequestContext::AddNonTailRequest() {
195 MOZ_ASSERT(NS_IsMainThread());
197 ++mNonTailRequests;
198 LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
199 mNonTailRequests));
201 ScheduleUnblock();
202 return NS_OK;
205 NS_IMETHODIMP
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));
213 --mNonTailRequests;
215 ScheduleUnblock();
216 return NS_OK;
219 void RequestContext::ScheduleUnblock() {
220 MOZ_ASSERT(!IsNeckoChild());
221 MOZ_ASSERT(NS_IsMainThread());
223 if (!gHttpHandler) {
224 return;
227 uint32_t quantum =
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
236 // has started.
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
243 : 0;
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.
253 delay += quantum;
256 if (!delay.isValid() || delay.value() > delayMax) {
257 delay = delayMax;
260 LOG(
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);
278 if (mUntailTimer) {
279 mUntailTimer->Cancel();
282 if (!mTailQueue.Length()) {
283 mUntailTimer = nullptr;
284 mTimerScheduledAt = TimeStamp();
285 return;
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;
296 } else {
297 mTimerScheduledAt = mUntailAt;
300 uint32_t delay = interval.ToMilliseconds();
301 nsresult rv =
302 NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay,
303 nsITimer::TYPE_ONE_SHOT, nullptr);
304 if (NS_FAILED(rv)) {
305 NS_WARNING("Could not reschedule untail timer");
308 LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
311 NS_IMETHODIMP
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);
324 return NS_OK;
327 // Must drop to allow re-engage of the timer
328 mTimerScheduledAt = TimeStamp();
330 ProcessTailQueue(NS_OK);
332 return NS_OK;
335 NS_IMETHODIMP
336 RequestContext::GetName(nsACString& aName) {
337 aName.AssignLiteral("RequestContext");
338 return NS_OK;
341 NS_IMETHODIMP
342 RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
343 bool* aBlocked) {
344 MOZ_ASSERT(NS_IsMainThread());
346 LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
347 this, aRequest, mTailQueue.Length()));
349 *aBlocked = false;
351 if (sShutdown) {
352 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
355 if (mUntailAt.IsNull()) {
356 LOG((" untail time passed"));
357 return NS_OK;
360 if (mAfterDOMContentLoaded && !mNonTailRequests) {
361 LOG((" after DOMContentLoaded and no untailed requests"));
362 return NS_OK;
365 if (!gHttpHandler) {
366 // Xpcshell tests may not have http handler
367 LOG((" missing gHttpHandler?"));
368 return NS_OK;
371 *aBlocked = true;
372 mTailQueue.AppendElement(aRequest);
374 LOG((" request queued"));
376 if (!mUntailTimer) {
377 ScheduleUnblock();
380 return NS_OK;
383 NS_IMETHODIMP
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,
390 aRequest, removed));
392 // Stop untail timer if all tail requests are canceled.
393 if (removed && mTailQueue.IsEmpty()) {
394 if (mUntailTimer) {
395 mUntailTimer->Cancel();
396 mUntailTimer = nullptr;
399 // Must drop to allow re-engage of the timer
400 mTimerScheduledAt = TimeStamp();
403 return NS_OK;
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)));
410 if (mUntailTimer) {
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);
426 NS_IMETHODIMP
427 RequestContext::CancelTailPendingRequests(nsresult aResult) {
428 MOZ_ASSERT(NS_IsMainThread());
429 MOZ_ASSERT(NS_FAILED(aResult));
431 ProcessTailQueue(aResult);
432 return NS_OK;
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());
443 sSelf = this;
445 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
446 runtime->GetProcessID(&mRCIDNamespace);
449 RequestContextService::~RequestContextService() {
450 MOZ_ASSERT(NS_IsMainThread());
451 Shutdown();
452 sSelf = nullptr;
455 nsresult RequestContextService::Init() {
456 nsresult rv;
458 MOZ_ASSERT(NS_IsMainThread());
459 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
460 if (!obs) {
461 return NS_ERROR_NOT_AVAILABLE;
464 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
465 if (NS_FAILED(rv)) {
466 return rv;
468 obs->AddObserver(this, "content-document-interactive", false);
469 if (NS_FAILED(rv)) {
470 return rv;
473 return NS_OK;
476 void RequestContextService::Shutdown() {
477 MOZ_ASSERT(NS_IsMainThread());
478 // We need to do this to prevent the requests from being scheduled after
479 // shutdown.
480 for (const auto& data : mTable.Values()) {
481 data->CancelTailPendingRequests(NS_ERROR_ABORT);
483 mTable.Clear();
484 sShutdown = true;
487 /* static */
488 already_AddRefed<nsIRequestContextService>
489 RequestContextService::GetOrCreate() {
490 MOZ_ASSERT(NS_IsMainThread());
492 if (sShutdown) {
493 return nullptr;
496 RefPtr<RequestContextService> svc;
497 if (gSingleton) {
498 svc = gSingleton;
499 } else {
500 svc = new RequestContextService();
501 nsresult rv = svc->Init();
502 NS_ENSURE_SUCCESS(rv, nullptr);
503 gSingleton = svc;
504 ClearOnShutdown(&gSingleton);
507 return svc.forget();
510 NS_IMETHODIMP
511 RequestContextService::GetRequestContext(const uint64_t rcID,
512 nsIRequestContext** rc) {
513 MOZ_ASSERT(NS_IsMainThread());
514 NS_ENSURE_ARG_POINTER(rc);
515 *rc = nullptr;
517 if (sShutdown) {
518 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
521 if (!rcID) {
522 return NS_ERROR_INVALID_ARG;
525 *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] {
526 nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
527 return newSC;
528 })).take();
530 return NS_OK;
533 NS_IMETHODIMP
534 RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
535 nsIRequestContext** rc) {
536 nsresult rv;
538 uint64_t rcID;
539 rv = aLoadGroup->GetRequestContextID(&rcID);
540 if (NS_FAILED(rv)) {
541 return rv;
544 return GetRequestContext(rcID, rc);
547 NS_IMETHODIMP
548 RequestContextService::NewRequestContext(nsIRequestContext** rc) {
549 MOZ_ASSERT(NS_IsMainThread());
550 NS_ENSURE_ARG_POINTER(rc);
551 *rc = nullptr;
553 if (sShutdown) {
554 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
557 uint64_t rcID =
558 ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
559 mNextRCID++;
561 nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
562 mTable.InsertOrUpdate(rcID, newSC);
563 newSC.swap(*rc);
565 return NS_OK;
568 NS_IMETHODIMP
569 RequestContextService::RemoveRequestContext(const uint64_t rcID) {
570 MOZ_ASSERT(NS_IsMainThread());
571 mTable.Remove(rcID);
572 return NS_OK;
575 NS_IMETHODIMP
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)) {
580 Shutdown();
581 return NS_OK;
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.
589 if (!document) {
590 return NS_OK;
592 nsIDocShell* ds = document->GetDocShell();
593 // XML documents don't always have a docshell assigned
594 if (!ds) {
595 return NS_OK;
597 nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
598 if (!dl) {
599 return NS_OK;
601 nsCOMPtr<nsILoadGroup> lg;
602 dl->GetLoadGroup(getter_AddRefs(lg));
603 if (!lg) {
604 return NS_OK;
606 nsCOMPtr<nsIRequestContext> rc;
607 GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
608 if (rc) {
609 rc->DOMContentLoaded();
612 return NS_OK;
615 MOZ_ASSERT(false, "Unexpected observer topic");
616 return NS_OK;
619 } // namespace net
620 } // namespace mozilla
622 #undef LOG