summaryrefslogtreecommitdiffstats
path: root/netwerk/base/RequestContextService.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/base/RequestContextService.cpp622
1 files changed, 622 insertions, 0 deletions
diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp
new file mode 100644
index 0000000000..72331bdc1e
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDocumentLoader.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIXULRuntime.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "RequestContextService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PSpdyPush.h"
+
+#include "../protocol/http/nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gRequestContextLog("RequestContext");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
+
+static StaticRefPtr<RequestContextService> gSingleton;
+
+// This is used to prevent adding tail pending requests after shutdown
+static bool sShutdown = false;
+
+// nsIRequestContext
+class RequestContext final : public nsIRequestContext,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit RequestContext(const uint64_t id);
+
+ private:
+ virtual ~RequestContext();
+
+ void ProcessTailQueue(nsresult aResult);
+ // Reschedules the timer if needed
+ void ScheduleUnblock();
+ // Hard-reschedules the timer
+ void RescheduleUntailTimer(TimeStamp const& now);
+
+ uint64_t mID;
+ Atomic<uint32_t> mBlockingTransactionCount;
+ UniquePtr<SpdyPushCache> mSpdyCache;
+
+ using PendingTailRequest = nsCOMPtr<nsIRequestTailUnblockCallback>;
+ // Number of known opened non-tailed requets
+ uint32_t mNonTailRequests;
+ // Queue of requests that have been tailed, when conditions are met
+ // we call each of them to unblock and drop the reference
+ nsTArray<PendingTailRequest> mTailQueue;
+ // Loosly scheduled timer, never scheduled further to the future than
+ // mUntailAt time
+ nsCOMPtr<nsITimer> mUntailTimer;
+ // Timestamp when the timer is expected to fire,
+ // always less than or equal to mUntailAt
+ TimeStamp mTimerScheduledAt;
+ // Timestamp when we want to actually untail queued requets based on
+ // the number of request count change in the past; iff this timestamp
+ // is set, we tail requests
+ TimeStamp mUntailAt;
+
+ // Timestamp of the navigation start time, set to Now() in BeginLoad().
+ // This is used to progressively lower the maximum delay time so that
+ // we can't get to a situation when a number of repetitive requests
+ // on the page causes forever tailing.
+ TimeStamp mBeginLoadTime;
+
+ // This member is true only between DOMContentLoaded notification and
+ // next document load beginning for this request context.
+ // Top level request contexts are recycled.
+ bool mAfterDOMContentLoaded;
+};
+
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed)
+
+RequestContext::RequestContext(const uint64_t aID)
+ : mID(aID),
+ mBlockingTransactionCount(0),
+ mNonTailRequests(0),
+ mAfterDOMContentLoaded(false) {
+ LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
+}
+
+RequestContext::~RequestContext() {
+ MOZ_ASSERT(mTailQueue.Length() == 0);
+
+ LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+}
+
+NS_IMETHODIMP
+RequestContext::BeginLoad() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::BeginLoad %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextLoadBegin(mID);
+ }
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = false;
+ mBeginLoadTime = TimeStamp::NowLoRes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::DOMContentLoaded() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::DOMContentLoaded %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
+ }
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded) {
+ // There is a possibility of a duplicate notification
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = true;
+
+ // Conditions for the delay calculation has changed.
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetBlockingTransactionCount(
+ uint32_t* aBlockingTransactionCount) {
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::AddBlockingTransaction() {
+ mBlockingTransactionCount++;
+ LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveBlockingTransaction(uint32_t* outval) {
+ NS_ENSURE_ARG_POINTER(outval);
+ mBlockingTransactionCount--;
+ LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ *outval = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); }
+
+void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
+ mSpdyCache = WrapUnique(aSpdyPushCache);
+}
+
+uint64_t RequestContext::GetID() { return mID; }
+
+NS_IMETHODIMP
+RequestContext::AddNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ++mNonTailRequests;
+ LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests));
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNonTailRequests > 0);
+
+ LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests - 1));
+
+ --mNonTailRequests;
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+void RequestContext::ScheduleUnblock() {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler) {
+ return;
+ }
+
+ uint32_t quantum =
+ gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
+ uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
+ uint32_t totalMax = gHttpHandler->TailBlockingTotalMax();
+
+ if (!mBeginLoadTime.IsNull()) {
+ // We decrease the maximum delay progressively with the time since the page
+ // load begin. This seems like a reasonable and clear heuristic allowing us
+ // to start loading tailed requests in a deterministic time after the load
+ // has started.
+
+ uint32_t sinceBeginLoad = static_cast<uint32_t>(
+ (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds());
+ uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax);
+ uint32_t proportion = totalMax // values clamped between 0 and 60'000
+ ? (delayMax * tillTotal) / totalMax
+ : 0;
+ delayMax = std::min(delayMax, proportion);
+ }
+
+ CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
+
+ if (!mAfterDOMContentLoaded) {
+ // Before DOMContentLoaded notification we want to make sure that tailed
+ // requests don't start when there is a short delay during which we may
+ // not have any active requests on the page happening.
+ delay += quantum;
+ }
+
+ if (!delay.isValid() || delay.value() > delayMax) {
+ delay = delayMax;
+ }
+
+ LOG(
+ ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
+ "delay=%u after-DCL=%d",
+ this, mNonTailRequests, mTailQueue.Length(), delay.value(),
+ mAfterDOMContentLoaded));
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
+
+ if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
+ LOG(("RequestContext %p timer would fire too late, rescheduling", this));
+ RescheduleUntailTimer(now);
+ }
+}
+
+void RequestContext::RescheduleUntailTimer(TimeStamp const& now) {
+ MOZ_ASSERT(mUntailAt >= now);
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ }
+
+ if (!mTailQueue.Length()) {
+ mUntailTimer = nullptr;
+ mTimerScheduledAt = TimeStamp();
+ return;
+ }
+
+ TimeDuration interval = mUntailAt - now;
+ if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
+ // When the number of untailed requests goes down,
+ // let's half the interval, since it's likely we would
+ // reschedule for a shorter time again very soon.
+ // This will likely save rescheduling this timer.
+ interval = interval / int64_t(2);
+ mTimerScheduledAt = mUntailAt - interval;
+ } else {
+ mTimerScheduledAt = mUntailAt;
+ }
+
+ uint32_t delay = interval.ToMilliseconds();
+ nsresult rv =
+ NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not reschedule untail timer");
+ }
+
+ LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
+}
+
+NS_IMETHODIMP
+RequestContext::Notify(nsITimer* timer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(timer == mUntailTimer);
+ MOZ_ASSERT(!mTimerScheduledAt.IsNull());
+ MOZ_ASSERT(mTailQueue.Length());
+
+ mUntailTimer = nullptr;
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
+ LOG(("RequestContext %p timer fired too soon, rescheduling", this));
+ RescheduleUntailTimer(now);
+ return NS_OK;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+
+ ProcessTailQueue(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetName(nsACString& aName) {
+ aName.AssignLiteral("RequestContext");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
+ bool* aBlocked) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
+ this, aRequest, mTailQueue.Length()));
+
+ *aBlocked = false;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (mUntailAt.IsNull()) {
+ LOG((" untail time passed"));
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded && !mNonTailRequests) {
+ LOG((" after DOMContentLoaded and no untailed requests"));
+ return NS_OK;
+ }
+
+ if (!gHttpHandler) {
+ // Xpcshell tests may not have http handler
+ LOG((" missing gHttpHandler?"));
+ return NS_OK;
+ }
+
+ *aBlocked = true;
+ mTailQueue.AppendElement(aRequest);
+
+ LOG((" request queued"));
+
+ if (!mUntailTimer) {
+ ScheduleUnblock();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool removed = mTailQueue.RemoveElement(aRequest);
+
+ LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
+ aRequest, removed));
+
+ // Stop untail timer if all tail requests are canceled.
+ if (removed && mTailQueue.IsEmpty()) {
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+ }
+
+ return NS_OK;
+}
+
+void RequestContext::ProcessTailQueue(nsresult aResult) {
+ LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
+ this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to stop tailing requests
+ mUntailAt = TimeStamp();
+
+ nsTArray<PendingTailRequest> queue = std::move(mTailQueue);
+
+ for (const auto& request : queue) {
+ LOG((" untailing %p", request.get()));
+ request->OnTailUnblock(aResult);
+ }
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailPendingRequests(nsresult aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_FAILED(aResult));
+
+ ProcessTailQueue(aResult);
+ return NS_OK;
+}
+
+// nsIRequestContextService
+RequestContextService* RequestContextService::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
+
+RequestContextService::RequestContextService() {
+ MOZ_ASSERT(!sSelf, "multiple rcs instances!");
+ MOZ_ASSERT(NS_IsMainThread());
+ sSelf = this;
+
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ runtime->GetProcessID(&mRCIDNamespace);
+}
+
+RequestContextService::~RequestContextService() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+ sSelf = nullptr;
+}
+
+nsresult RequestContextService::Init() {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ obs->AddObserver(this, "content-document-interactive", false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void RequestContextService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We need to do this to prevent the requests from being scheduled after
+ // shutdown.
+ for (const auto& data : mTable.Values()) {
+ data->CancelTailPendingRequests(NS_ERROR_ABORT);
+ }
+ mTable.Clear();
+ sShutdown = true;
+}
+
+/* static */
+already_AddRefed<nsIRequestContextService>
+RequestContextService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ RefPtr<RequestContextService> svc;
+ if (gSingleton) {
+ svc = gSingleton;
+ } else {
+ svc = new RequestContextService();
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ gSingleton = svc;
+ ClearOnShutdown(&gSingleton);
+ }
+
+ return svc.forget();
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContext(const uint64_t rcID,
+ nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!rcID) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ return newSC;
+ })).take();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
+ nsIRequestContext** rc) {
+ nsresult rv;
+
+ uint64_t rcID;
+ rv = aLoadGroup->GetRequestContextID(&rcID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetRequestContext(rcID, rc);
+}
+
+NS_IMETHODIMP
+RequestContextService::NewRequestContext(nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ uint64_t rcID =
+ ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
+ mNextRCID++;
+
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.InsertOrUpdate(rcID, newSC);
+ newSC.swap(*rc);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::RemoveRequestContext(const uint64_t rcID) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Remove(rcID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp("content-document-interactive", topic)) {
+ nsCOMPtr<dom::Document> document(do_QueryInterface(subject));
+ MOZ_ASSERT(document);
+ // We want this be triggered also for iframes, since those track their
+ // own request context ids.
+ if (!document) {
+ return NS_OK;
+ }
+ nsIDocShell* ds = document->GetDocShell();
+ // XML documents don't always have a docshell assigned
+ if (!ds) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
+ if (!dl) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsILoadGroup> lg;
+ dl->GetLoadGroup(getter_AddRefs(lg));
+ if (!lg) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIRequestContext> rc;
+ GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
+ if (rc) {
+ rc->DOMContentLoaded();
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "Unexpected observer topic");
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG