Autogenerated with:
cp dom/.clang-format netwerk/ && mach format netwerk/**.{cpp,h,mm}
Manual fixes:
* mozurl/cbindgen.toml (including headers earlier)
* NetlinkService.h (forward-declaring a necessary struct)
* nsIWebSocketChannel.idl (forward-declaring OriginAttributes).
* nsAsyncRedirectVerifyHelper.h (forward-declaring nsIEventTarget)
* CapsuleEncoder.h gets a missing Capsule forward-decl.
* WebSocketConnectionBase.h gets some missing includes.
* HappyEyeballsConnectionAttempt gets some missing includes.
* nsUDPSocket missed nsSocketTransportService2.h for OnSocketThread().
* HttpInfo missed a forward declaration.
* MockHttpAuth missed an nsCOMPtr include.
* nsHttpAuthManager missed a forward declaration for nsIHttpAuthCache.
* WebTransportSessionProxy was using mozilla::Mutex in a member without
including it.
* win32 fixes to keep include order because windows headers suck.
* nsInputStreamPump needs an Atomics.h include.
Differential Revision: https://phabricator.services.mozilla.com/D311696
4098 lines
140 KiB
C++
4098 lines
140 KiB
C++
/* 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/. */
|
||
|
||
// HttpLog.h should generally be included first
|
||
#include "HttpLog.h"
|
||
|
||
// Log on level :5, instead of default :4.
|
||
#undef LOG
|
||
#define LOG(args) LOG5(args)
|
||
#undef LOG_ENABLED
|
||
#define LOG_ENABLED() LOG5_ENABLED()
|
||
|
||
#include <algorithm>
|
||
#include <utility>
|
||
|
||
#include "ConnectionHandle.h"
|
||
#include "HttpConnectionUDP.h"
|
||
#include "NullHttpTransaction.h"
|
||
#include "SpeculativeTransaction.h"
|
||
#include "mozilla/Components.h"
|
||
#include "mozilla/PerfStats.h"
|
||
#include "mozilla/ProfilerMarkers.h"
|
||
#include "mozilla/SpinEventLoopUntil.h"
|
||
#include "mozilla/StaticPrefs_network.h"
|
||
#include "mozilla/glean/NetwerkMetrics.h"
|
||
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
|
||
#include "mozilla/net/DNS.h"
|
||
#include "mozilla/net/DashboardTypes.h"
|
||
#include "nsCOMPtr.h"
|
||
#include "nsHttpConnectionMgr.h"
|
||
#include "nsHttpHandler.h"
|
||
#include "nsIClassOfService.h"
|
||
#include "nsIDNSByTypeRecord.h"
|
||
#include "nsIDNSListener.h"
|
||
#include "nsIDNSRecord.h"
|
||
#include "nsIDNSService.h"
|
||
#include "nsIHttpChannelInternal.h"
|
||
#include "nsIPipe.h"
|
||
#include "nsIRequestContext.h"
|
||
#include "nsISocketTransport.h"
|
||
#include "nsISocketTransportService.h"
|
||
#include "nsITransport.h"
|
||
#include "nsIXPConnect.h"
|
||
#include "nsInterfaceRequestorAgg.h"
|
||
#include "nsNetCID.h"
|
||
#include "nsNetSegmentUtils.h"
|
||
#include "nsNetUtil.h"
|
||
#include "nsQueryObject.h"
|
||
#include "nsSocketTransportService2.h"
|
||
#include "nsStreamUtils.h"
|
||
|
||
using namespace mozilla;
|
||
|
||
namespace geckoprofiler::markers {
|
||
|
||
struct UrlMarker {
|
||
static constexpr Span<const char> MarkerTypeName() {
|
||
return MakeStringSpan("Url");
|
||
}
|
||
static void StreamJSONMarkerData(
|
||
mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
|
||
const mozilla::ProfilerString8View& aURL, const TimeDuration& aDuration,
|
||
uint64_t aChannelId) {
|
||
if (aURL.Length() != 0) {
|
||
aWriter.StringProperty("url", aURL);
|
||
}
|
||
if (!aDuration.IsZero()) {
|
||
aWriter.DoubleProperty("duration", aDuration.ToMilliseconds());
|
||
}
|
||
aWriter.IntProperty("channelId", static_cast<int64_t>(aChannelId));
|
||
}
|
||
static MarkerSchema MarkerTypeDisplay() {
|
||
using MS = MarkerSchema;
|
||
MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
|
||
schema.SetTableLabel("{marker.data.url}");
|
||
schema.AddKeyFormat("url", MS::Format::Url);
|
||
schema.AddKeyLabelFormat("duration", "Duration", MS::Format::Duration);
|
||
// Bug 1618687 - Use channelId to segment "Waiting for Socket Thread".
|
||
schema.AddKeyFormat("channelId", MS::Format::Integer);
|
||
return schema;
|
||
}
|
||
};
|
||
|
||
} // namespace geckoprofiler::markers
|
||
|
||
namespace mozilla::net {
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver, nsINamed)
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
nsHttpConnectionMgr::nsHttpConnectionMgr() {
|
||
LOG(("Creating nsHttpConnectionMgr @%p\n", this));
|
||
}
|
||
|
||
nsHttpConnectionMgr::~nsHttpConnectionMgr() {
|
||
LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
|
||
MOZ_ASSERT(mCoalescingHash.Count() == 0);
|
||
if (mTimeoutTick) mTimeoutTick->Cancel();
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::EnsureSocketThreadTarget() {
|
||
if (mIsShuttingDown) return NS_OK;
|
||
|
||
nsCOMPtr<nsIEventTarget> sts;
|
||
nsCOMPtr<nsIIOService> ioService = components::IO::Service();
|
||
if (ioService) {
|
||
nsCOMPtr<nsISocketTransportService> realSTS =
|
||
components::SocketTransport::Service();
|
||
sts = do_QueryInterface(realSTS);
|
||
}
|
||
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
|
||
// do nothing if already initialized or if we've shut down
|
||
if (*lock || mIsShuttingDown) return NS_OK;
|
||
|
||
*lock = sts;
|
||
|
||
return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::Init(
|
||
uint16_t maxUrgentExcessiveConns, uint16_t maxConns,
|
||
uint16_t maxPersistConnsPerHost, uint16_t maxPersistConnsPerProxy,
|
||
uint16_t maxRequestDelay, bool throttleEnabled, uint32_t throttleSuspendFor,
|
||
uint32_t throttleResumeFor, uint32_t throttleHoldTime,
|
||
uint32_t throttleMaxTime, bool beConservativeForProxy) {
|
||
LOG(("nsHttpConnectionMgr::Init\n"));
|
||
|
||
mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
|
||
mMaxConns = maxConns;
|
||
mMaxPersistConnsPerHost = maxPersistConnsPerHost;
|
||
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
|
||
mMaxRequestDelay = maxRequestDelay;
|
||
|
||
mThrottleEnabled = throttleEnabled;
|
||
mThrottleSuspendFor = throttleSuspendFor;
|
||
mThrottleResumeFor = throttleResumeFor;
|
||
mThrottleHoldTime = throttleHoldTime;
|
||
mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
|
||
|
||
mBeConservativeForProxy = beConservativeForProxy;
|
||
|
||
mIsShuttingDown = false;
|
||
|
||
return EnsureSocketThreadTarget();
|
||
}
|
||
|
||
class BoolWrapper : public ARefBase {
|
||
public:
|
||
BoolWrapper() = default;
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
|
||
|
||
public: // intentional!
|
||
bool mBool{false};
|
||
|
||
private:
|
||
virtual ~BoolWrapper() = default;
|
||
};
|
||
|
||
class ConnEvent : public Runnable, public nsIRunnablePriority {
|
||
public:
|
||
ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
|
||
int32_t iparam, ARefBase* vparam, uint32_t priority)
|
||
: Runnable("net::ConnEvent"),
|
||
mMgr(mgr),
|
||
mHandler(handler),
|
||
mIParam(iparam),
|
||
mVParam(vparam),
|
||
mPriority(priority) {}
|
||
|
||
NS_DECL_ISUPPORTS_INHERITED
|
||
NS_DECL_NSIRUNNABLEPRIORITY
|
||
|
||
NS_IMETHOD Run() override {
|
||
(mMgr->*mHandler)(mIParam, mVParam);
|
||
return NS_OK;
|
||
}
|
||
|
||
private:
|
||
virtual ~ConnEvent() = default;
|
||
|
||
RefPtr<nsHttpConnectionMgr> mMgr;
|
||
nsConnEventHandler mHandler;
|
||
int32_t mIParam;
|
||
RefPtr<ARefBase> mVParam;
|
||
uint32_t mPriority;
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS_INHERITED(ConnEvent, Runnable, nsIRunnablePriority)
|
||
|
||
NS_IMETHODIMP
|
||
ConnEvent::GetPriority(uint32_t* aPriority) {
|
||
*aPriority = mPriority;
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::Shutdown() {
|
||
LOG(("nsHttpConnectionMgr::Shutdown\n"));
|
||
|
||
RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
|
||
nsCOMPtr<nsIEventTarget> target;
|
||
{
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
|
||
// do nothing if already shutdown
|
||
if (!*lock) return NS_OK;
|
||
|
||
// Copy the target, then clear it to prevent further PostEvent calls from
|
||
// succeeding. This is how we indicate that we are shutting down.
|
||
target = *lock;
|
||
mIsShuttingDown = true;
|
||
*lock = nullptr;
|
||
}
|
||
|
||
nsCOMPtr<nsIRunnable> event =
|
||
new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdown, 0,
|
||
shutdownWrapper, nsIRunnablePriority::PRIORITY_NORMAL);
|
||
nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||
|
||
if (NS_FAILED(rv)) {
|
||
NS_WARNING("unable to post SHUTDOWN message");
|
||
return rv;
|
||
}
|
||
|
||
// wait for shutdown event to complete
|
||
SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns,
|
||
[&, shutdownWrapper]() { return shutdownWrapper->mBool; });
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
|
||
int32_t iparam, ARefBase* vparam,
|
||
uint32_t priority) {
|
||
(void)EnsureSocketThreadTarget();
|
||
|
||
nsCOMPtr<nsIEventTarget> target;
|
||
{
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
target = *lock;
|
||
}
|
||
|
||
if (!target) {
|
||
NS_WARNING("cannot post event if not initialized");
|
||
return NS_ERROR_NOT_INITIALIZED;
|
||
}
|
||
|
||
nsCOMPtr<nsIRunnable> event =
|
||
new ConnEvent(this, handler, iparam, vparam, priority);
|
||
return target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) {
|
||
LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
|
||
|
||
if (!mTimer) mTimer = NS_NewTimer();
|
||
|
||
// failure to create a timer is not a fatal error, but idle connections
|
||
// will not be cleaned up until we try to use them.
|
||
if (mTimer) {
|
||
mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
|
||
mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
|
||
} else {
|
||
NS_WARNING("failed to create: timer for pruning the dead connections!");
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() {
|
||
// Leave the timer in place if there are connections that potentially
|
||
// need management
|
||
if (mNumIdleConns ||
|
||
(mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
|
||
return;
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
|
||
|
||
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
|
||
mTimeOfNextWakeUp = UINT64_MAX;
|
||
if (mTimer) {
|
||
mTimer->Cancel();
|
||
mTimer = nullptr;
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
|
||
LOG(
|
||
("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
|
||
"armed=%d active=%d\n",
|
||
mTimeoutTickArmed, mNumActiveConns));
|
||
|
||
if (!mTimeoutTickArmed) return;
|
||
|
||
if (mNumActiveConns) return;
|
||
|
||
LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
|
||
|
||
mTimeoutTick->Cancel();
|
||
mTimeoutTickArmed = false;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// nsHttpConnectionMgr::nsINamed
|
||
//-----------------------------------------------------------------------------
|
||
|
||
NS_IMETHODIMP
|
||
nsHttpConnectionMgr::GetName(nsACString& aName) {
|
||
aName.AssignLiteral("nsHttpConnectionMgr");
|
||
return NS_OK;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// nsHttpConnectionMgr::nsIObserver
|
||
//-----------------------------------------------------------------------------
|
||
|
||
NS_IMETHODIMP
|
||
nsHttpConnectionMgr::Observe(nsISupports* subject, const char* topic,
|
||
const char16_t* data) {
|
||
LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
|
||
|
||
if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
|
||
nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
|
||
if (timer == mTimer) {
|
||
(void)PruneDeadConnections();
|
||
} else if (timer == mTimeoutTick) {
|
||
TimeoutTick();
|
||
} else if (timer == mTrafficTimer) {
|
||
(void)PruneNoTraffic();
|
||
} else if (timer == mThrottleTicker) {
|
||
ThrottlerTick();
|
||
} else if (timer == mDelayedResumeReadTimer) {
|
||
ResumeBackgroundThrottledTransactions();
|
||
} else {
|
||
MOZ_ASSERT(false, "unexpected timer-callback");
|
||
LOG(("Unexpected timer object\n"));
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
|
||
int32_t priority) {
|
||
LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
|
||
// Make sure a transaction is not in a pending queue.
|
||
CheckTransInPendingQueue(trans->AsHttpTransaction());
|
||
uint32_t runnablePriority = nsIRunnablePriority::PRIORITY_NORMAL;
|
||
nsHttpTransaction* httpTrans = trans->AsHttpTransaction();
|
||
if (httpTrans && httpTrans->IsTRRTransaction()) {
|
||
runnablePriority = nsIRunnablePriority::PRIORITY_MEDIUMHIGH;
|
||
}
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
|
||
trans->AsHttpTransaction(), runnablePriority);
|
||
}
|
||
|
||
class NewTransactionData : public ARefBase {
|
||
public:
|
||
NewTransactionData(nsHttpTransaction* trans, int32_t priority,
|
||
nsHttpTransaction* transWithStickyConn)
|
||
: mTrans(trans),
|
||
mPriority(priority),
|
||
mTransWithStickyConn(transWithStickyConn) {}
|
||
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
|
||
|
||
RefPtr<nsHttpTransaction> mTrans;
|
||
int32_t mPriority;
|
||
RefPtr<nsHttpTransaction> mTransWithStickyConn;
|
||
|
||
private:
|
||
virtual ~NewTransactionData() = default;
|
||
};
|
||
|
||
nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
|
||
HttpTransactionShell* trans, int32_t priority,
|
||
HttpTransactionShell* transWithStickyConn) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::AddTransactionWithStickyConn "
|
||
"[trans=%p %d transWithStickyConn=%p]\n",
|
||
trans, priority, transWithStickyConn));
|
||
// Make sure a transaction is not in a pending queue.
|
||
CheckTransInPendingQueue(trans->AsHttpTransaction());
|
||
|
||
RefPtr<NewTransactionData> data =
|
||
new NewTransactionData(trans->AsHttpTransaction(), priority,
|
||
transWithStickyConn->AsHttpTransaction());
|
||
uint32_t runnablePriority = nsIRunnablePriority::PRIORITY_NORMAL;
|
||
nsHttpTransaction* httpTrans = trans->AsHttpTransaction();
|
||
if (httpTrans && httpTrans->IsTRRTransaction()) {
|
||
runnablePriority = nsIRunnablePriority::PRIORITY_MEDIUMHIGH;
|
||
}
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
|
||
data, runnablePriority);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
|
||
int32_t priority) {
|
||
LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
|
||
priority));
|
||
uint32_t runnablePriority = nsIRunnablePriority::PRIORITY_NORMAL;
|
||
nsHttpTransaction* httpTrans = trans->AsHttpTransaction();
|
||
if (httpTrans && httpTrans->IsTRRTransaction()) {
|
||
runnablePriority = nsIRunnablePriority::PRIORITY_MEDIUMHIGH;
|
||
}
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
|
||
trans->AsHttpTransaction(), runnablePriority);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
|
||
HttpTransactionShell* trans, const ClassOfService& classOfService) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
|
||
"classOfService flags=%" PRIu32 " inc=%d]\n",
|
||
trans, static_cast<uint32_t>(classOfService.Flags()),
|
||
classOfService.Incremental()));
|
||
|
||
(void)EnsureSocketThreadTarget();
|
||
|
||
nsCOMPtr<nsIEventTarget> target;
|
||
{
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
target = *lock;
|
||
}
|
||
|
||
if (!target) {
|
||
NS_WARNING("cannot post event if not initialized");
|
||
return;
|
||
}
|
||
|
||
RefPtr<nsHttpConnectionMgr> self(this);
|
||
(void)target->Dispatch(NS_NewRunnableFunction(
|
||
"nsHttpConnectionMgr::CallUpdateClassOfServiceOnTransaction",
|
||
[cos{classOfService}, self{std::move(self)}, trans = RefPtr{trans}]() {
|
||
self->OnMsgUpdateClassOfServiceOnTransaction(
|
||
cos, trans->AsHttpTransaction());
|
||
}));
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans,
|
||
nsresult reason) {
|
||
LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
|
||
trans, static_cast<uint32_t>(reason)));
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
|
||
static_cast<int32_t>(reason), trans->AsHttpTransaction());
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::PruneDeadConnections() {
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
|
||
}
|
||
|
||
//
|
||
// Called after a timeout. Check for active connections that have had no
|
||
// traffic since they were "marked" and nuke them.
|
||
nsresult nsHttpConnectionMgr::PruneNoTraffic() {
|
||
LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
|
||
mPruningNoTraffic = true;
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::VerifyTraffic() {
|
||
LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanup() {
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
|
||
nullptr);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
|
||
nsHttpConnectionInfo* aCI) {
|
||
if (!aCI) {
|
||
return NS_ERROR_INVALID_ARG;
|
||
}
|
||
|
||
RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
|
||
ci);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::DoSingleConnectionCleanup(
|
||
nsHttpConnectionInfo* aCI, uint32_t aPriority) {
|
||
if (!aCI) {
|
||
return NS_ERROR_INVALID_ARG;
|
||
}
|
||
|
||
RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup, 0, ci,
|
||
aPriority);
|
||
}
|
||
|
||
class SpeculativeConnectArgs : public ARefBase {
|
||
public:
|
||
SpeculativeConnectArgs() = default;
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
|
||
|
||
public: // intentional!
|
||
RefPtr<SpeculativeTransaction> mTrans;
|
||
|
||
bool mFetchHTTPSRR{false};
|
||
|
||
private:
|
||
virtual ~SpeculativeConnectArgs() = default;
|
||
NS_DECL_OWNINGTHREAD
|
||
};
|
||
|
||
nsresult nsHttpConnectionMgr::SpeculativeConnect(
|
||
nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
|
||
SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
|
||
if (!IsNeckoChild() && NS_IsMainThread()) {
|
||
// HACK: make sure PSM gets initialized on the main thread.
|
||
net_EnsurePSMInit();
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
|
||
ci->HashKey().get()));
|
||
|
||
nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
|
||
do_GetInterface(callbacks);
|
||
|
||
bool allow1918 = overrider ? overrider->GetAllow1918() : false;
|
||
|
||
// Hosts that are Local IP Literals should not be speculatively
|
||
// connected - Bug 853423.
|
||
if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
|
||
"address [%s]",
|
||
ci->Origin()));
|
||
return NS_OK;
|
||
}
|
||
|
||
nsAutoCString url(ci->EndToEndSSL() ? "https://"_ns : "http://"_ns);
|
||
url += ci->GetOrigin();
|
||
PROFILER_MARKER("SpeculativeConnect", NETWORK, {}, UrlMarker, url,
|
||
TimeDuration::Zero(), 0);
|
||
|
||
RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
|
||
|
||
// Wrap up the callbacks and the target to ensure they're released on the
|
||
// target thread properly.
|
||
nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
|
||
NS_NewInterfaceRequestorAggregation(callbacks, nullptr,
|
||
getter_AddRefs(wrappedCallbacks));
|
||
|
||
caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
|
||
caps |= NS_HTTP_ERROR_SOFTLY;
|
||
args->mTrans = aTransaction
|
||
? aTransaction
|
||
: new SpeculativeTransaction(ci, wrappedCallbacks, caps);
|
||
args->mFetchHTTPSRR = aFetchHTTPSRR;
|
||
|
||
if (overrider) {
|
||
args->mTrans->SetParallelSpeculativeConnectLimit(
|
||
overrider->GetParallelSpeculativeConnectLimit());
|
||
args->mTrans->SetIgnoreIdle(overrider->GetIgnoreIdle());
|
||
args->mTrans->SetAllow1918(overrider->GetAllow1918());
|
||
}
|
||
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) {
|
||
(void)EnsureSocketThreadTarget();
|
||
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
nsCOMPtr<nsIEventTarget> temp(*lock);
|
||
temp.forget(target);
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) {
|
||
LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
|
||
|
||
(void)EnsureSocketThreadTarget();
|
||
|
||
nsCOMPtr<nsIEventTarget> target;
|
||
{
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
target = *lock;
|
||
}
|
||
|
||
if (!target) {
|
||
NS_WARNING("cannot post event if not initialized");
|
||
return NS_ERROR_NOT_INITIALIZED;
|
||
}
|
||
|
||
RefPtr<HttpConnectionBase> connRef(conn);
|
||
RefPtr<nsHttpConnectionMgr> self(this);
|
||
return target->Dispatch(NS_NewRunnableFunction(
|
||
"nsHttpConnectionMgr::CallReclaimConnection",
|
||
[conn{std::move(connRef)}, self{std::move(self)}]() {
|
||
self->OnMsgReclaimConnection(conn);
|
||
}));
|
||
}
|
||
|
||
// A structure used to marshall 6 pointers across the various necessary
|
||
// threads to complete an HTTP upgrade.
|
||
class nsCompleteUpgradeData : public ARefBase {
|
||
public:
|
||
nsCompleteUpgradeData(nsHttpTransaction* aTrans,
|
||
nsIHttpUpgradeListener* aListener, bool aJsWrapped)
|
||
: mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {}
|
||
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData, override)
|
||
|
||
RefPtr<nsHttpTransaction> mTrans;
|
||
nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
|
||
|
||
nsCOMPtr<nsISocketTransport> mSocketTransport;
|
||
nsCOMPtr<nsIAsyncInputStream> mSocketIn;
|
||
nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
|
||
|
||
bool mJsWrapped;
|
||
|
||
private:
|
||
virtual ~nsCompleteUpgradeData() {
|
||
NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
|
||
mUpgradeListener.forget());
|
||
}
|
||
};
|
||
|
||
nsresult nsHttpConnectionMgr::CompleteUpgrade(
|
||
HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
|
||
// test if aUpgradeListener is a wrapped JsObject
|
||
nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener);
|
||
|
||
bool wrapped = !!wrapper;
|
||
|
||
RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData(
|
||
aTrans->AsHttpTransaction(), aUpgradeListener, wrapped);
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) {
|
||
uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
|
||
static_cast<int32_t>(param), nullptr);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry* aEntry) {
|
||
LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [aEntry=%p]\n", aEntry));
|
||
|
||
if (aEntry->mPendingQProcessingScheduled) {
|
||
return;
|
||
}
|
||
aEntry->mPendingQProcessingScheduled = true;
|
||
|
||
RefPtr<ConnectionEntry> entry = aEntry;
|
||
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
|
||
"nsHttpConnectionMgr::ProcessPendingQForEntry",
|
||
[self = RefPtr{this}, entry]() {
|
||
entry->mPendingQProcessingScheduled = false;
|
||
if (!self->ProcessPendingQForEntry(entry, false)) {
|
||
// if we reach here, it means that we couldn't dispatch a transaction
|
||
// for the specified connection info. walk the connection table...
|
||
for (const auto& ent : self->mCT.Values()) {
|
||
if (ent.get() != entry.get() &&
|
||
self->ProcessPendingQForEntry(ent.get(), false)) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}));
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo* aCI) {
|
||
LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get()));
|
||
RefPtr<nsHttpConnectionInfo> ci;
|
||
if (aCI) {
|
||
ci = aCI->Clone();
|
||
}
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::ProcessPendingQ() {
|
||
LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
|
||
ARefBase* param) {
|
||
EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param);
|
||
gHttpHandler->SetRequestTokenBucket(tokenBucket);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket(
|
||
EventTokenBucket* aBucket) {
|
||
// Call From main thread when a new EventTokenBucket has been made in order
|
||
// to post the new value to the socket thread.
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0,
|
||
aBucket);
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
|
||
nullptr);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory"));
|
||
|
||
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
|
||
RefPtr<ConnectionEntry> ent = iter.Data();
|
||
if (ent->IsEmpty()) {
|
||
mPendingQEntries.Remove(ent.get());
|
||
iter.Remove();
|
||
}
|
||
}
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection* conn) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn));
|
||
|
||
if (!conn->ConnectionInfo()) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
|
||
|
||
if (!ent || NS_FAILED(ent->CloseIdleConnection(conn))) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection* conn) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn));
|
||
|
||
if (!conn->ConnectionInfo()) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
|
||
|
||
if (!ent || NS_FAILED(ent->RemoveIdleConnection(conn))) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
|
||
ConnectionEntry* ent, HashNumber key, bool justKidding, bool aNoHttp2,
|
||
bool aNoHttp3) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
|
||
MOZ_ASSERT(ent->mConnInfo);
|
||
nsHttpConnectionInfo* ci = ent->mConnInfo;
|
||
|
||
nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key);
|
||
if (!listOfWeakConns) {
|
||
return nullptr;
|
||
}
|
||
|
||
uint32_t listLen = listOfWeakConns->Length();
|
||
for (uint32_t j = 0; j < listLen;) {
|
||
RefPtr<HttpConnectionBase> potentialMatch =
|
||
do_QueryReferent(listOfWeakConns->ElementAt(j));
|
||
if (!potentialMatch) {
|
||
// This is a connection that needs to be removed from the list
|
||
LOG(
|
||
("FindCoalescableConnectionByHashKey() found old conn %p that has "
|
||
"null weak ptr - removing\n",
|
||
listOfWeakConns->ElementAt(j).get()));
|
||
if (j != listLen - 1) {
|
||
listOfWeakConns->Elements()[j] =
|
||
listOfWeakConns->Elements()[listLen - 1];
|
||
}
|
||
listOfWeakConns->RemoveLastElement();
|
||
MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
|
||
listLen--;
|
||
continue; // without adjusting iterator
|
||
}
|
||
|
||
if (aNoHttp3 && potentialMatch->UsingHttp3()) {
|
||
j++;
|
||
continue;
|
||
}
|
||
if (aNoHttp2 && potentialMatch->UsingSpdy()) {
|
||
j++;
|
||
continue;
|
||
}
|
||
bool couldJoin;
|
||
if (justKidding) {
|
||
couldJoin =
|
||
potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
|
||
} else {
|
||
couldJoin =
|
||
potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
|
||
}
|
||
if (couldJoin) {
|
||
LOG(
|
||
("FindCoalescableConnectionByHashKey() found match conn=%p "
|
||
"key=%" PRIu32 " newCI=%s matchedCI=%s join ok\n",
|
||
potentialMatch.get(), key, ci->HashKey().get(),
|
||
potentialMatch->ConnectionInfo()->HashKey().get()));
|
||
return potentialMatch.get();
|
||
}
|
||
LOG(("FindCoalescableConnectionByHashKey() found match conn=%p key=%" PRIu32
|
||
" newCI=%s matchedCI=%s join failed\n",
|
||
potentialMatch.get(), key, ci->HashKey().get(),
|
||
potentialMatch->ConnectionInfo()->HashKey().get()));
|
||
|
||
++j; // bypassed by continue when weakptr fails
|
||
}
|
||
|
||
if (!listLen) { // shrunk to 0 while iterating
|
||
LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
|
||
mCoalescingHash.Remove(key);
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection(
|
||
ConnectionEntry* ent, bool justKidding, bool aNoHttp2, bool aNoHttp3) {
|
||
MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(ent->mConnInfo);
|
||
nsHttpConnectionInfo* ci = ent->mConnInfo;
|
||
LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
|
||
|
||
if (ci->GetWebTransport()) {
|
||
LOG(("Don't coalesce a WebTransport conn "));
|
||
return nullptr;
|
||
}
|
||
// First try and look it up by origin frame
|
||
HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
|
||
ent, ent->OriginFrameHashKey(), justKidding, aNoHttp2, aNoHttp3);
|
||
if (conn) {
|
||
LOG(("FindCoalescableConnection(%s) match conn %p on frame key %" PRIu32,
|
||
ci->HashKey().get(), conn, ent->OriginFrameHashKey()));
|
||
return conn;
|
||
}
|
||
|
||
// now check for DNS based keys
|
||
// deleted conns (null weak pointers) are removed from list
|
||
uint32_t keyLen = ent->mCoalescingKeys.Length();
|
||
for (uint32_t i = 0; i < keyLen; ++i) {
|
||
conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i],
|
||
justKidding, aNoHttp2, aNoHttp3);
|
||
|
||
auto usableEntry = [&](HttpConnectionBase* conn) {
|
||
// This is allowed by the spec, but other browsers don't coalesce
|
||
// so agressively, which surprises developers. See bug 1420777.
|
||
if (StaticPrefs::network_http_http2_aggressive_coalescing()) {
|
||
return true;
|
||
}
|
||
|
||
// Make sure that the connection's IP address is one that is in
|
||
// the set of IP addresses in the entry's DNS response.
|
||
NetAddr addr;
|
||
nsresult rv = conn->GetPeerAddr(&addr);
|
||
if (NS_FAILED(rv)) {
|
||
// Err on the side of not coalescing
|
||
return false;
|
||
}
|
||
// We don't care about remote port when matching entries.
|
||
addr.inet.port = 0;
|
||
return ent->mAddresses.Contains(addr);
|
||
};
|
||
|
||
if (conn) {
|
||
LOG(("Found connection with matching hash"));
|
||
if (usableEntry(conn)) {
|
||
LOG(("> coalescing"));
|
||
return conn;
|
||
} else {
|
||
LOG(("> not coalescing as remote address not present in DNS records"));
|
||
}
|
||
}
|
||
}
|
||
|
||
LOG(("FindCoalescableConnection(%s) no matching conn\n",
|
||
ci->HashKey().get()));
|
||
return nullptr;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
|
||
HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(newConn);
|
||
MOZ_ASSERT(newConn->ConnectionInfo());
|
||
MOZ_ASSERT(ent);
|
||
MOZ_ASSERT_IF(!newConn->ConnectionInfo()->GetHappyEyeballsEnabled(),
|
||
mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
|
||
LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn, aNoHttp3));
|
||
if (newConn->ConnectionInfo()->GetWebTransport()) {
|
||
LOG(("Don't coalesce a WebTransport conn %p", newConn));
|
||
// TODO: implement this properly in bug 1815735.
|
||
return;
|
||
}
|
||
|
||
HttpConnectionBase* existingConn =
|
||
FindCoalescableConnection(ent, true, false, false);
|
||
if (existingConn) {
|
||
// Prefer http3 connection, but allow an HTTP/2 connection if it is used for
|
||
// WebSocket.
|
||
if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
|
||
RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn);
|
||
if (connTCP && !connTCP->IsForWebSocket()) {
|
||
LOG(
|
||
("UpdateCoalescingForNewConn() found existing active H2 conn that "
|
||
"could have served newConn, but new connection is H3, therefore "
|
||
"close the H2 conncetion"));
|
||
existingConn->SetCloseReason(
|
||
ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
|
||
existingConn->DontReuse();
|
||
}
|
||
} else if (existingConn->UsingHttp3() && newConn->UsingSpdy()) {
|
||
RefPtr<nsHttpConnection> connTCP = do_QueryObject(newConn);
|
||
if (connTCP && !connTCP->IsForWebSocket() && !aNoHttp3) {
|
||
LOG(
|
||
("UpdateCoalescingForNewConn() found existing active H3 conn that "
|
||
"could have served H2 newConn graceful close of newConn=%p to "
|
||
"migrate to existingConn %p\n",
|
||
newConn, existingConn));
|
||
newConn->SetCloseReason(
|
||
ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
|
||
newConn->DontReuse();
|
||
return;
|
||
}
|
||
} else {
|
||
LOG(
|
||
("UpdateCoalescingForNewConn() found existing active conn that could "
|
||
"have served newConn "
|
||
"graceful close of newConn=%p to migrate to existingConn %p\n",
|
||
newConn, existingConn));
|
||
newConn->SetCloseReason(
|
||
ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
|
||
newConn->DontReuse();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// This connection might go into the mCoalescingHash for new transactions to
|
||
// be coalesced onto if it can accept new transactions
|
||
if (!newConn->CanDirectlyActivate()) {
|
||
return;
|
||
}
|
||
|
||
uint32_t keyLen = ent->mCoalescingKeys.Length();
|
||
for (uint32_t i = 0; i < keyLen; ++i) {
|
||
LOG(
|
||
("UpdateCoalescingForNewConn() registering newConn %p %s under key "
|
||
"%" PRIu32 "\n",
|
||
newConn, newConn->ConnectionInfo()->HashKey().get(),
|
||
ent->mCoalescingKeys[i]));
|
||
|
||
mCoalescingHash
|
||
.LookupOrInsertWith(
|
||
ent->mCoalescingKeys[i],
|
||
[] {
|
||
LOG(("UpdateCoalescingForNewConn() need new list element\n"));
|
||
return MakeUnique<nsTArray<nsWeakPtr>>(1);
|
||
})
|
||
->AppendElement(do_GetWeakReference(
|
||
static_cast<nsISupportsWeakReference*>(newConn)));
|
||
}
|
||
|
||
// this is a new connection that can be coalesced onto. hooray!
|
||
// if there are other connection to this entry (e.g.
|
||
// some could still be handshaking, shutting down, etc..) then close
|
||
// them down after any transactions that are on them are complete.
|
||
// This probably happened due to the parallel connection algorithm
|
||
// that is used only before the host is known to speak h2.
|
||
ent->MakeAllDontReuseExcept(newConn);
|
||
}
|
||
|
||
// This function lets a connection, after completing the NPN phase,
|
||
// report whether or not it is using spdy through the usingSpdy
|
||
// argument. It would not be necessary if NPN were driven out of
|
||
// the connection manager. The connection entry associated with the
|
||
// connection is then updated to indicate whether or not we want to use
|
||
// spdy with that host and update the coalescing hash
|
||
// entries used for de-sharding hostsnames.
|
||
void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn,
|
||
bool usingSpdy,
|
||
bool disallowHttp3) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
if (!conn->ConnectionInfo()) {
|
||
return;
|
||
}
|
||
if (conn->IsRacing()) {
|
||
// We are not sure if this connection will be used or not. Don't report it.
|
||
return;
|
||
}
|
||
ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
|
||
if (!ent || !usingSpdy) {
|
||
return;
|
||
}
|
||
|
||
ent->mUsingSpdy = true;
|
||
mNumSpdyHttp3ActiveConns++;
|
||
|
||
// adjust timeout timer
|
||
uint32_t ttl = conn->TimeToLive();
|
||
uint64_t timeOfExpire = NowInSeconds() + ttl;
|
||
if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
|
||
PruneDeadConnectionsAfter(ttl);
|
||
}
|
||
|
||
UpdateCoalescingForNewConn(conn, ent, disallowHttp3);
|
||
|
||
nsresult rv = ProcessPendingQ(ent->mConnInfo);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("ReportSpdyConnection conn=%p ent=%p "
|
||
"failed to process pending queue (%08x)\n",
|
||
conn, ent, static_cast<uint32_t>(rv)));
|
||
}
|
||
rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("ReportSpdyConnection conn=%p ent=%p "
|
||
"failed to post event (%08x)\n",
|
||
conn, ent, static_cast<uint32_t>(rv)));
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase* conn,
|
||
ConnectionEntry* entry) {
|
||
LOG(("nsHttpConnectionMgr::ReportHttp3Connection conn=%p", conn));
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
if (!conn->ConnectionInfo()) {
|
||
return;
|
||
}
|
||
ConnectionEntry* ent =
|
||
entry ? entry : mCT.GetWeak(conn->ConnectionInfo()->HashKey());
|
||
if (!ent) {
|
||
return;
|
||
}
|
||
if (conn->IsRacing()) {
|
||
// We are not sure if this connection will be used or not. Don't report it.
|
||
return;
|
||
}
|
||
|
||
mNumSpdyHttp3ActiveConns++;
|
||
|
||
UpdateCoalescingForNewConn(conn, ent, false);
|
||
nsresult rv = ProcessPendingQ(ent->mConnInfo);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("ReportHttp3Connection conn=%p ent=%p "
|
||
"failed to process pending queue (%08x)\n",
|
||
conn, ent, static_cast<uint32_t>(rv)));
|
||
}
|
||
rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("ReportHttp3Connection conn=%p ent=%p "
|
||
"failed to post event (%08x)\n",
|
||
conn, ent, static_cast<uint32_t>(rv)));
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
bool nsHttpConnectionMgr::DispatchPendingQ(
|
||
nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
|
||
bool considerAll) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
PendingTransactionInfo* pendingTransInfo = nullptr;
|
||
nsresult rv;
|
||
bool dispatchedSuccessfully = false;
|
||
|
||
// if !considerAll iterate the pending list until one is dispatched
|
||
// successfully. Keep iterating afterwards only until a transaction fails to
|
||
// dispatch. if considerAll == true then try and dispatch all items.
|
||
for (uint32_t i = 0; i < pendingQ.Length();) {
|
||
pendingTransInfo = pendingQ[i];
|
||
|
||
bool alreadyDnsAndConnectSocketOrWaitingForTLS =
|
||
pendingTransInfo->IsAlreadyClaimedInitializingConn();
|
||
|
||
// The transaction could be closed after calling TryDispatchTransaction
|
||
rv = TryDispatchTransaction(ent, alreadyDnsAndConnectSocketOrWaitingForTLS,
|
||
pendingTransInfo);
|
||
if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE) ||
|
||
pendingTransInfo->Transaction()->Closed()) {
|
||
if (NS_SUCCEEDED(rv)) {
|
||
LOG((" dispatching pending transaction...\n"));
|
||
} else {
|
||
LOG(
|
||
(" removing pending transaction based on "
|
||
"TryDispatchTransaction returning hard error %" PRIx32 "\n",
|
||
static_cast<uint32_t>(rv)));
|
||
if (rv == NS_ERROR_HTTP2_FALLBACK_TO_HTTP1) {
|
||
// The transaction is about to restart under a re-keyed entry. Abandon
|
||
// the in-flight attempt it still owns here, or that attempt completes
|
||
// later and re-queues the restarted transaction (bug 2051415).
|
||
nsWeakPtr weak =
|
||
pendingTransInfo->ForgetConnectionAttemptAndActiveConn();
|
||
if (RefPtr<ConnectionAttempt> attempt = do_QueryReferent(weak)) {
|
||
// Detach first so the abandoned attempt's teardown won't touch the
|
||
// transaction we Close()/Restart() below.
|
||
attempt->ForgetRealTransaction();
|
||
ent->RemoveConnectionAttempt(attempt, /* abandon = */ true);
|
||
}
|
||
pendingTransInfo->Transaction()->Close(
|
||
NS_ERROR_HTTP2_FALLBACK_TO_HTTP1);
|
||
}
|
||
}
|
||
if (pendingQ.RemoveElement(pendingTransInfo)) {
|
||
// pendingTransInfo is now potentially destroyed
|
||
dispatchedSuccessfully = true;
|
||
continue; // dont ++i as we just made the array shorter
|
||
}
|
||
|
||
LOG((" transaction not found in pending queue\n"));
|
||
}
|
||
|
||
if (dispatchedSuccessfully && !considerAll) break;
|
||
|
||
++i;
|
||
}
|
||
return dispatchedSuccessfully;
|
||
}
|
||
|
||
uint32_t nsHttpConnectionMgr::MaxPersistConnections(
|
||
ConnectionEntry* ent) const {
|
||
if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
|
||
return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
|
||
}
|
||
|
||
return static_cast<uint32_t>(mMaxPersistConnsPerHost);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::PreparePendingQForDispatching(
|
||
ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
|
||
bool considerAll) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
pendingQ.Clear();
|
||
|
||
uint32_t totalCount = ent->TotalActiveConnections();
|
||
uint32_t maxPersistConns = MaxPersistConnections(ent);
|
||
uint32_t availableConnections =
|
||
maxPersistConns > totalCount ? maxPersistConns - totalCount : 0;
|
||
|
||
// No need to try dispatching if we reach the active connection limit.
|
||
if (!availableConnections) {
|
||
return;
|
||
}
|
||
|
||
// Only have to get transactions from the queue whose window id is 0.
|
||
if (!StaticPrefs::network_http_active_tab_priority()) {
|
||
ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
|
||
return;
|
||
}
|
||
|
||
uint32_t maxFocusedWindowConnections =
|
||
availableConnections * gHttpHandler->FocusedWindowTransactionRatio();
|
||
MOZ_ASSERT(maxFocusedWindowConnections < availableConnections);
|
||
|
||
if (!maxFocusedWindowConnections) {
|
||
maxFocusedWindowConnections = 1;
|
||
}
|
||
|
||
// Only need to dispatch transactions for either focused or
|
||
// non-focused window because considerAll is false.
|
||
if (!considerAll) {
|
||
ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
|
||
maxFocusedWindowConnections);
|
||
|
||
if (pendingQ.IsEmpty()) {
|
||
ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ,
|
||
availableConnections);
|
||
}
|
||
return;
|
||
}
|
||
|
||
uint32_t maxNonFocusedWindowConnections =
|
||
availableConnections - maxFocusedWindowConnections;
|
||
nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
|
||
|
||
ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
|
||
maxFocusedWindowConnections);
|
||
|
||
if (maxNonFocusedWindowConnections) {
|
||
ent->AppendPendingQForNonFocusedWindows(
|
||
mCurrentBrowserId, remainingPendingQ, maxNonFocusedWindowConnections);
|
||
}
|
||
|
||
// If the slots for either focused or non-focused window are not filled up
|
||
// to the availability, try to use the remaining available connections
|
||
// for the other slot (with preference for the focused window).
|
||
if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
|
||
ent->AppendPendingQForFocusedWindow(
|
||
mCurrentBrowserId, pendingQ,
|
||
maxNonFocusedWindowConnections - remainingPendingQ.Length());
|
||
} else if (pendingQ.Length() < maxFocusedWindowConnections) {
|
||
ent->AppendPendingQForNonFocusedWindows(
|
||
mCurrentBrowserId, remainingPendingQ,
|
||
maxFocusedWindowConnections - pendingQ.Length());
|
||
}
|
||
|
||
MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <=
|
||
availableConnections);
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::PreparePendingQForDispatching "
|
||
"focused window pendingQ.Length()=%zu"
|
||
", remainingPendingQ.Length()=%zu\n",
|
||
pendingQ.Length(), remainingPendingQ.Length()));
|
||
|
||
// Append elements in |remainingPendingQ| to |pendingQ|. The order in
|
||
// |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
|
||
pendingQ.AppendElements(std::move(remainingPendingQ));
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry* ent,
|
||
bool considerAll) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::ProcessPendingQForEntry "
|
||
"[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
|
||
" queued=%zu]\n",
|
||
ent->mConnInfo->HashKey().get(), ent, ent->ActiveConnsLength(),
|
||
ent->IdleConnectionsLength(), ent->UrgentStartQueueLength(),
|
||
ent->PendingQueueLength()));
|
||
|
||
if (LOG_ENABLED()) {
|
||
ent->PrintPendingQ();
|
||
ent->LogConnections();
|
||
}
|
||
|
||
if (ent->PendingQueueIsEmpty() && ent->UrgentStartQueueIsEmpty()) {
|
||
return false;
|
||
}
|
||
|
||
// Move any HTTP/3 connection that is still in active list but can no longer
|
||
// serve new transactions.
|
||
ent->MoveUnusableH3ConnsToPending();
|
||
|
||
ProcessSpdyPendingQ(ent);
|
||
|
||
bool dispatchedSuccessfully = false;
|
||
|
||
if (!ent->UrgentStartQueueIsEmpty()) {
|
||
nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
|
||
ent->AppendPendingUrgentStartQ(pendingQ);
|
||
dispatchedSuccessfully = DispatchPendingQ(pendingQ, ent, considerAll);
|
||
for (const auto& transactionInfo : Reversed(pendingQ)) {
|
||
ent->InsertTransaction(transactionInfo);
|
||
}
|
||
}
|
||
|
||
if (dispatchedSuccessfully && !considerAll) {
|
||
return dispatchedSuccessfully;
|
||
}
|
||
|
||
nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
|
||
PreparePendingQForDispatching(ent, pendingQ, considerAll);
|
||
|
||
// The only case that |pendingQ| is empty is when there is no
|
||
// connection available for dispatching.
|
||
if (pendingQ.IsEmpty()) {
|
||
return dispatchedSuccessfully;
|
||
}
|
||
|
||
dispatchedSuccessfully |= DispatchPendingQ(pendingQ, ent, considerAll);
|
||
|
||
// Put the leftovers into connection entry, in the same order as they
|
||
// were before to keep the natural ordering.
|
||
for (const auto& transactionInfo : Reversed(pendingQ)) {
|
||
ent->InsertTransaction(transactionInfo, true);
|
||
}
|
||
|
||
// Only remove empty pendingQ when considerAll is true.
|
||
if (considerAll) {
|
||
ent->RemoveEmptyPendingQ();
|
||
}
|
||
|
||
MaybeRemoveEntryFromPendingSet(ent);
|
||
return dispatchedSuccessfully;
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (ent) return ProcessPendingQForEntry(ent, false);
|
||
return false;
|
||
}
|
||
|
||
// we're at the active connection limit if any one of the following conditions
|
||
// is true:
|
||
// (1) at max-connections
|
||
// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
|
||
// (3) keep-alive disabled and at max-connections-per-server
|
||
// Note: forInnerConn is true only when we are about to create a new inner
|
||
// connection.
|
||
bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry* ent,
|
||
uint32_t caps,
|
||
bool forInnerConn) {
|
||
nsHttpConnectionInfo* ci = ent->mConnInfo;
|
||
if (ci->GetWebTransport()) {
|
||
// TODO: implement this properly in bug 1815735.
|
||
return false;
|
||
}
|
||
|
||
// Enforce a single active HTTP/3 connection per entry, except when we're
|
||
// dispatching a new inner connection through an HTTP/3 proxy.
|
||
if (!(ci->IsHttp3ProxyConnection() && forInnerConn)) {
|
||
if (ent->HasActiveH3Connection()) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
uint32_t totalCount = ent->TotalActiveConnections();
|
||
uint32_t maxPersistConns = MaxPersistConnections(ent);
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
|
||
"totalCount=%u, maxPersistConns=%u]\n",
|
||
ci->HashKey().get(), caps, totalCount, maxPersistConns));
|
||
|
||
if (caps & NS_HTTP_URGENT_START) {
|
||
if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
|
||
LOG((
|
||
"The number of total connections are greater than or equal to sum of "
|
||
"max urgent-start queue length and the number of max persistent "
|
||
"connections.\n"));
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// update maxconns if potentially limited by the max socket count
|
||
// this requires a dynamic reduction in the max socket count to a point
|
||
// lower than the max-connections pref.
|
||
uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
|
||
if (mMaxConns > maxSocketCount) {
|
||
mMaxConns = maxSocketCount;
|
||
LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
|
||
mMaxConns));
|
||
}
|
||
|
||
// If there are more active connections than the global limit, then we're
|
||
// done. Purging idle connections won't get us below it.
|
||
if (mNumActiveConns >= mMaxConns) {
|
||
LOG((" num active conns == max conns\n"));
|
||
return true;
|
||
}
|
||
|
||
bool result = (totalCount >= maxPersistConns);
|
||
LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
|
||
return result;
|
||
}
|
||
|
||
// returns NS_OK if a connection was started
|
||
// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
|
||
// ephemeral limits
|
||
// returns other NS_ERROR on hard failure conditions
|
||
nsresult nsHttpConnectionMgr::MakeNewConnection(
|
||
ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
|
||
LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent,
|
||
pendingTransInfo->Transaction()));
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
if (ent->FindConnToClaim(pendingTransInfo)) {
|
||
return NS_OK;
|
||
}
|
||
|
||
nsHttpTransaction* trans = pendingTransInfo->Transaction();
|
||
|
||
// If this host is trying to negotiate a SPDY session right now,
|
||
// don't create any new connections until the result of the
|
||
// negotiation is known.
|
||
if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
|
||
(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && ent->RestrictConnections()) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
|
||
"Not Available Due to RestrictConnections()\n",
|
||
ent->mConnInfo->HashKey().get()));
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
// We need to make a new connection. If that is going to exceed the
|
||
// global connection limit then try and free up some room by closing
|
||
// an idle connection to another host. We know it won't select "ent"
|
||
// because we have already determined there are no idle connections
|
||
// to our destination
|
||
|
||
if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
|
||
// If the global number of connections is preventing the opening of new
|
||
// connections to a host without idle connections, then close them
|
||
// regardless of their TTL.
|
||
auto iter = mCT.ConstIter();
|
||
while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) {
|
||
RefPtr<ConnectionEntry> entry = iter.Data();
|
||
entry->CloseIdleConnections((mNumIdleConns + mNumActiveConns + 1) -
|
||
mMaxConns);
|
||
iter.Next();
|
||
}
|
||
}
|
||
|
||
if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns &&
|
||
StaticPrefs::network_http_http2_enabled()) {
|
||
// If the global number of connections is preventing the opening of new
|
||
// connections to a host without idle connections, then close any spdy
|
||
// ASAP.
|
||
for (const RefPtr<ConnectionEntry>& entry : mCT.Values()) {
|
||
while (entry->MakeFirstActiveSpdyConnDontReuse()) {
|
||
// Stop on <= (particularly =) because this dontreuse
|
||
// causes async close.
|
||
if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
|
||
goto outerLoopEnd;
|
||
}
|
||
}
|
||
}
|
||
outerLoopEnd:;
|
||
}
|
||
|
||
if (AtActiveConnectionLimit(ent, trans->Caps())) {
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
nsresult rv = ent->CreateDnsAndConnectSocket(
|
||
trans, trans->Caps(), false,
|
||
trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart, true,
|
||
pendingTransInfo);
|
||
if (NS_FAILED(rv)) {
|
||
/* hard failure */
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
|
||
"CreateDnsAndConnectSocket() hard failure.\n",
|
||
ent->mConnInfo->HashKey().get(), trans));
|
||
trans->Close(rv);
|
||
if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE;
|
||
return rv;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
// returns OK if a connection is found for the transaction
|
||
// and the transaction is started.
|
||
// returns ERROR_NOT_AVAILABLE if no connection can be found and it
|
||
// should be queued until circumstances change
|
||
// returns other ERROR when transaction has a hard failure and should
|
||
// not remain in the pending queue
|
||
nsresult nsHttpConnectionMgr::TryDispatchTransaction(
|
||
ConnectionEntry* ent, bool onlyReusedConnection,
|
||
PendingTransactionInfo* pendingTransInfo) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
nsHttpTransaction* trans = pendingTransInfo->Transaction();
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::TryDispatchTransaction without conn "
|
||
"[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu "
|
||
"idle=%zu]\n",
|
||
trans, ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
|
||
uint32_t(trans->Caps()), onlyReusedConnection, ent->ActiveConnsLength(),
|
||
ent->IdleConnectionsLength()));
|
||
|
||
uint32_t caps = trans->Caps();
|
||
|
||
// 0 - If this should use spdy then dispatch it post haste.
|
||
// 1 - If there is connection pressure then see if we can pipeline this on
|
||
// a connection of a matching type instead of using a new conn
|
||
// 2 - If there is an idle connection, use it!
|
||
// 3 - if class == reval or script and there is an open conn of that type
|
||
// then pipeline onto shortest pipeline of that class if limits allow
|
||
// 4 - If we aren't up against our connection limit,
|
||
// then open a new one
|
||
// 5 - Try a pipeline if we haven't already - this will be unusual because
|
||
// it implies a low connection pressure situation where
|
||
// MakeNewConnection() failed.. that is possible, but unlikely, due to
|
||
// global limits
|
||
// 6 - no connection is available - queue it
|
||
|
||
RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection;
|
||
|
||
// step 0
|
||
// look for existing spdy connection - that's always best because it is
|
||
// essentially pipelining without head of line blocking
|
||
|
||
if (ent->IsHttp3ProxyConnection()) {
|
||
RefPtr<nsHttpConnection> h2Tunnel = ent->GetH2TunnelActiveConn();
|
||
// For WebSocket/WebTransport through H3 proxy, we need to create a TCP
|
||
// tunnel through the H3 proxy first. But if there's already an H2 session
|
||
// available (from a previously established tunnel), we should use that
|
||
// instead of creating a new tunnel.
|
||
// The WebSocket transaction doesn't have a connection set (it was queued
|
||
// without one in DnsAndConnectSocket::SetupConn to avoid triggering reclaim
|
||
// when we clear it here).
|
||
if (trans->IsWebsocketUpgrade() || trans->IsForWebTransport()) {
|
||
// First check if there's an H2 session available (from existing tunnel)
|
||
// This handles the case where the tunnel was already established and the
|
||
// WebSocket transaction was reset to wait for H2 negotiation.
|
||
// We can't use GetH2orH3ActiveConn because it skips H3 proxy entries when
|
||
// looking for H2 connections. We use GetH2TunnelActiveConn to directly
|
||
// look for an H2 tunnel connection in the active connections.
|
||
if (h2Tunnel) {
|
||
LOG(
|
||
("TryDispatchTransaction: WebSocket through H3 proxy - using "
|
||
"existing H2 tunnel"));
|
||
return TryDispatchExtendedCONNECTransaction(ent, trans, h2Tunnel);
|
||
}
|
||
|
||
// No H2 session available yet - create a tunnel through the H3 proxy
|
||
RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(ent, true, false);
|
||
RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
|
||
if (connUDP) {
|
||
LOG(("TryDispatchTransaction: WebSocket through HTTP/3 proxy"));
|
||
RefPtr<HttpConnectionBase> tunnelConn;
|
||
nsresult rv = connUDP->CreateTunnelStream(
|
||
trans, getter_AddRefs(tunnelConn), true);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
ent->InsertIntoActiveConns(tunnelConn);
|
||
tunnelConn->SetInTunnel();
|
||
if (trans->IsWebsocketUpgrade()) {
|
||
trans->SetIsHttp2Websocket(true);
|
||
}
|
||
return DispatchTransaction(ent, trans, tunnelConn);
|
||
}
|
||
} else {
|
||
// Handle the case where some transactions have NS_HTTP_DISALLOW_HTTP3 set
|
||
// while we’re using an HTTP/3 proxy. The flag applies to HTTP/3 to the
|
||
// *origin server*, not to an HTTP/3 proxy, so we can’t reuse
|
||
// GetH2orH3ActiveConn.
|
||
// TODO: This is a workaround and we should revisit this.
|
||
if (h2Tunnel) {
|
||
LOG((" dispatch to spdy: [conn=%p]\n", h2Tunnel.get()));
|
||
trans->RemoveDispatchedAsBlocking(); /* just in case */
|
||
return DispatchTransaction(ent, trans, h2Tunnel);
|
||
}
|
||
}
|
||
}
|
||
|
||
RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
|
||
ent,
|
||
(!StaticPrefs::network_http_http2_enabled() ||
|
||
(caps & NS_HTTP_DISALLOW_SPDY)),
|
||
(!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
|
||
if (conn) {
|
||
LOG(("TryingDispatchTransaction: an active h2 connection exists"));
|
||
if (trans->IsWebsocketUpgrade() || trans->IsForWebTransport()) {
|
||
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
||
if (connTCP) {
|
||
return TryDispatchExtendedCONNECTransaction(ent, trans, connTCP);
|
||
}
|
||
} else {
|
||
if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
|
||
(caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) ||
|
||
!conn->IsExperienced()) {
|
||
LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
|
||
trans->RemoveDispatchedAsBlocking(); /* just in case */
|
||
nsresult rv = DispatchTransaction(ent, trans, conn);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
return NS_OK;
|
||
}
|
||
unusedSpdyPersistentConnection = conn;
|
||
}
|
||
}
|
||
|
||
// If this is not a blocking transaction and the request context for it is
|
||
// currently processing one or more blocking transactions then we
|
||
// need to just leave it in the queue until those are complete unless it is
|
||
// explicitly marked as unblocked.
|
||
if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
|
||
if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
|
||
nsIRequestContext* requestContext = trans->RequestContext();
|
||
if (requestContext) {
|
||
uint32_t blockers = 0;
|
||
if (NS_SUCCEEDED(
|
||
requestContext->GetBlockingTransactionCount(&blockers)) &&
|
||
blockers) {
|
||
// need to wait for blockers to clear
|
||
LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
|
||
requestContext, trans, blockers));
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// Mark the transaction and its load group as blocking right now to prevent
|
||
// other transactions from being reordered in the queue due to slow syns.
|
||
trans->DispatchedAsBlocking();
|
||
}
|
||
|
||
// step 1
|
||
// If connection pressure, then we want to favor pipelining of any kind
|
||
// h1 pipelining has been removed
|
||
|
||
// Subject most transactions at high parallelism to rate pacing.
|
||
// It will only be actually submitted to the
|
||
// token bucket once, and if possible it is granted admission synchronously.
|
||
// It is important to leave a transaction in the pending queue when blocked by
|
||
// pacing so it can be found on cancel if necessary.
|
||
// Transactions that cause blocking or bypass it (e.g. js/css) are not rate
|
||
// limited.
|
||
if (gHttpHandler->UseRequestTokenBucket()) {
|
||
// submit even whitelisted transactions to the token bucket though they will
|
||
// not be slowed by it
|
||
bool runNow = trans->TryToRunPacedRequest();
|
||
if (!runNow) {
|
||
if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <=
|
||
gHttpHandler->RequestTokenBucketMinParallelism()) {
|
||
runNow = true; // white list it
|
||
} else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
|
||
runNow = true; // white list it
|
||
}
|
||
}
|
||
if (!runNow) {
|
||
LOG((" blocked due to rate pacing trans=%p\n", trans));
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
}
|
||
|
||
// step 2
|
||
// consider an idle persistent connection
|
||
bool idleConnsAllUrgent = false;
|
||
if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
|
||
nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, true,
|
||
&idleConnsAllUrgent);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
LOG((" dispatched step 2 (idle) trans=%p\n", trans));
|
||
return NS_OK;
|
||
}
|
||
|
||
if (rv == NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED) {
|
||
LOG((" Local network access failure in step 2 (idle) trans=%p ",
|
||
trans));
|
||
return NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED;
|
||
}
|
||
}
|
||
|
||
// step 3
|
||
// consider pipelining scripts and revalidations
|
||
// h1 pipelining has been removed
|
||
|
||
// Don't dispatch if this transaction is waiting for HTTPS RR.
|
||
// This usually happens when the pref "network.dns.force_waiting_https_rr" is
|
||
// true or when echConfig is enabled.
|
||
if (trans->WaitingForHTTPSRR()) {
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
// step 4
|
||
if (!onlyReusedConnection) {
|
||
nsresult rv = MakeNewConnection(ent, pendingTransInfo);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
// this function returns NOT_AVAILABLE for asynchronous connects
|
||
LOG((" dispatched step 4 (async new conn) trans=%p\n", trans));
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
if (rv != NS_ERROR_NOT_AVAILABLE) {
|
||
// not available return codes should try next step as they are
|
||
// not hard errors. Other codes should stop now
|
||
LOG((" failed step 4 (%" PRIx32 ") trans=%p\n",
|
||
static_cast<uint32_t>(rv), trans));
|
||
return rv;
|
||
}
|
||
|
||
// repeat step 2 when there are only idle connections and all are urgent,
|
||
// don't respect urgency so that non-urgent transaction will be allowed
|
||
// to dispatch on an urgent-start-only marked connection to avoid
|
||
// dispatch deadlocks
|
||
if (!(trans->GetClassOfService().Flags() &
|
||
nsIClassOfService::UrgentStart) &&
|
||
idleConnsAllUrgent &&
|
||
ent->ActiveConnsLength() < MaxPersistConnections(ent)) {
|
||
rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans));
|
||
return NS_OK;
|
||
}
|
||
}
|
||
}
|
||
|
||
// step 5
|
||
// previously pipelined anything here if allowed but h1 pipelining has been
|
||
// removed
|
||
|
||
// step 6
|
||
if (unusedSpdyPersistentConnection) {
|
||
// to avoid deadlocks, we need to throw away this perfectly valid SPDY
|
||
// connection to make room for a new one that can service a no KEEPALIVE
|
||
// request
|
||
unusedSpdyPersistentConnection->DontReuse();
|
||
}
|
||
|
||
LOG((" not dispatched (queued) trans=%p\n", trans));
|
||
return NS_ERROR_NOT_AVAILABLE; /* queue it */
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn(
|
||
ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
|
||
bool respectUrgency, bool* allUrgent) {
|
||
bool onlyUrgent = !!ent->IdleConnectionsLength();
|
||
|
||
nsHttpTransaction* trans = pendingTransInfo->Transaction();
|
||
bool urgentTrans =
|
||
trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart;
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
|
||
"trans=%p, urgent=%d",
|
||
ent, trans, urgentTrans));
|
||
|
||
RefPtr<nsHttpConnection> conn =
|
||
ent->GetIdleConnection(respectUrgency, urgentTrans, &onlyUrgent);
|
||
|
||
if (allUrgent) {
|
||
*allUrgent = onlyUrgent;
|
||
}
|
||
|
||
if (conn) {
|
||
// This will update the class of the connection to be the class of
|
||
// the transaction dispatched on it.
|
||
ent->InsertIntoActiveConns(conn);
|
||
nsresult rv = DispatchTransaction(ent, trans, conn);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::TryDispatchExtendedCONNECTransaction(
|
||
ConnectionEntry* aEnt, nsHttpTransaction* aTrans, nsHttpConnection* aConn) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(aTrans->IsWebsocketUpgrade() || aTrans->IsForWebTransport());
|
||
MOZ_ASSERT(aConn);
|
||
|
||
ExtendedCONNECTSupport extendedConnect = aConn->GetExtendedCONNECTSupport();
|
||
LOG(("TryingDispatchTransaction: extended CONNECT"));
|
||
if (extendedConnect == ExtendedCONNECTSupport::NO_SUPPORT) {
|
||
LOG(
|
||
("TryingDispatchTransaction: no support for extended CONNECT over "
|
||
"HTTP/2"));
|
||
// This is a transaction wants to do extended CONNECT and we already
|
||
// have a h2 connection that do not support it, we should disable h2
|
||
// for this transaction.
|
||
aTrans->DisableSpdy();
|
||
|
||
// WebTransport doesn’t allow to fall back to HTTP/1.1.
|
||
if (aTrans->IsForWebTransport()) {
|
||
return NS_ERROR_CONNECTION_REFUSED;
|
||
}
|
||
|
||
// See the assertion in nsHttpConnectionMgr::OnMsgCompleteUpgrade.
|
||
// Transactions used for WebSockets are expected to have
|
||
// NS_HTTP_STICKY_CONNECTION, so we also need to call MakeRestartable to
|
||
// allow them to restart successfully.
|
||
aTrans->MakeSticky();
|
||
aTrans->MakeRestartable();
|
||
|
||
return NS_ERROR_HTTP2_FALLBACK_TO_HTTP1;
|
||
} else if (extendedConnect == ExtendedCONNECTSupport::SUPPORTED) {
|
||
LOG(("TryingDispatchTransaction: extended CONNECT supported"));
|
||
|
||
// No limit for number of websockets, dispatch transaction to the
|
||
// tunnel
|
||
RefPtr<HttpConnectionBase> connToTunnel;
|
||
nsresult rv =
|
||
aConn->CreateTunnelStream(aTrans, getter_AddRefs(connToTunnel), true);
|
||
if (rv == NS_ERROR_WEBTRANSPORT_SESSION_LIMIT_EXCEEDED) {
|
||
LOG(
|
||
("TryingDispatchTransaction: WebTransport session limit "
|
||
"exceeded"));
|
||
return rv;
|
||
}
|
||
aEnt->InsertIntoExtendedCONNECTConns(connToTunnel);
|
||
aTrans->SetConnection(nullptr);
|
||
connToTunnel->SetInTunnel(); // tells conn it is already in tunnel
|
||
if (aTrans->IsWebsocketUpgrade()) {
|
||
aTrans->SetIsHttp2Websocket(true);
|
||
}
|
||
rv = DispatchTransaction(aEnt, aTrans, connToTunnel);
|
||
// need to undo NonSticky bypass for transaction reset to continue
|
||
// for correct websocket upgrade handling
|
||
aTrans->MakeSticky();
|
||
aTrans->SetResettingForTunnelConn(false);
|
||
return rv;
|
||
}
|
||
|
||
// if we aren't sure that extended CONNECT is supported yet or we are
|
||
// already at the connection limit then we queue the transaction
|
||
LOG(
|
||
("TryingDispatchTransaction: unsure if extended CONNECT "
|
||
"supported"));
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::DispatchTransaction(ConnectionEntry* ent,
|
||
nsHttpTransaction* trans,
|
||
HttpConnectionBase* conn) {
|
||
uint32_t caps = trans->Caps();
|
||
int32_t priority = trans->Priority();
|
||
nsresult rv;
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::DispatchTransaction "
|
||
"[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
|
||
"isHttp3=%d]\n",
|
||
ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority,
|
||
conn->UsingSpdy(), conn->UsingHttp3()));
|
||
|
||
// It is possible for a rate-paced transaction to be dispatched independent
|
||
// of the token bucket when the amount of parallelization has changed or
|
||
// when a muxed connection (e.g. h2) becomes available.
|
||
trans->CancelPacing(NS_OK);
|
||
|
||
TimeStamp now = TimeStamp::Now();
|
||
TimeDuration elapsed = now - trans->GetPendingTime();
|
||
PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime,
|
||
elapsed);
|
||
|
||
PROFILER_MARKER(
|
||
"DispatchTransaction", NETWORK,
|
||
MarkerOptions(MarkerThreadId::MainThread(),
|
||
MarkerTiming::Interval(trans->GetPendingTime(), now)),
|
||
UrlMarker, trans->GetUrl(), elapsed, trans->ChannelId());
|
||
|
||
nsAutoCString httpVersionkey("h1"_ns);
|
||
if (conn->UsingSpdy() || conn->UsingHttp3()) {
|
||
LOG(
|
||
("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
|
||
"Connection host = %s\n",
|
||
trans->ConnectionInfo()->Origin(), conn->ConnectionInfo()->Origin()));
|
||
rv = conn->Activate(trans, caps, priority);
|
||
if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
|
||
if (conn->UsingSpdy()) {
|
||
httpVersionkey = "h2"_ns;
|
||
glean::http::transaction_wait_time_spdy.AccumulateRawDuration(
|
||
now - trans->GetPendingTime());
|
||
} else {
|
||
httpVersionkey = "h3"_ns;
|
||
glean::http::transaction_wait_time_http3.AccumulateRawDuration(
|
||
now - trans->GetPendingTime());
|
||
}
|
||
trans->SetPendingTime(false);
|
||
}
|
||
return rv;
|
||
}
|
||
|
||
MOZ_ASSERT(conn && !conn->Transaction(),
|
||
"DispatchTranaction() on non spdy active connection");
|
||
|
||
rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
|
||
|
||
if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
|
||
glean::http::transaction_wait_time_http.AccumulateRawDuration(
|
||
now - trans->GetPendingTime());
|
||
trans->SetPendingTime(false);
|
||
}
|
||
return rv;
|
||
}
|
||
|
||
// Use this method for dispatching nsAHttpTransction's. It can only safely be
|
||
// used upon first use of a connection when NPN has not negotiated SPDY vs
|
||
// HTTP/1 yet as multiplexing onto an existing SPDY session requires a
|
||
// concrete nsHttpTransaction
|
||
nsresult nsHttpConnectionMgr::DispatchAbstractTransaction(
|
||
ConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps,
|
||
HttpConnectionBase* conn, int32_t priority) {
|
||
MOZ_ASSERT(ent);
|
||
|
||
nsresult rv;
|
||
MOZ_ASSERT(!conn->UsingSpdy(),
|
||
"Spdy Must Not Use DispatchAbstractTransaction");
|
||
LOG(
|
||
("nsHttpConnectionMgr::DispatchAbstractTransaction "
|
||
"[ci=%s trans=%p caps=%x conn=%p]\n",
|
||
ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
|
||
|
||
RefPtr<nsAHttpTransaction> transaction(aTrans);
|
||
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
|
||
|
||
// give the transaction the indirect reference to the connection.
|
||
transaction->SetConnection(handle);
|
||
|
||
rv = conn->Activate(transaction, caps, priority);
|
||
if (NS_FAILED(rv)) {
|
||
LOG((" conn->Activate failed [rv=%" PRIx32 "]\n",
|
||
static_cast<uint32_t>(rv)));
|
||
DebugOnly<nsresult> rv_remove = ent->RemoveActiveConnection(conn);
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv_remove));
|
||
|
||
// sever back references to connection, and do so without triggering
|
||
// a call to ReclaimConnection ;-)
|
||
transaction->SetConnection(nullptr);
|
||
handle->Reset(); // destroy the connection
|
||
}
|
||
|
||
return rv;
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
// since "adds" and "cancels" are processed asynchronously and because
|
||
// various events might trigger an "add" directly on the socket thread,
|
||
// we must take care to avoid dispatching a transaction that has already
|
||
// been canceled (see bug 190001).
|
||
if (NS_FAILED(trans->Status())) {
|
||
LOG((" transaction was canceled... dropping event!\n"));
|
||
return NS_OK;
|
||
}
|
||
|
||
// Make sure a transaction is not in a pending queue.
|
||
CheckTransInPendingQueue(trans);
|
||
|
||
trans->SetPendingTime();
|
||
|
||
PROFILER_MARKER("ProcessNewTransaction", NETWORK,
|
||
MarkerThreadId::MainThread(), UrlMarker, trans->GetUrl(),
|
||
TimeDuration::Zero(), trans->ChannelId());
|
||
|
||
nsresult rv = NS_OK;
|
||
nsHttpConnectionInfo* ci = trans->ConnectionInfo();
|
||
MOZ_ASSERT(ci);
|
||
MOZ_ASSERT(!ci->IsHttp3() || !(trans->Caps() & NS_HTTP_DISALLOW_HTTP3));
|
||
|
||
bool isWildcard = false;
|
||
ConnectionEntry* ent = GetOrCreateConnectionEntry(
|
||
ci, trans->Caps() & NS_HTTP_DISALLOW_HTTP2_PROXY,
|
||
trans->Caps() & NS_HTTP_DISALLOW_SPDY,
|
||
trans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
|
||
MOZ_ASSERT(ent);
|
||
|
||
if (nsHttpHandler::EchConfigEnabled(ci->IsHttp3())) {
|
||
ent->MaybeUpdateEchConfig(ci);
|
||
}
|
||
|
||
// Check if the transaction already has a sticky reference to a connection.
|
||
// If so, then we can just use it directly by transferring its reference
|
||
// to the new connection variable instead of searching for a new one
|
||
|
||
nsAHttpConnection* wrappedConnection = trans->Connection();
|
||
RefPtr<HttpConnectionBase> conn;
|
||
RefPtr<PendingTransactionInfo> pendingTransInfo;
|
||
if (wrappedConnection) conn = wrappedConnection->TakeHttpConnection();
|
||
|
||
if (conn) {
|
||
MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
|
||
LOG(
|
||
("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
|
||
"sticky connection=%p\n",
|
||
trans, conn.get()));
|
||
|
||
if (!ent->IsInActiveConns(conn)) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
|
||
"sticky connection=%p needs to go on the active list\n",
|
||
trans, conn.get()));
|
||
|
||
// make sure it isn't on the idle list - we expect this to be an
|
||
// unknown fresh connection
|
||
MOZ_ASSERT(!ent->IsInIdleConnections(conn));
|
||
MOZ_ASSERT(!conn->IsExperienced());
|
||
|
||
ent->InsertIntoActiveConns(conn); // make it active
|
||
}
|
||
|
||
trans->SetConnection(nullptr);
|
||
rv = DispatchTransaction(ent, trans, conn);
|
||
} else if (isWildcard) {
|
||
// Determine which connections we want to use.
|
||
// - If this is an HTTP/3 proxy connection (`isHttp3Proxy == true`), we only
|
||
// want to consider existing HTTP/3 connections, so we set aNoHttp2 = true
|
||
// and aNoHttp3 = false.
|
||
// - Otherwise (`isHttp3Proxy == false`), we are looking for a regular
|
||
// HTTP/2 connection, so we set aNoHttp2 = false and aNoHttp3 = true.
|
||
bool isHttp3Proxy = ci->IsHttp3ProxyConnection();
|
||
RefPtr<HttpConnectionBase> conn =
|
||
GetH2orH3ActiveConn(ent, isHttp3Proxy, !isHttp3Proxy);
|
||
if (ci->UsingHttpsProxy() && ci->UsingConnect()) {
|
||
LOG(("About to create new tunnel conn from [%p]", conn.get()));
|
||
ConnectionEntry* specificEnt = mCT.GetWeak(ci->HashKey());
|
||
|
||
if (!specificEnt) {
|
||
RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
|
||
specificEnt = new ConnectionEntry(clone, mPendingQEntries);
|
||
mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
|
||
}
|
||
|
||
ent = specificEnt;
|
||
bool atLimit = AtActiveConnectionLimit(ent, trans->Caps(), true);
|
||
if (atLimit) {
|
||
LOG(("hit limit in proxy conn"));
|
||
rv = NS_ERROR_NOT_AVAILABLE;
|
||
} else {
|
||
RefPtr<HttpConnectionBase> newTunnel;
|
||
conn->CreateTunnelStream(trans, getter_AddRefs(newTunnel));
|
||
|
||
ent->InsertIntoActiveConns(newTunnel);
|
||
trans->SetConnection(nullptr);
|
||
newTunnel->SetInTunnel();
|
||
rv = DispatchTransaction(ent, trans, newTunnel);
|
||
// need to undo the bypass for transaction reset for proxy
|
||
trans->MakeNonRestartable();
|
||
}
|
||
} else {
|
||
rv = DispatchTransaction(ent, trans, conn);
|
||
}
|
||
} else {
|
||
if (!ent->AllowHttp2()) {
|
||
trans->DisableSpdy();
|
||
}
|
||
pendingTransInfo = new PendingTransactionInfo(trans);
|
||
rv = TryDispatchTransaction(ent, false, pendingTransInfo);
|
||
}
|
||
|
||
if (NS_SUCCEEDED(rv)) {
|
||
LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
|
||
return rv;
|
||
}
|
||
|
||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||
if (!pendingTransInfo) {
|
||
pendingTransInfo = new PendingTransactionInfo(trans);
|
||
}
|
||
|
||
ent->InsertTransaction(pendingTransInfo);
|
||
return NS_OK;
|
||
}
|
||
|
||
LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans,
|
||
static_cast<uint32_t>(rv)));
|
||
return rv;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::IncrementActiveConnCount() {
|
||
mNumActiveConns++;
|
||
ActivateTimeoutTick();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) {
|
||
MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
|
||
if (mNumActiveConns > 0) {
|
||
mNumActiveConns--;
|
||
}
|
||
|
||
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
||
if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--;
|
||
ConditionallyStopTimeoutTick();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::StartedConnect() {
|
||
mNumActiveConns++;
|
||
ActivateTimeoutTick(); // likely disabled by RecvdConnect()
|
||
}
|
||
|
||
void nsHttpConnectionMgr::RecvdConnect() {
|
||
MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
|
||
if (mNumActiveConns > 0) {
|
||
mNumActiveConns--;
|
||
}
|
||
|
||
ConditionallyStopTimeoutTick();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DispatchSpdyPendingQ(
|
||
nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
|
||
HttpConnectionBase* connH2, HttpConnectionBase* connH3) {
|
||
if (pendingQ.Length() == 0) {
|
||
return;
|
||
}
|
||
|
||
nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
|
||
uint32_t index;
|
||
// Dispatch all the transactions we can
|
||
for (index = 0; index < pendingQ.Length() &&
|
||
((connH3 && connH3->CanDirectlyActivate()) ||
|
||
(connH2 && connH2->CanDirectlyActivate()));
|
||
++index) {
|
||
PendingTransactionInfo* pendingTransInfo = pendingQ[index];
|
||
|
||
// We can not dispatch NS_HTTP_ALLOW_KEEPALIVE transactions.
|
||
// For WebTransport case, we can't dispatch the transaction here. We need to
|
||
// put the transaction back, so TryDispatchTransaction can be called later.
|
||
// TODO: this is hacky and should be improved.
|
||
if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
|
||
pendingTransInfo->Transaction()->IsResettingForTunnelConn()) {
|
||
leftovers.AppendElement(pendingTransInfo);
|
||
continue;
|
||
}
|
||
|
||
// Try dispatching on HTTP3 first
|
||
HttpConnectionBase* conn = nullptr;
|
||
if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3) &&
|
||
connH3 && connH3->CanDirectlyActivate()) {
|
||
conn = connH3;
|
||
} else if (!(pendingTransInfo->Transaction()->Caps() &
|
||
NS_HTTP_DISALLOW_SPDY) &&
|
||
connH2 && connH2->CanDirectlyActivate()) {
|
||
conn = connH2;
|
||
} else {
|
||
leftovers.AppendElement(pendingTransInfo);
|
||
continue;
|
||
}
|
||
|
||
nsresult rv =
|
||
DispatchTransaction(ent, pendingTransInfo->Transaction(), conn);
|
||
if (NS_FAILED(rv)) {
|
||
// Dispatching a transaction to an existing HTTP/2 session should not
|
||
// fail. The only expected failure here is
|
||
// NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED (e.g., when a
|
||
// speculative/preconnected HTTP/2 session was created before). Any other
|
||
// rv indicates a bug.
|
||
MOZ_ASSERT(rv == NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
|
||
"Dispatch H2 transaction should only fail with Local Network "
|
||
"Access denied");
|
||
LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
|
||
pendingTransInfo->Transaction()));
|
||
pendingTransInfo->Transaction()->Close(rv);
|
||
}
|
||
}
|
||
|
||
// Slurp up the rest of the pending queue into our leftovers bucket (we
|
||
// might have some left if conn->CanDirectlyActivate returned false)
|
||
for (; index < pendingQ.Length(); ++index) {
|
||
PendingTransactionInfo* pendingTransInfo = pendingQ[index];
|
||
leftovers.AppendElement(pendingTransInfo);
|
||
}
|
||
|
||
// Put the leftovers back in the pending queue and get rid of the
|
||
// transactions we dispatched
|
||
pendingQ = std::move(leftovers);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::MaybeRemoveEntryFromPendingSet(ConnectionEntry* ent) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
if (ent->PendingQueueIsEmpty() && ent->UrgentStartQueueIsEmpty()) {
|
||
mPendingQEntries.Remove(ent);
|
||
}
|
||
}
|
||
|
||
// This function tries to dispatch the pending h2 or h3 transactions on
|
||
// the connection entry sent in as an argument. It will do so on the
|
||
// active h2 or h3 connection either in that same entry or from the
|
||
// coalescing hash table
|
||
void nsHttpConnectionMgr::ProcessSpdyPendingQ(ConnectionEntry* ent) {
|
||
// Look for one HTTP2 and one HTTP3 connection.
|
||
// We may have transactions that have NS_HTTP_DISALLOW_SPDY/HTTP3 set
|
||
// and we may need an alternative.
|
||
HttpConnectionBase* connH3 = GetH2orH3ActiveConn(ent, true, false);
|
||
HttpConnectionBase* connH2 = GetH2orH3ActiveConn(ent, false, true);
|
||
if ((!connH3 || !connH3->CanDirectlyActivate()) &&
|
||
(!connH2 || !connH2->CanDirectlyActivate())) {
|
||
return;
|
||
}
|
||
|
||
nsTArray<RefPtr<PendingTransactionInfo>> urgentQ;
|
||
ent->AppendPendingUrgentStartQ(urgentQ);
|
||
DispatchSpdyPendingQ(urgentQ, ent, connH2, connH3);
|
||
for (const auto& transactionInfo : Reversed(urgentQ)) {
|
||
ent->InsertTransaction(transactionInfo);
|
||
}
|
||
|
||
if ((!connH3 || !connH3->CanDirectlyActivate()) &&
|
||
(!connH2 || !connH2->CanDirectlyActivate())) {
|
||
return;
|
||
}
|
||
|
||
nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
|
||
// XXX Get all transactions for SPDY currently.
|
||
ent->AppendPendingQForNonFocusedWindows(0, pendingQ);
|
||
DispatchSpdyPendingQ(pendingQ, ent, connH2, connH3);
|
||
|
||
// Put the leftovers back in the pending queue.
|
||
for (const auto& transactionInfo : pendingQ) {
|
||
ent->InsertTransaction(transactionInfo);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
|
||
AutoTArray<RefPtr<ConnectionEntry>, 16> entries;
|
||
for (ConnectionEntry* entry : mPendingQEntries) {
|
||
entries.AppendElement(entry);
|
||
}
|
||
for (const auto& entry : entries) {
|
||
ProcessSpdyPendingQ(entry.get());
|
||
MaybeRemoveEntryFromPendingSet(entry.get());
|
||
}
|
||
}
|
||
|
||
// Given a connection entry, return an active h2 or h3 connection
|
||
// that can be directly activated or null.
|
||
HttpConnectionBase* nsHttpConnectionMgr::GetH2orH3ActiveConn(
|
||
ConnectionEntry* ent, bool aNoHttp2, bool aNoHttp3) {
|
||
if (aNoHttp2 && aNoHttp3) {
|
||
return nullptr;
|
||
}
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(ent);
|
||
|
||
HttpConnectionBase* conn = ent->GetH2orH3ActiveConn(aNoHttp2, aNoHttp3);
|
||
if (conn) {
|
||
return conn;
|
||
}
|
||
|
||
nsHttpConnectionInfo* ci = ent->mConnInfo;
|
||
|
||
// there was no active HTTP2/3 connection in the connection entry, but
|
||
// there might be one in the hash table for coalescing
|
||
HttpConnectionBase* existingConn =
|
||
FindCoalescableConnection(ent, false, aNoHttp2, aNoHttp3);
|
||
if (existingConn) {
|
||
LOG(
|
||
("GetH2orH3ActiveConn() request for ent %p %s "
|
||
"found an active connection %p in the coalescing hashtable\n",
|
||
ent, ci->HashKey().get(), existingConn));
|
||
return existingConn;
|
||
}
|
||
|
||
LOG(
|
||
("GetH2orH3ActiveConn() request for ent %p %s "
|
||
"did not find an active connection\n",
|
||
ent, ci->HashKey().get()));
|
||
return nullptr;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) {
|
||
if (!OnSocketThread()) {
|
||
(void)PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
|
||
return;
|
||
}
|
||
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
|
||
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
|
||
RefPtr<ConnectionEntry> ent = iter.Data();
|
||
|
||
// Close all active connections.
|
||
ent->CloseActiveConnections();
|
||
|
||
// Close all idle connections.
|
||
ent->CloseIdleConnections();
|
||
|
||
// Close tunneled connections
|
||
ent->CloseExtendedCONNECTConnections();
|
||
|
||
ent->ClosePendingConnections();
|
||
|
||
// Close all pending transactions.
|
||
ent->CancelAllTransactions(NS_ERROR_ABORT);
|
||
|
||
// Close all half open tcp connections.
|
||
ent->CloseAllConnectionAttempts();
|
||
|
||
iter.Remove();
|
||
}
|
||
|
||
mPendingQEntries.Clear();
|
||
mActiveTransactions[false].Clear();
|
||
mActiveTransactions[true].Clear();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
|
||
|
||
gHttpHandler->StopRequestTokenBucket();
|
||
AbortAndCloseAllConnections(0, nullptr);
|
||
|
||
// If all idle connections are removed we can stop pruning dead
|
||
// connections.
|
||
ConditionallyStopPruneDeadConnectionsTimer();
|
||
|
||
if (mTimeoutTick) {
|
||
mTimeoutTick->Cancel();
|
||
mTimeoutTick = nullptr;
|
||
mTimeoutTickArmed = false;
|
||
}
|
||
if (mTimer) {
|
||
mTimer->Cancel();
|
||
mTimer = nullptr;
|
||
}
|
||
if (mTrafficTimer) {
|
||
mTrafficTimer->Cancel();
|
||
mTrafficTimer = nullptr;
|
||
}
|
||
DestroyThrottleTicker();
|
||
|
||
mCoalescingHash.Clear();
|
||
|
||
uint32_t priority = nsIRunnablePriority::PRIORITY_NORMAL;
|
||
if (StaticPrefs::network_trr_high_priority_events()) {
|
||
// This doesn't technically need to be under the TRR pref
|
||
// but it's safer to be under some pref in case the reordering
|
||
// causes any regressions. Can be removed after a few months on release.
|
||
priority = nsIRunnablePriority::PRIORITY_MEDIUMHIGH;
|
||
}
|
||
|
||
// signal shutdown complete
|
||
nsCOMPtr<nsIRunnable> runnable = new ConnEvent(
|
||
this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, 0, param, priority);
|
||
NS_DispatchToMainThread(runnable);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(NS_IsMainThread());
|
||
LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
|
||
|
||
BoolWrapper* shutdown = static_cast<BoolWrapper*>(param);
|
||
shutdown->mBool = true;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority,
|
||
ARefBase* param) {
|
||
nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
|
||
|
||
LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans));
|
||
trans->SetPriority(priority);
|
||
nsresult rv = ProcessNewTransaction(trans);
|
||
if (NS_FAILED(rv)) trans->Close(rv); // for whatever its worth
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
NewTransactionData* data = static_cast<NewTransactionData*>(param);
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn "
|
||
"[trans=%p, transWithStickyConn=%p, conn=%p]\n",
|
||
data->mTrans.get(), data->mTransWithStickyConn.get(),
|
||
data->mTransWithStickyConn->Connection()));
|
||
|
||
MOZ_ASSERT(data->mTransWithStickyConn &&
|
||
data->mTransWithStickyConn->Caps() & NS_HTTP_STICKY_CONNECTION);
|
||
|
||
data->mTrans->SetPriority(data->mPriority);
|
||
|
||
RefPtr<nsAHttpConnection> conn = data->mTransWithStickyConn->Connection();
|
||
if (conn && conn->IsPersistent()) {
|
||
// This is so far a workaround to only reuse persistent
|
||
// connection for authentication retry. See bug 459620 comment 4
|
||
// for details.
|
||
LOG((" Reuse connection [%p] for transaction [%p]", conn.get(),
|
||
data->mTrans.get()));
|
||
data->mTrans->SetConnection(conn);
|
||
}
|
||
|
||
nsresult rv = ProcessNewTransaction(data->mTrans);
|
||
if (NS_FAILED(rv)) {
|
||
data->mTrans->Close(rv); // for whatever its worth
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
|
||
|
||
RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction*>(param);
|
||
trans->SetPriority(priority);
|
||
|
||
if (!trans->ConnectionInfo()) {
|
||
return;
|
||
}
|
||
ConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
|
||
|
||
if (ent) {
|
||
ent->ReschedTransaction(trans);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
|
||
ClassOfService cos, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
|
||
"[trans=%p]\n",
|
||
param));
|
||
|
||
nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
|
||
|
||
ClassOfService previous = trans->GetClassOfService();
|
||
trans->SetClassOfService(cos);
|
||
|
||
// incremental change alone will not trigger a reschedule
|
||
if ((previous.Flags() ^ cos.Flags()) &
|
||
(NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
|
||
(void)RescheduleTransaction(trans, trans->Priority());
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
|
||
|
||
nsresult closeCode = static_cast<nsresult>(reason);
|
||
|
||
// caller holds a ref to param/trans on stack
|
||
nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
|
||
|
||
//
|
||
// if the transaction owns a connection and the transaction is not done,
|
||
// then ask the connection to close the transaction. otherwise, close the
|
||
// transaction directly (removing it from the pending queue first).
|
||
//
|
||
RefPtr<nsAHttpConnection> conn(trans->Connection());
|
||
if (conn && !trans->IsDone()) {
|
||
conn->CloseTransaction(trans, closeCode);
|
||
} else {
|
||
ConnectionEntry* ent = nullptr;
|
||
if (trans->ConnectionInfo()) {
|
||
ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
|
||
}
|
||
if (ent && ent->RemoveTransFromPendingQ(trans)) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
|
||
" removed from pending queue\n",
|
||
trans));
|
||
}
|
||
|
||
trans->Close(closeCode);
|
||
|
||
// Cancel is a pretty strong signal that things might be hanging
|
||
// so we want to cancel any null transactions related to this connection
|
||
// entry. They are just optimizations, but they aren't hooked up to
|
||
// anything that might get canceled from the rest of gecko, so best
|
||
// to assume that's what was meant by the cancel we did receive if
|
||
// it only applied to something in the queue.
|
||
if (ent) {
|
||
ent->CloseAllActiveConnsWithNullTransactcion(closeCode);
|
||
}
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
|
||
|
||
if (!ci) {
|
||
LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
|
||
// Try and dispatch everything
|
||
for (const auto& entry : mCT.Values()) {
|
||
(void)ProcessPendingQForEntry(entry.get(), true);
|
||
}
|
||
return;
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
|
||
ci->HashKey().get()));
|
||
|
||
// start by processing the queue identified by the given connection info.
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (!(ent && ProcessPendingQForEntry(ent, false))) {
|
||
// if we reach here, it means that we couldn't dispatch a transaction
|
||
// for the specified connection info. walk the connection table...
|
||
for (const auto& entry : mCT.Values()) {
|
||
if (ProcessPendingQForEntry(entry.get(), false)) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci,
|
||
nsresult code) {
|
||
LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get()));
|
||
|
||
int32_t intReason = static_cast<int32_t>(code);
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason,
|
||
ci);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code,
|
||
ARefBase* param) {
|
||
nsresult reason = static_cast<nsresult>(code);
|
||
nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
|
||
ci->HashKey().get(), ent));
|
||
if (ent) {
|
||
ent->CancelAllTransactions(reason);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase*) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
|
||
|
||
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
|
||
mTimeOfNextWakeUp = UINT64_MAX;
|
||
|
||
// Prune dead connections from entries that have idle or active connections.
|
||
// Empty entries are always removed regardless of table size.
|
||
bool shouldPrune =
|
||
mNumIdleConns ||
|
||
(mNumActiveConns && StaticPrefs::network_http_http2_enabled());
|
||
|
||
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
|
||
RefPtr<ConnectionEntry> ent = iter.Data();
|
||
|
||
LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
|
||
|
||
if (shouldPrune) {
|
||
// Find out how long it will take for next idle connection to not
|
||
// be reusable anymore.
|
||
uint32_t timeToNextExpire = ent->PruneDeadConnections();
|
||
|
||
// If time to next expire found is shorter than time to next
|
||
// wake-up, we need to change the time for next wake-up.
|
||
if (timeToNextExpire != UINT32_MAX) {
|
||
uint32_t now = NowInSeconds();
|
||
uint64_t timeOfNextExpire = now + timeToNextExpire;
|
||
// If pruning of dead connections is not already scheduled to
|
||
// happen or time found for next connection to expire is is
|
||
// before mTimeOfNextWakeUp, we need to schedule the pruning to
|
||
// happen after timeToNextExpire.
|
||
if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
|
||
PruneDeadConnectionsAfter(timeToNextExpire);
|
||
}
|
||
} else {
|
||
ConditionallyStopPruneDeadConnectionsTimer();
|
||
}
|
||
}
|
||
|
||
ent->RemoveEmptyPendingQ();
|
||
|
||
if (ent->IsEmpty()) {
|
||
LOG((" removing empty connection entry\n"));
|
||
mPendingQEntries.Remove(ent.get());
|
||
iter.Remove();
|
||
continue;
|
||
}
|
||
|
||
if (shouldPrune) {
|
||
ent->Compact();
|
||
}
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase*) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
|
||
|
||
// Prune connections without traffic
|
||
for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
|
||
// Close the connections with no registered traffic.
|
||
ent->PruneNoTraffic();
|
||
}
|
||
|
||
mPruningNoTraffic = false; // not pruning anymore
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase*) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
|
||
|
||
if (mPruningNoTraffic) {
|
||
// Called in the time gap when the timeout to prune notraffic
|
||
// connections has triggered but the pruning hasn't happened yet.
|
||
return;
|
||
}
|
||
|
||
mCoalescingHash.Clear();
|
||
|
||
// Mark connections for traffic verification
|
||
for (const auto& entry : mCT.Values()) {
|
||
entry->ResetIPFamilyPreference();
|
||
entry->VerifyTraffic();
|
||
}
|
||
|
||
// If the timer is already there. we just re-init it
|
||
if (!mTrafficTimer) {
|
||
mTrafficTimer = NS_NewTimer();
|
||
}
|
||
|
||
// failure to create a timer is not a fatal error, but dead
|
||
// connections will not be cleaned up as nicely
|
||
if (mTrafficTimer) {
|
||
// Give active connections time to get more traffic before killing
|
||
// them off. Default: 5000 milliseconds
|
||
mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
|
||
nsITimer::TYPE_ONE_SHOT);
|
||
} else {
|
||
NS_WARNING("failed to create timer for VerifyTraffic!");
|
||
}
|
||
// Calling ActivateTimeoutTick to ensure the next timeout tick is 1s.
|
||
ActivateTimeoutTick();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
|
||
ARefBase* param) {
|
||
LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
mCoalescingHash.Clear();
|
||
|
||
nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
|
||
|
||
bool preserveTRR = StaticPrefs::network_trr_preserve_on_background();
|
||
for (const auto& entry : mCT.Values()) {
|
||
if (preserveTRR && entry->mConnInfo->GetIsTrrServiceChannel()) {
|
||
continue;
|
||
}
|
||
entry->ClosePersistentConnections();
|
||
}
|
||
|
||
if (ci) ResetIPFamilyPreference(ci);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup(int32_t,
|
||
ARefBase* param) {
|
||
LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n"));
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
|
||
|
||
if (!ci) {
|
||
return;
|
||
}
|
||
|
||
ConnectionEntry* entry = mCT.GetWeak(ci->HashKey());
|
||
if (entry) {
|
||
entry->ClosePersistentConnections();
|
||
}
|
||
|
||
ResetIPFamilyPreference(ci);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase* conn) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
//
|
||
// 1) remove the connection from the active list
|
||
// 2) if keep-alive, add connection to idle list
|
||
// 3) post event to process the pending transaction queue
|
||
//
|
||
|
||
MOZ_ASSERT(conn);
|
||
ConnectionEntry* ent = conn->ConnectionInfo()
|
||
? mCT.GetWeak(conn->ConnectionInfo()->HashKey())
|
||
: nullptr;
|
||
|
||
if (!ent) {
|
||
// this can happen if the connection is made outside of the
|
||
// connection manager and is being "reclaimed" for use with
|
||
// future transactions. HTTP/2 tunnels work like this.
|
||
bool isWildcard = false;
|
||
ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false, false,
|
||
&isWildcard);
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
|
||
"forced new hash entry %s\n",
|
||
conn, conn->ConnectionInfo()->HashKey().get()));
|
||
}
|
||
|
||
MOZ_ASSERT(ent);
|
||
RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
|
||
|
||
LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent,
|
||
conn));
|
||
|
||
// If the connection is in the active list, remove that entry
|
||
// and the reference held by the mActiveConns list.
|
||
// This is never the final reference on conn as the event context
|
||
// is also holding one that is released at the end of this function.
|
||
|
||
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
||
if (!connTCP || connTCP->EverUsedSpdy()) {
|
||
// Spdyand Http3 connections aren't reused in the traditional HTTP way in
|
||
// the idleconns list, they are actively multplexed as active
|
||
// conns. Even when they have 0 transactions on them they are
|
||
// considered active connections. So when one is reclaimed it
|
||
// is really complete and is meant to be shut down and not
|
||
// reused.
|
||
conn->DontReuse();
|
||
}
|
||
|
||
// a connection that still holds a reference to a transaction was
|
||
// not closed naturally (i.e. it was reset or aborted) and is
|
||
// therefore not something that should be reused.
|
||
if (conn->Transaction()) {
|
||
conn->DontReuse();
|
||
}
|
||
|
||
if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn)) ||
|
||
NS_SUCCEEDED(ent->RemovePendingConnection(conn))) {
|
||
} else {
|
||
LOG(
|
||
("HttpConnectionBase %p not found in its connection entry, try "
|
||
"OwnerEntry",
|
||
conn));
|
||
RefPtr<ConnectionEntry> entry = conn->OwnerEntry();
|
||
if (entry) {
|
||
entry->RemoveActiveConnection(conn);
|
||
}
|
||
}
|
||
|
||
MOZ_ASSERT(conn->OwnerEntry() == nullptr);
|
||
|
||
if (connTCP && connTCP->CanReuse()) {
|
||
LOG((" adding connection to idle list\n"));
|
||
// Keep The idle connection list sorted with the connections that
|
||
// have moved the largest data pipelines at the front because these
|
||
// connections have the largest cwnds on the server.
|
||
|
||
// The linear search is ok here because the number of idleconns
|
||
// in a single entry is generally limited to a small number (i.e. 6)
|
||
|
||
ent->InsertIntoIdleConnections(connTCP);
|
||
} else {
|
||
if (ent->IsInExtendedCONNECTConns(conn)) {
|
||
ent->RemoveExtendedCONNECTConns(conn);
|
||
}
|
||
LOG((" connection cannot be reused; closing connection\n"));
|
||
conn->SetCloseReason(ConnectionCloseReason::CANT_REUSED);
|
||
conn->Close(NS_ERROR_ABORT);
|
||
}
|
||
|
||
OnMsgProcessPendingQ(0, ci);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
nsresult rv = NS_OK;
|
||
nsCompleteUpgradeData* data = static_cast<nsCompleteUpgradeData*>(param);
|
||
MOZ_ASSERT(data->mTrans && data->mTrans->Caps() & NS_HTTP_STICKY_CONNECTION);
|
||
|
||
RefPtr<nsAHttpConnection> conn(data->mTrans->Connection());
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
|
||
"conn=%p listener=%p wrapped=%d\n",
|
||
conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
|
||
|
||
if (!conn) {
|
||
// Delay any error reporting to happen in transportAvailableFunc
|
||
rv = NS_ERROR_UNEXPECTED;
|
||
} else {
|
||
MOZ_ASSERT(!data->mSocketTransport);
|
||
rv = conn->TakeTransport(getter_AddRefs(data->mSocketTransport),
|
||
getter_AddRefs(data->mSocketIn),
|
||
getter_AddRefs(data->mSocketOut));
|
||
|
||
if (NS_FAILED(rv)) {
|
||
LOG((" conn->TakeTransport failed with %" PRIx32,
|
||
static_cast<uint32_t>(rv)));
|
||
}
|
||
}
|
||
|
||
RefPtr<nsCompleteUpgradeData> upgradeData(data);
|
||
|
||
nsCOMPtr<nsIAsyncInputStream> socketIn;
|
||
nsCOMPtr<nsIAsyncOutputStream> socketOut;
|
||
|
||
// If this is for JS, the input and output sockets need to be piped over the
|
||
// socket thread. Otherwise, the JS may attempt to read and/or write the
|
||
// sockets on the main thread, which could cause network I/O on the main
|
||
// thread. This is particularly bad in the case of TLS connections, because
|
||
// PSM and NSS rely on those connections only being used on the socket
|
||
// thread.
|
||
if (data->mJsWrapped) {
|
||
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
||
uint32_t segsize = 0;
|
||
uint32_t segcount = 0;
|
||
net_ResolveSegmentParams(segsize, segcount);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(socketOut), true, true,
|
||
segsize, segcount);
|
||
rv = NS_AsyncCopy(pipeIn, data->mSocketOut, gSocketTransportService,
|
||
NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
|
||
}
|
||
|
||
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
||
if (NS_SUCCEEDED(rv)) {
|
||
NS_NewPipe2(getter_AddRefs(socketIn), getter_AddRefs(pipeOut), true, true,
|
||
segsize, segcount);
|
||
rv = NS_AsyncCopy(data->mSocketIn, pipeOut, gSocketTransportService,
|
||
NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
|
||
}
|
||
} else {
|
||
socketIn = upgradeData->mSocketIn;
|
||
socketOut = upgradeData->mSocketOut;
|
||
}
|
||
|
||
auto transportAvailableFunc = [upgradeData{std::move(upgradeData)}, socketIn,
|
||
socketOut, aRv(rv)]() {
|
||
// Handle any potential previous errors first
|
||
// and call OnUpgradeFailed if necessary.
|
||
nsresult rv = aRv;
|
||
|
||
if (NS_FAILED(rv)) {
|
||
rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
|
||
" listener=%p\n",
|
||
upgradeData->mUpgradeListener.get()));
|
||
}
|
||
return;
|
||
}
|
||
|
||
rv = upgradeData->mUpgradeListener->OnTransportAvailable(
|
||
upgradeData->mSocketTransport, socketIn, socketOut);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
|
||
"failed. listener=%p\n",
|
||
upgradeData->mUpgradeListener.get()));
|
||
}
|
||
};
|
||
|
||
if (data->mJsWrapped) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
|
||
"conn=%p listener=%p wrapped=%d pass to main thread\n",
|
||
conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
|
||
NS_DispatchToMainThread(
|
||
NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade",
|
||
transportAvailableFunc));
|
||
} else {
|
||
transportAvailableFunc();
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase*) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
uint32_t param = static_cast<uint32_t>(inParam);
|
||
uint16_t name = ((param) & 0xFFFF0000) >> 16;
|
||
uint16_t value = param & 0x0000FFFF;
|
||
|
||
switch (name) {
|
||
case MAX_CONNECTIONS:
|
||
mMaxConns = value;
|
||
break;
|
||
case MAX_URGENT_START_Q:
|
||
mMaxUrgentExcessiveConns = value;
|
||
break;
|
||
case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
|
||
mMaxPersistConnsPerHost = value;
|
||
break;
|
||
case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
|
||
mMaxPersistConnsPerProxy = value;
|
||
break;
|
||
case MAX_REQUEST_DELAY:
|
||
mMaxRequestDelay = value;
|
||
break;
|
||
case THROTTLING_ENABLED:
|
||
SetThrottlingEnabled(!!value);
|
||
break;
|
||
case THROTTLING_SUSPEND_FOR:
|
||
mThrottleSuspendFor = value;
|
||
break;
|
||
case THROTTLING_RESUME_FOR:
|
||
mThrottleResumeFor = value;
|
||
break;
|
||
case THROTTLING_HOLD_TIME:
|
||
mThrottleHoldTime = value;
|
||
break;
|
||
case THROTTLING_MAX_TIME:
|
||
mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
|
||
break;
|
||
case PROXY_BE_CONSERVATIVE:
|
||
mBeConservativeForProxy = !!value;
|
||
break;
|
||
default:
|
||
MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
|
||
}
|
||
}
|
||
|
||
// Read Timeout Tick handlers
|
||
|
||
void nsHttpConnectionMgr::ActivateTimeoutTick() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
LOG(
|
||
("nsHttpConnectionMgr::ActivateTimeoutTick() "
|
||
"this=%p mTimeoutTick=%p\n",
|
||
this, mTimeoutTick.get()));
|
||
|
||
// The timer tick should be enabled if it is not already pending.
|
||
// Upon running the tick will rearm itself if there are active
|
||
// connections available.
|
||
|
||
if (mTimeoutTick && mTimeoutTickArmed) {
|
||
// make sure we get one iteration on a quick tick
|
||
if (mTimeoutTickNext > 1) {
|
||
mTimeoutTickNext = 1;
|
||
mTimeoutTick->SetDelay(1000);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!mTimeoutTick) {
|
||
mTimeoutTick = NS_NewTimer();
|
||
if (!mTimeoutTick) {
|
||
NS_WARNING("failed to create timer for http timeout management");
|
||
return;
|
||
}
|
||
nsCOMPtr<nsIEventTarget> target;
|
||
{
|
||
auto lock = mSocketThreadTarget.Lock();
|
||
target = *lock;
|
||
}
|
||
if (!target) {
|
||
NS_WARNING("cannot activate timout if not initialized or shutdown");
|
||
return;
|
||
}
|
||
mTimeoutTick->SetTarget(target);
|
||
}
|
||
|
||
if (mIsShuttingDown) { // Atomic
|
||
// don't set a timer to generate an event if we're shutting down
|
||
// (and mSocketThreadTarget might be null or garbage if we're shutting down)
|
||
return;
|
||
}
|
||
MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
|
||
mTimeoutTickArmed = true;
|
||
mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
|
||
}
|
||
|
||
class UINT64Wrapper : public ARefBase {
|
||
public:
|
||
explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
|
||
|
||
uint64_t GetValue() { return mUint64; }
|
||
|
||
private:
|
||
uint64_t mUint64;
|
||
virtual ~UINT64Wrapper() = default;
|
||
};
|
||
|
||
nsresult nsHttpConnectionMgr::UpdateCurrentBrowserId(uint64_t aId) {
|
||
RefPtr<UINT64Wrapper> idWrapper = new UINT64Wrapper(aId);
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId, 0,
|
||
idWrapper);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) {
|
||
LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
mThrottleEnabled = aEnable;
|
||
|
||
if (mThrottleEnabled) {
|
||
EnsureThrottleTickerIfNeeded();
|
||
} else {
|
||
DestroyThrottleTicker();
|
||
ResumeReadOf(mActiveTransactions[false]);
|
||
ResumeReadOf(mActiveTransactions[true]);
|
||
}
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::InThrottlingTimeWindow() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
if (mThrottlingWindowEndsAt.IsNull()) {
|
||
return true;
|
||
}
|
||
return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker) {
|
||
LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
|
||
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
|
||
|
||
if (!mThrottleTicker && MOZ_LIKELY(aEnsureTicker) &&
|
||
MOZ_LIKELY(mThrottleEnabled)) {
|
||
EnsureThrottleTickerIfNeeded();
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::LogActiveTransactions(char operation) {
|
||
if (!LOG_ENABLED()) {
|
||
return;
|
||
}
|
||
|
||
nsTArray<RefPtr<nsHttpTransaction>>* trs = nullptr;
|
||
uint32_t au, at, bu = 0, bt = 0;
|
||
|
||
trs = mActiveTransactions[false].Get(mCurrentBrowserId);
|
||
au = trs ? trs->Length() : 0;
|
||
trs = mActiveTransactions[true].Get(mCurrentBrowserId);
|
||
at = trs ? trs->Length() : 0;
|
||
|
||
for (const auto& data : mActiveTransactions[false].Values()) {
|
||
bu += data->Length();
|
||
}
|
||
bu -= au;
|
||
for (const auto& data : mActiveTransactions[true].Values()) {
|
||
bt += data->Length();
|
||
}
|
||
bt -= at;
|
||
|
||
// Shows counts of:
|
||
// - unthrottled transaction for the active tab
|
||
// - throttled transaction for the active tab
|
||
// - unthrottled transaction for background tabs
|
||
// - throttled transaction for background tabs
|
||
LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
|
||
}
|
||
|
||
void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction* aTrans) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
uint64_t tabId = aTrans->BrowserId();
|
||
bool throttled = aTrans->EligibleForThrottling();
|
||
|
||
nsTArray<RefPtr<nsHttpTransaction>>* transactions =
|
||
mActiveTransactions[throttled].GetOrInsertNew(tabId);
|
||
|
||
MOZ_ASSERT(!transactions->Contains(aTrans));
|
||
|
||
transactions->AppendElement(aTrans);
|
||
|
||
LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64
|
||
"(%d) thr=%d",
|
||
aTrans, tabId, tabId == mCurrentBrowserId, throttled));
|
||
LogActiveTransactions('+');
|
||
|
||
if (tabId == mCurrentBrowserId) {
|
||
mActiveTabTransactionsExist = true;
|
||
if (!throttled) {
|
||
mActiveTabUnthrottledTransactionsExist = true;
|
||
}
|
||
}
|
||
|
||
// Shift the throttling window to the future (actually, makes sure
|
||
// that throttling will engage when there is anything to throttle.)
|
||
// The |false| argument means we don't need this call to ensure
|
||
// the ticker, since we do it just below. Calling
|
||
// EnsureThrottleTickerIfNeeded directly does a bit more than call
|
||
// from inside of TouchThrottlingTimeWindow.
|
||
TouchThrottlingTimeWindow(false);
|
||
|
||
if (!mThrottleEnabled) {
|
||
return;
|
||
}
|
||
|
||
EnsureThrottleTickerIfNeeded();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::RemoveActiveTransaction(
|
||
nsHttpTransaction* aTrans, Maybe<bool> const& aOverride) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
uint64_t tabId = aTrans->BrowserId();
|
||
bool forActiveTab = tabId == mCurrentBrowserId;
|
||
bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling());
|
||
|
||
nsTArray<RefPtr<nsHttpTransaction>>* transactions =
|
||
mActiveTransactions[throttled].Get(tabId);
|
||
|
||
if (!transactions || !transactions->RemoveElement(aTrans)) {
|
||
// Was not tracked as active, probably just ignore.
|
||
return;
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
|
||
"(%d) thr=%d",
|
||
aTrans, tabId, forActiveTab, throttled));
|
||
|
||
if (!transactions->IsEmpty()) {
|
||
// There are still transactions of the type, hence nothing in the throttling
|
||
// conditions has changed and we don't need to update "Exists" caches nor we
|
||
// need to wake any now throttled transactions.
|
||
LogActiveTransactions('-');
|
||
return;
|
||
}
|
||
|
||
// To optimize the following logic, always remove the entry when the array is
|
||
// empty.
|
||
mActiveTransactions[throttled].Remove(tabId);
|
||
LogActiveTransactions('-');
|
||
|
||
if (forActiveTab) {
|
||
// Update caches of the active tab transaction existence, since it's now
|
||
// affected
|
||
if (!throttled) {
|
||
mActiveTabUnthrottledTransactionsExist = false;
|
||
}
|
||
if (mActiveTabTransactionsExist) {
|
||
mActiveTabTransactionsExist =
|
||
mActiveTransactions[!throttled].Contains(tabId);
|
||
}
|
||
}
|
||
|
||
if (!mThrottleEnabled) {
|
||
return;
|
||
}
|
||
|
||
bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
|
||
bool throttledExist = !mActiveTransactions[true].IsEmpty();
|
||
|
||
if (!unthrottledExist && !throttledExist) {
|
||
// Nothing active globally, just get rid of the timer completely and we are
|
||
// done.
|
||
MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
|
||
MOZ_ASSERT(!mActiveTabTransactionsExist);
|
||
|
||
DestroyThrottleTicker();
|
||
return;
|
||
}
|
||
|
||
if (!mThrottlingInhibitsReading) {
|
||
// There is then nothing to wake up. Affected transactions will not be
|
||
// put to sleep automatically on next tick.
|
||
LOG((" reading not currently inhibited"));
|
||
return;
|
||
}
|
||
|
||
if (mActiveTabUnthrottledTransactionsExist) {
|
||
// There are still unthrottled transactions for the active tab, hence the
|
||
// state is unaffected and we don't need to do anything (nothing to wake).
|
||
LOG((" there are unthrottled for the active tab"));
|
||
return;
|
||
}
|
||
|
||
if (mActiveTabTransactionsExist) {
|
||
// There are only trottled transactions for the active tab.
|
||
// If the last transaction we just removed was a non-throttled for the
|
||
// active tab we can wake the throttled transactions for the active tab.
|
||
if (forActiveTab && !throttled) {
|
||
LOG((" resuming throttled for active tab"));
|
||
ResumeReadOf(mActiveTransactions[true].Get(mCurrentBrowserId));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!unthrottledExist) {
|
||
// There are no unthrottled transactions for any tab. Resume all throttled,
|
||
// all are only for background tabs.
|
||
LOG((" delay resuming throttled for background tabs"));
|
||
DelayedResumeBackgroundThrottledTransactions();
|
||
return;
|
||
}
|
||
|
||
if (forActiveTab) {
|
||
// Removing the last transaction for the active tab frees up the unthrottled
|
||
// background tabs transactions.
|
||
LOG((" delay resuming unthrottled for background tabs"));
|
||
DelayedResumeBackgroundThrottledTransactions();
|
||
return;
|
||
}
|
||
|
||
LOG((" not resuming anything"));
|
||
}
|
||
|
||
void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction* aTrans) {
|
||
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
|
||
|
||
// First remove then add. In case of a download that is the only active
|
||
// transaction and has just been marked as download (goes unthrottled to
|
||
// throttled), adding first would cause it to be throttled for first few
|
||
// milliseconds - becuause it would appear as if there were both throttled
|
||
// and unthrottled transactions at the time.
|
||
|
||
Maybe<bool> reversed;
|
||
reversed.emplace(!aTrans->EligibleForThrottling());
|
||
RemoveActiveTransaction(aTrans, reversed);
|
||
|
||
AddActiveTransaction(aTrans);
|
||
|
||
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction* aTrans) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
|
||
|
||
if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
|
||
return false;
|
||
}
|
||
|
||
uint64_t tabId = aTrans->BrowserId();
|
||
bool forActiveTab = tabId == mCurrentBrowserId;
|
||
bool throttled = aTrans->EligibleForThrottling();
|
||
|
||
bool stop = [&]() {
|
||
if (mActiveTabTransactionsExist) {
|
||
if (!tabId) {
|
||
// Chrome initiated and unidentified transactions just respect
|
||
// their throttle flag, when something for the active tab is happening.
|
||
// This also includes downloads.
|
||
LOG((" active tab loads, trans is tab-less, throttled=%d", throttled));
|
||
return throttled;
|
||
}
|
||
if (!forActiveTab) {
|
||
// This is a background tab request, we want them to always throttle
|
||
// when there are transactions running for the ative tab.
|
||
LOG((" active tab loads, trans not of the active tab"));
|
||
return true;
|
||
}
|
||
|
||
if (mActiveTabUnthrottledTransactionsExist) {
|
||
// Unthrottled transactions for the active tab take precedence
|
||
LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
|
||
return throttled;
|
||
}
|
||
|
||
LOG((" trans for active tab, don't throttle"));
|
||
return false;
|
||
}
|
||
|
||
MOZ_ASSERT(!forActiveTab);
|
||
|
||
if (!mActiveTransactions[false].IsEmpty()) {
|
||
// This means there are unthrottled active transactions for background
|
||
// tabs. If we are here, there can't be any transactions for the active
|
||
// tab. (If there is no transaction for a tab id, there is no entry for it
|
||
// in the hashtable.)
|
||
LOG((" backround tab(s) load unthrottled, trans throttled=%d",
|
||
throttled));
|
||
return throttled;
|
||
}
|
||
|
||
// There are only unthrottled transactions for background tabs: don't
|
||
// throttle.
|
||
LOG((" backround tab(s) load throttled, don't throttle"));
|
||
return false;
|
||
}();
|
||
|
||
if (forActiveTab && !stop) {
|
||
// This is an active-tab transaction and is allowed to read. Hence,
|
||
// prolong the throttle time window to make sure all 'lower-decks'
|
||
// transactions will actually throttle.
|
||
TouchThrottlingTimeWindow();
|
||
return false;
|
||
}
|
||
|
||
// Only stop reading when in the configured throttle max-time (aka time
|
||
// window). This window is prolonged (restarted) by a call to
|
||
// TouchThrottlingTimeWindow called on new transaction activation or on
|
||
// receive of response bytes of an active tab transaction.
|
||
bool inWindow = InThrottlingTimeWindow();
|
||
|
||
LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d", stop, inWindow,
|
||
!!mDelayedResumeReadTimer));
|
||
|
||
if (!forActiveTab) {
|
||
// If the delayed background resume timer exists, background transactions
|
||
// are scheduled to be woken after a delay, hence leave them throttled.
|
||
inWindow = inWindow || mDelayedResumeReadTimer;
|
||
}
|
||
|
||
return stop && inWindow;
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::IsConnEntryUnderPressure(
|
||
nsHttpConnectionInfo* connInfo) {
|
||
ConnectionEntry* ent = mCT.GetWeak(connInfo->HashKey());
|
||
if (!ent) {
|
||
// No entry, no pressure.
|
||
return false;
|
||
}
|
||
|
||
return ent->PendingQueueLengthForWindow(mCurrentBrowserId) > 0;
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::IsThrottleTickerNeeded() {
|
||
LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
|
||
|
||
if (mActiveTabUnthrottledTransactionsExist &&
|
||
mActiveTransactions[false].Count() > 1) {
|
||
LOG((" there are unthrottled transactions for both active and bck"));
|
||
return true;
|
||
}
|
||
|
||
if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) {
|
||
LOG((" there are throttled transactions for both active and bck"));
|
||
return true;
|
||
}
|
||
|
||
if (!mActiveTransactions[true].IsEmpty() &&
|
||
!mActiveTransactions[false].IsEmpty()) {
|
||
LOG((" there are both throttled and unthrottled transactions"));
|
||
return true;
|
||
}
|
||
|
||
LOG((" nothing to throttle"));
|
||
return false;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
|
||
if (!IsThrottleTickerNeeded()) {
|
||
return;
|
||
}
|
||
|
||
// There is a new demand to throttle, hence unschedule delayed resume
|
||
// of background throttled transastions.
|
||
CancelDelayedResumeBackgroundThrottledTransactions();
|
||
|
||
if (mThrottleTicker) {
|
||
return;
|
||
}
|
||
|
||
mThrottleTicker = NS_NewTimer();
|
||
if (mThrottleTicker) {
|
||
MOZ_ASSERT(!mThrottlingInhibitsReading);
|
||
|
||
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
|
||
mThrottlingInhibitsReading = true;
|
||
}
|
||
|
||
LogActiveTransactions('^');
|
||
}
|
||
|
||
// Can be called with or without the monitor held
|
||
void nsHttpConnectionMgr::DestroyThrottleTicker() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
// Nothing to throttle, hence no need for this timer anymore.
|
||
CancelDelayedResumeBackgroundThrottledTransactions();
|
||
|
||
MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
|
||
|
||
if (!mThrottleTicker) {
|
||
return;
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
|
||
mThrottleTicker->Cancel();
|
||
mThrottleTicker = nullptr;
|
||
|
||
mThrottlingInhibitsReading = false;
|
||
|
||
LogActiveTransactions('v');
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ThrottlerTick() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
|
||
|
||
LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d",
|
||
mThrottlingInhibitsReading));
|
||
|
||
// If there are only background transactions to be woken after a delay, keep
|
||
// the ticker so that we woke them only for the resume-for interval and then
|
||
// throttle them again until the background-resume delay passes.
|
||
if (!mThrottlingInhibitsReading && !mDelayedResumeReadTimer &&
|
||
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
|
||
LOG((" last tick"));
|
||
mThrottleTicker = nullptr;
|
||
}
|
||
|
||
if (mThrottlingInhibitsReading) {
|
||
if (mThrottleTicker) {
|
||
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
|
||
}
|
||
} else {
|
||
if (mThrottleTicker) {
|
||
mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
|
||
}
|
||
|
||
ResumeReadOf(mActiveTransactions[false], true);
|
||
ResumeReadOf(mActiveTransactions[true]);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
if (mDelayedResumeReadTimer) {
|
||
return;
|
||
}
|
||
|
||
LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
|
||
NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this,
|
||
mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
|
||
if (!mDelayedResumeReadTimer) {
|
||
return;
|
||
}
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::"
|
||
"CancelDelayedResumeBackgroundThrottledTransactions"));
|
||
mDelayedResumeReadTimer->Cancel();
|
||
mDelayedResumeReadTimer = nullptr;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
|
||
mDelayedResumeReadTimer = nullptr;
|
||
|
||
if (!IsThrottleTickerNeeded()) {
|
||
DestroyThrottleTicker();
|
||
}
|
||
|
||
if (!mActiveTransactions[false].IsEmpty()) {
|
||
ResumeReadOf(mActiveTransactions[false], true);
|
||
} else {
|
||
ResumeReadOf(mActiveTransactions[true], true);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ResumeReadOf(
|
||
nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&
|
||
hashtable,
|
||
bool excludeForActiveTab) {
|
||
for (const auto& entry : hashtable) {
|
||
if (excludeForActiveTab && entry.GetKey() == mCurrentBrowserId) {
|
||
// These have never been throttled (never stopped reading)
|
||
continue;
|
||
}
|
||
ResumeReadOf(entry.GetWeak());
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ResumeReadOf(
|
||
nsTArray<RefPtr<nsHttpTransaction>>* transactions) {
|
||
MOZ_ASSERT(transactions);
|
||
|
||
for (const auto& trans : *transactions) {
|
||
trans->ResumeReading();
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::NotifyConnectionOfBrowserIdChange(
|
||
uint64_t previousId) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
|
||
nsTArray<RefPtr<nsAHttpConnection>> connections;
|
||
|
||
auto addConnectionHelper =
|
||
[&connections](nsTArray<RefPtr<nsHttpTransaction>>* trans) {
|
||
if (!trans) {
|
||
return;
|
||
}
|
||
|
||
for (const auto& t : *trans) {
|
||
RefPtr<nsAHttpConnection> conn = t->Connection();
|
||
if (conn && !connections.Contains(conn)) {
|
||
connections.AppendElement(conn);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Get unthrottled transactions with the previous and current window id.
|
||
transactions = mActiveTransactions[false].Get(previousId);
|
||
addConnectionHelper(transactions);
|
||
transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
|
||
addConnectionHelper(transactions);
|
||
|
||
// Get throttled transactions with the previous and current window id.
|
||
transactions = mActiveTransactions[true].Get(previousId);
|
||
addConnectionHelper(transactions);
|
||
transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
|
||
addConnectionHelper(transactions);
|
||
|
||
for (const auto& conn : connections) {
|
||
conn->CurrentBrowserIdChanged(mCurrentBrowserId);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId(int32_t aLoading,
|
||
ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
uint64_t id = static_cast<UINT64Wrapper*>(param)->GetValue();
|
||
|
||
if (mCurrentBrowserId == id) {
|
||
// duplicate notification
|
||
return;
|
||
}
|
||
|
||
bool activeTabWasLoading = mActiveTabTransactionsExist;
|
||
|
||
uint64_t previousId = mCurrentBrowserId;
|
||
mCurrentBrowserId = id;
|
||
|
||
if (StaticPrefs::network_http_active_tab_priority()) {
|
||
NotifyConnectionOfBrowserIdChange(previousId);
|
||
}
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId"
|
||
" id=%" PRIx64 "\n",
|
||
mCurrentBrowserId));
|
||
|
||
nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
|
||
|
||
// Update the "Exists" caches and resume any transactions that now deserve it,
|
||
// changing the active tab changes the conditions for throttling.
|
||
transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
|
||
mActiveTabUnthrottledTransactionsExist = !!transactions;
|
||
|
||
if (!mActiveTabUnthrottledTransactionsExist) {
|
||
transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
|
||
}
|
||
mActiveTabTransactionsExist = !!transactions;
|
||
|
||
if (transactions) {
|
||
// This means there are some transactions for this newly activated tab,
|
||
// resume them but anything else.
|
||
LOG((" resuming newly activated tab transactions"));
|
||
ResumeReadOf(transactions);
|
||
return;
|
||
}
|
||
|
||
if (!activeTabWasLoading) {
|
||
// There were no transactions for the previously active tab, hence
|
||
// all remaning transactions, if there were, were all unthrottled,
|
||
// no need to wake them.
|
||
return;
|
||
}
|
||
|
||
if (!mActiveTransactions[false].IsEmpty()) {
|
||
LOG((" resuming unthrottled background transactions"));
|
||
ResumeReadOf(mActiveTransactions[false]);
|
||
return;
|
||
}
|
||
|
||
if (!mActiveTransactions[true].IsEmpty()) {
|
||
LOG((" resuming throttled background transactions"));
|
||
ResumeReadOf(mActiveTransactions[true]);
|
||
return;
|
||
}
|
||
|
||
DestroyThrottleTicker();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::TimeoutTick() {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
|
||
|
||
LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
|
||
// The next tick will be between 1 second and 1 hr
|
||
// Set it to the max value here, and the TimeoutTick()s can
|
||
// reduce it to their local needs.
|
||
mTimeoutTickNext = 3600; // 1hr
|
||
|
||
for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
|
||
uint32_t timeoutTickNext = ent->TimeoutTick();
|
||
mTimeoutTickNext = std::min(mTimeoutTickNext, timeoutTickNext);
|
||
}
|
||
|
||
if (mTimeoutTick) {
|
||
mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
|
||
mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
|
||
}
|
||
}
|
||
|
||
// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
|
||
// dispatching a transaction according to these rules
|
||
// 1] use an ent that matches the ci that can be dispatched immediately
|
||
// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
|
||
// 3] otherwise create an ent that matches ci and make new conn on it
|
||
|
||
ConnectionEntry* nsHttpConnectionMgr::GetOrCreateConnectionEntry(
|
||
nsHttpConnectionInfo* specificCI, bool prohibitWildCard, bool aNoHttp2,
|
||
bool aNoHttp3, bool* aIsWildcard, bool* aAvailableForDispatchNow) {
|
||
if (aAvailableForDispatchNow) {
|
||
*aAvailableForDispatchNow = false;
|
||
}
|
||
*aIsWildcard = false;
|
||
|
||
// step 1
|
||
LOG(("GetOrCreateConnectionEntry step 1"));
|
||
ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
|
||
if (specificEnt && specificEnt->AvailableForDispatchNow()) {
|
||
if (aAvailableForDispatchNow) {
|
||
*aAvailableForDispatchNow = true;
|
||
}
|
||
return specificEnt;
|
||
}
|
||
|
||
// step 1 repeated for an inverted anonymous flag; we return an entry
|
||
// only when it has an h2 established connection that is not authenticated
|
||
// with a client certificate.
|
||
// When GetOrCreateConnectionEntry is called to create a wildcard entry, we
|
||
// should not allow coalescing onto an anonymous entry. Since the anonymous
|
||
// flag is specifically inherited from the origin info in
|
||
// nsHttpConnectionInfo::CreateWildCard, allowing coalescing onto an anonymous
|
||
// entry here results in inconsistency.
|
||
if (!specificCI->IsWildCard()) {
|
||
nsAutoCString anonInvertedKey;
|
||
specificCI->AnonymousInvertedHashKey(anonInvertedKey);
|
||
ConnectionEntry* invertedEnt = mCT.GetWeak(anonInvertedKey);
|
||
if (invertedEnt) {
|
||
HttpConnectionBase* h2orh3conn =
|
||
GetH2orH3ActiveConn(invertedEnt, aNoHttp2, aNoHttp3);
|
||
if (h2orh3conn && h2orh3conn->IsExperienced() &&
|
||
h2orh3conn->NoClientCertAuth()) {
|
||
MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3());
|
||
LOG(
|
||
("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
|
||
"connections, ent=%p",
|
||
invertedEnt));
|
||
if (aAvailableForDispatchNow) {
|
||
*aAvailableForDispatchNow = true;
|
||
}
|
||
return invertedEnt;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!specificCI->UsingHttpsProxy()) {
|
||
prohibitWildCard = true;
|
||
}
|
||
|
||
// step 2
|
||
LOG(("GetOrCreateConnectionEntry step 2 prohibitWildCard=%d, aNoHttp3=%d",
|
||
prohibitWildCard, aNoHttp3));
|
||
if (!prohibitWildCard) {
|
||
RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
|
||
DebugOnly<nsresult> rv =
|
||
specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
ConnectionEntry* wildCardEnt = mCT.GetWeak(wildCardProxyCI->HashKey());
|
||
if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
|
||
if (aAvailableForDispatchNow) {
|
||
*aAvailableForDispatchNow = true;
|
||
}
|
||
*aIsWildcard = true;
|
||
return wildCardEnt;
|
||
}
|
||
}
|
||
|
||
// step 3
|
||
LOG(("GetOrCreateConnectionEntry step 3"));
|
||
if (!specificEnt) {
|
||
RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
|
||
specificEnt = new ConnectionEntry(clone, mPendingQEntries);
|
||
mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
|
||
}
|
||
return specificEnt;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DoSpeculativeConnection(
|
||
SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(aTrans);
|
||
|
||
bool isWildcard = false;
|
||
ConnectionEntry* ent = GetOrCreateConnectionEntry(
|
||
aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
|
||
aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
|
||
if (!aFetchHTTPSRR &&
|
||
nsHttpHandler::EchConfigEnabled(aTrans->ConnectionInfo()->IsHttp3())) {
|
||
// This happens when this is called from
|
||
// SpeculativeTransaction::OnHTTPSRRAvailable. We have to update this
|
||
// entry's echConfig so that the newly created connection can use the latest
|
||
// echConfig.
|
||
ent->MaybeUpdateEchConfig(aTrans->ConnectionInfo());
|
||
}
|
||
DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DoSpeculativeConnectionInternal(
|
||
ConnectionEntry* aEnt, SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(aTrans);
|
||
MOZ_ASSERT(aEnt);
|
||
if (!gHttpHandler->Active()) {
|
||
// Do nothing if we are shutting down.
|
||
return;
|
||
}
|
||
|
||
nsIHttpChannelInternal::ProxyDNSStrategy strategy = GetProxyDNSStrategyHelper(
|
||
aEnt->mConnInfo->ProxyType(), aEnt->mConnInfo->ProxyFlag());
|
||
// Speculative connections can be triggered by non-Necko consumers,
|
||
// so add an extra check to ensure HTTPS RR isn't fetched when a proxy is
|
||
// used.
|
||
if (aFetchHTTPSRR &&
|
||
strategy == nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN &&
|
||
NS_SUCCEEDED(aTrans->FetchHTTPSRR())) {
|
||
// nsHttpConnectionMgr::DoSpeculativeConnection will be called again
|
||
// when HTTPS RR is available.
|
||
return;
|
||
}
|
||
|
||
uint32_t parallelSpeculativeConnectLimit =
|
||
aTrans->ParallelSpeculativeConnectLimit()
|
||
? *aTrans->ParallelSpeculativeConnectLimit()
|
||
: gHttpHandler->ParallelSpeculativeConnectLimit();
|
||
bool ignoreIdle = aTrans->IgnoreIdle() ? *aTrans->IgnoreIdle() : false;
|
||
bool allow1918 = aTrans->Allow1918() ? *aTrans->Allow1918() : false;
|
||
|
||
bool keepAlive = aTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
|
||
if (mNumDnsAndConnectSockets < parallelSpeculativeConnectLimit &&
|
||
((ignoreIdle &&
|
||
(aEnt->IdleConnectionsLength() < parallelSpeculativeConnectLimit)) ||
|
||
!aEnt->IdleConnectionsLength()) &&
|
||
!(keepAlive && aEnt->RestrictConnections()) &&
|
||
!AtActiveConnectionLimit(aEnt, aTrans->Caps())) {
|
||
nsresult rv = aEnt->CreateDnsAndConnectSocket(aTrans, aTrans->Caps(), true,
|
||
false, allow1918, nullptr);
|
||
if (NS_FAILED(rv)) {
|
||
LOG(
|
||
("DoSpeculativeConnectionInternal Transport socket creation "
|
||
"failure: %" PRIx32 "\n",
|
||
static_cast<uint32_t>(rv)));
|
||
}
|
||
} else {
|
||
LOG(
|
||
("DoSpeculativeConnectionInternal Transport ci=%s "
|
||
"not created due to existing connection count:%d",
|
||
aEnt->mConnInfo->HashKey().get(), parallelSpeculativeConnectLimit));
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DoFallbackConnection(SpeculativeTransaction* aTrans,
|
||
bool aFetchHTTPSRR) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(aTrans);
|
||
|
||
LOG(("nsHttpConnectionMgr::DoFallbackConnection"));
|
||
|
||
bool availableForDispatchNow = false;
|
||
bool aIsWildcard = false;
|
||
ConnectionEntry* ent = GetOrCreateConnectionEntry(
|
||
aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
|
||
aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &aIsWildcard,
|
||
&availableForDispatchNow);
|
||
|
||
if (availableForDispatchNow) {
|
||
LOG(
|
||
("nsHttpConnectionMgr::DoFallbackConnection fallback connection is "
|
||
"ready for dispatching ent=%p",
|
||
ent));
|
||
aTrans->InvokeCallback();
|
||
return;
|
||
}
|
||
|
||
DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
SpeculativeConnectArgs* args = static_cast<SpeculativeConnectArgs*>(param);
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s, "
|
||
"mFetchHTTPSRR=%d]\n",
|
||
args->mTrans->ConnectionInfo()->HashKey().get(), args->mFetchHTTPSRR));
|
||
DoSpeculativeConnection(args->mTrans, args->mFetchHTTPSRR);
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo* proxy) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
if (mBeConservativeForProxy) {
|
||
// The pref says to be conservative for proxies.
|
||
return true;
|
||
}
|
||
|
||
if (!proxy) {
|
||
// There is no proxy, so be conservative by default.
|
||
return true;
|
||
}
|
||
|
||
// Be conservative only if there is no proxy host set either.
|
||
// This logic was copied from nsSSLIOLayerAddToSocket.
|
||
nsAutoCString proxyHost;
|
||
proxy->GetHost(proxyHost);
|
||
return proxyHost.IsEmpty();
|
||
}
|
||
|
||
// register a connection to receive CanJoinConnection() for particular
|
||
// origin keys
|
||
void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn,
|
||
const nsACString& host,
|
||
int32_t port) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr;
|
||
if (!ci || !conn->CanDirectlyActivate()) {
|
||
return;
|
||
}
|
||
|
||
HashNumber newKey =
|
||
nsHttpConnectionInfo::BuildOriginFrameHashKey(ci, host, port);
|
||
mCoalescingHash.GetOrInsertNew(newKey, 1)->AppendElement(
|
||
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
|
||
"Established New Coalescing Key %" PRIu32 " to %p %s\n",
|
||
newKey, conn, ci->HashKey().get()));
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams>* aArg) {
|
||
for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
|
||
aArg->AppendElement(ent->GetConnectionData());
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::GetHttp3ConnectionStatsData(
|
||
nsTArray<Http3ConnectionStatsParams>* aArg) {
|
||
for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
|
||
if (ent->mConnInfo->GetPrivate()) {
|
||
continue;
|
||
}
|
||
aArg->AppendElement(ent->GetHttp3ConnectionStatsData());
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (ent) {
|
||
ent->ResetIPFamilyPreference();
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
|
||
LOG(("nsHttpConnectionMgr::ExcludeHttp2 excluding ci %s",
|
||
ci->HashKey().get()));
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (!ent) {
|
||
LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!"));
|
||
return;
|
||
}
|
||
|
||
ent->DisallowHttp2();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
|
||
LOG(("nsHttpConnectionMgr::ExcludeHttp3 exclude ci %s", ci->HashKey().get()));
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (!ent) {
|
||
LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!"));
|
||
return;
|
||
}
|
||
|
||
ent->DontReuseHttp3Conn();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::MoveToWildCardConnEntry(
|
||
nsHttpConnectionInfo* specificCI, nsHttpConnectionInfo* wildCardCI,
|
||
HttpConnectionBase* proxyConn) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
MOZ_ASSERT(specificCI->UsingHttpsProxy());
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
|
||
"change CI from %s to %s\n",
|
||
proxyConn, specificCI->HashKey().get(), wildCardCI->HashKey().get()));
|
||
|
||
ConnectionEntry* ent = mCT.GetWeak(specificCI->HashKey());
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
|
||
"%d, h3=%d)\n",
|
||
proxyConn, ent, ent ? ent->mUsingSpdy : 0,
|
||
ent ? ent->IsHttp3ProxyConnection() : 0));
|
||
|
||
if (!ent || (!ent->mUsingSpdy && !ent->IsHttp3ProxyConnection())) {
|
||
return;
|
||
}
|
||
|
||
bool isWildcard = false;
|
||
ConnectionEntry* wcEnt =
|
||
GetOrCreateConnectionEntry(wildCardCI, true, false, false, &isWildcard);
|
||
if (wcEnt == ent) {
|
||
// nothing to do!
|
||
LOG(("nothing to do "));
|
||
return;
|
||
}
|
||
if (ent->mUsingSpdy) {
|
||
wcEnt->mUsingSpdy = true;
|
||
} else {
|
||
MOZ_ASSERT(wcEnt->IsHttp3ProxyConnection());
|
||
}
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
|
||
"idle=%zu active=%zu half=%zu pending=%zu\n",
|
||
ent, ent->IdleConnectionsLength(), ent->ActiveConnsLength(),
|
||
ent->DnsAndConnectSocketsLength(), ent->PendingQueueLength()));
|
||
|
||
LOG(
|
||
("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
|
||
"idle=%zu active=%zu half=%zu pending=%zu\n",
|
||
wcEnt, wcEnt->IdleConnectionsLength(), wcEnt->ActiveConnsLength(),
|
||
wcEnt->DnsAndConnectSocketsLength(), wcEnt->PendingQueueLength()));
|
||
|
||
ent->MoveConnection(proxyConn, wcEnt);
|
||
// Ensure other wildcard connections are closed gracefully.
|
||
wcEnt->MakeAllDontReuseExcept(proxyConn);
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
|
||
const nsACString& aHashKey) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
LOG(("nsHttpConnectionMgr::RemoveTransFromConnEntry: trans=%p ci=%s", aTrans,
|
||
PromiseFlatCString(aHashKey).get()));
|
||
|
||
if (aHashKey.IsEmpty()) {
|
||
return false;
|
||
}
|
||
|
||
// Step 1: Get the transaction's connection entry.
|
||
ConnectionEntry* entry = mCT.GetWeak(aHashKey);
|
||
if (!entry) {
|
||
return false;
|
||
}
|
||
|
||
// Step 2: Try to find the undispatched transaction.
|
||
return entry->RemoveTransFromPendingQ(aTrans);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::IncreaseNumDnsAndConnectSockets() {
|
||
mNumDnsAndConnectSockets++;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DecreaseNumDnsAndConnectSockets() {
|
||
MOZ_ASSERT(mNumDnsAndConnectSockets);
|
||
if (mNumDnsAndConnectSockets) { // just in case
|
||
mNumDnsAndConnectSockets--;
|
||
}
|
||
}
|
||
|
||
already_AddRefed<PendingTransactionInfo>
|
||
nsHttpConnectionMgr::FindTransactionHelper(bool removeWhenFound,
|
||
ConnectionEntry* aEnt,
|
||
nsAHttpTransaction* aTrans) {
|
||
nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
|
||
aEnt->GetTransactionPendingQHelper(aTrans);
|
||
|
||
int32_t index =
|
||
pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
|
||
|
||
RefPtr<PendingTransactionInfo> info;
|
||
if (index != -1) {
|
||
info = (*pendingQ)[index];
|
||
if (removeWhenFound) {
|
||
pendingQ->RemoveElementAt(index);
|
||
if (!(aTrans->Caps() & NS_HTTP_URGENT_START)) {
|
||
aEnt->OnPendingTransactionRemovedFromTable();
|
||
}
|
||
}
|
||
}
|
||
return info.forget();
|
||
}
|
||
|
||
already_AddRefed<ConnectionEntry> nsHttpConnectionMgr::FindConnectionEntry(
|
||
const nsHttpConnectionInfo* ci) {
|
||
return mCT.Get(ci->HashKey());
|
||
}
|
||
|
||
nsHttpConnectionMgr* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; }
|
||
|
||
HttpConnectionMgrParent* nsHttpConnectionMgr::AsHttpConnectionMgrParent() {
|
||
return nullptr;
|
||
}
|
||
|
||
void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive) {
|
||
mNumIdleConns++;
|
||
|
||
// If the added connection was first idle connection or has shortest
|
||
// time to live among the watched connections, pruning dead
|
||
// connections needs to be done when it can't be reused anymore.
|
||
if (!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) {
|
||
PruneDeadConnectionsAfter(timeToLive);
|
||
}
|
||
}
|
||
|
||
void nsHttpConnectionMgr::DecrementNumIdleConns() {
|
||
MOZ_ASSERT(mNumIdleConns);
|
||
mNumIdleConns--;
|
||
ConditionallyStopPruneDeadConnectionsTimer();
|
||
}
|
||
|
||
// A structure used to marshall objects necessary for ServerCertificateHashaes
|
||
class nsStoreServerCertHashesData : public ARefBase {
|
||
public:
|
||
nsStoreServerCertHashesData(
|
||
nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3,
|
||
nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes)
|
||
: mConnInfo(aConnInfo),
|
||
mNoSpdy(aNoSpdy),
|
||
mNoHttp3(aNoHttp3),
|
||
mServerCertHashes(std::move(aServerCertHashes)) {}
|
||
|
||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStoreServerCertHashesData, override)
|
||
|
||
RefPtr<nsHttpConnectionInfo> mConnInfo;
|
||
bool mNoSpdy;
|
||
bool mNoHttp3;
|
||
nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
|
||
|
||
private:
|
||
virtual ~nsStoreServerCertHashesData() = default;
|
||
};
|
||
|
||
// The connection manager needs to know the hashes used for a WebTransport
|
||
// connection authenticated with serverCertHashes
|
||
nsresult nsHttpConnectionMgr::StoreServerCertHashes(
|
||
nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3,
|
||
nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes) {
|
||
RefPtr<nsHttpConnectionInfo> ci = aConnInfo->Clone();
|
||
RefPtr<nsStoreServerCertHashesData> data = new nsStoreServerCertHashesData(
|
||
ci, aNoSpdy, aNoHttp3, std::move(aServerCertHashes));
|
||
return PostEvent(&nsHttpConnectionMgr::OnMsgStoreServerCertHashes, 0, data);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::OnMsgStoreServerCertHashes(int32_t, ARefBase* param) {
|
||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||
|
||
nsStoreServerCertHashesData* data =
|
||
static_cast<nsStoreServerCertHashesData*>(param);
|
||
|
||
bool isWildcard;
|
||
ConnectionEntry* connEnt = GetOrCreateConnectionEntry(
|
||
data->mConnInfo, true, data->mNoSpdy, data->mNoHttp3, &isWildcard);
|
||
MOZ_ASSERT(!isWildcard, "No webtransport with wildcard");
|
||
connEnt->SetServerCertHashes(std::move(data->mServerCertHashes));
|
||
}
|
||
|
||
const nsTArray<RefPtr<nsIWebTransportHash>>*
|
||
nsHttpConnectionMgr::GetServerCertHashes(nsHttpConnectionInfo* aConnInfo) {
|
||
ConnectionEntry* connEnt = mCT.GetWeak(aConnInfo->HashKey());
|
||
if (!connEnt) {
|
||
MOZ_ASSERT(0);
|
||
return nullptr;
|
||
}
|
||
return &connEnt->GetServerCertHashes();
|
||
}
|
||
|
||
void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction* aTrans) {
|
||
// We only do this check on socket thread. When this function is called on
|
||
// main thread, the transaction is newly created, so we can skip this check.
|
||
if (!OnSocketThread()) {
|
||
return;
|
||
}
|
||
|
||
nsAutoCString hashKey;
|
||
aTrans->GetHashKeyOfConnectionEntry(hashKey);
|
||
if (hashKey.IsEmpty()) {
|
||
return;
|
||
}
|
||
|
||
bool foundInPendingQ = RemoveTransFromConnEntry(aTrans, hashKey);
|
||
if (foundInPendingQ) {
|
||
glean::networking::trans_found_in_pending_queue.Add(1);
|
||
}
|
||
MOZ_ASSERT(!foundInPendingQ);
|
||
}
|
||
|
||
bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3(
|
||
nsHttpConnectionInfo* ci, nsresult aError) {
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (!ent) {
|
||
return false;
|
||
}
|
||
|
||
return ent->AllowToRetryDifferentIPFamilyForHttp3(aError);
|
||
}
|
||
|
||
void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3(
|
||
nsHttpConnectionInfo* ci, uint16_t aIPFamily) {
|
||
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
|
||
if (!ent) {
|
||
return;
|
||
}
|
||
|
||
ent->SetRetryDifferentIPFamilyForHttp3(aIPFamily);
|
||
}
|
||
|
||
} // namespace mozilla::net
|