Source code
Revision control
Copy as Markdown
Other Tools
/* vim:set ts=4 sw=2 sts=2 et cin: */
/* 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
// 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/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/glean/GleanMetrics.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.name} - {marker.data.url}");
schema.AddKeyFormatSearchable("url", MS::Format::Url,
MS::Searchable::Searchable);
schema.AddKeyLabelFormat("duration", "Duration", MS::Format::Duration);
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() {
nsCOMPtr<nsIEventTarget> sts;
nsCOMPtr<nsIIOService> ioService = components::IO::Service();
if (ioService) {
nsCOMPtr<nsISocketTransportService> realSTS =
components::SocketTransport::Service();
sts = do_QueryInterface(realSTS);
}
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
// do nothing if already initialized or if we've shut down
if (mSocketThreadTarget || mIsShuttingDown) return NS_OK;
mSocketThreadTarget = 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 throttleVersion,
uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
uint32_t throttleReadLimit, uint32_t throttleReadInterval,
uint32_t throttleHoldTime, uint32_t throttleMaxTime,
bool beConservativeForProxy) {
LOG(("nsHttpConnectionMgr::Init\n"));
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
mMaxConns = maxConns;
mMaxPersistConnsPerHost = maxPersistConnsPerHost;
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
mMaxRequestDelay = maxRequestDelay;
mThrottleEnabled = throttleEnabled;
mThrottleVersion = throttleVersion;
mThrottleSuspendFor = throttleSuspendFor;
mThrottleResumeFor = throttleResumeFor;
mThrottleReadLimit = throttleReadLimit;
mThrottleReadInterval = throttleReadInterval;
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;
};
nsresult nsHttpConnectionMgr::Shutdown() {
LOG(("nsHttpConnectionMgr::Shutdown\n"));
RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
// do nothing if already shutdown
if (!mSocketThreadTarget) return NS_OK;
nsresult rv =
PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, 0, shutdownWrapper);
// release our reference to the STS to prevent further events
// from being posted. this is how we indicate that we are
// shutting down.
mIsShuttingDown = true;
mSocketThreadTarget = nullptr;
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;
}
class ConnEvent : public Runnable {
public:
ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
int32_t iparam, ARefBase* vparam)
: Runnable("net::ConnEvent"),
mMgr(mgr),
mHandler(handler),
mIParam(iparam),
mVParam(vparam) {}
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;
};
nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
int32_t iparam, ARefBase* vparam) {
Unused << EnsureSocketThreadTarget();
nsCOMPtr<nsIEventTarget> target;
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
target = mSocketThreadTarget;
}
if (!target) {
NS_WARNING("cannot post event if not initialized");
return NS_ERROR_NOT_INITIALIZED;
}
nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
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) {
Unused << PruneDeadConnections();
} else if (timer == mTimeoutTick) {
TimeoutTick();
} else if (timer == mTrafficTimer) {
Unused << 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());
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
trans->AsHttpTransaction());
}
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());
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
data);
}
nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
int32_t priority) {
LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
priority));
return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
trans->AsHttpTransaction());
}
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()));
Unused << EnsureSocketThreadTarget();
nsCOMPtr<nsIEventTarget> target;
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
target = mSocketThreadTarget;
}
if (!target) {
NS_WARNING("cannot post event if not initialized");
return;
}
RefPtr<nsHttpConnectionMgr> self(this);
Unused << 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) {
if (!aCI) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup, 0, ci);
}
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
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->SetIsFromPredictor(overrider->GetIsFromPredictor());
args->mTrans->SetAllow1918(overrider->GetAllow1918());
}
return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
}
nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) {
Unused << EnsureSocketThreadTarget();
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
temp.forget(target);
return NS_OK;
}
nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) {
LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
Unused << EnsureSocketThreadTarget();
nsCOMPtr<nsIEventTarget> target;
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
target = mSocketThreadTarget;
}
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);
}
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->IdleConnectionsLength() == 0 && ent->ActiveConnsLength() == 0 &&
ent->DnsAndConnectSocketsLength() == 0 &&
ent->UrgentStartQueueLength() == 0 && ent->PendingQueueLength() == 0 &&
!ent->mDoNotDestroy) {
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, const nsCString& 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=%s "
"newCI=%s matchedCI=%s join ok\n",
potentialMatch.get(), key.get(), ci->HashKey().get(),
potentialMatch->ConnectionInfo()->HashKey().get()));
return potentialMatch.get();
}
LOG(
("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
"newCI=%s matchedCI=%s join failed\n",
potentialMatch.get(), key.get(), 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;
}
static void BuildOriginFrameHashKey(nsACString& newKey,
nsHttpConnectionInfo* ci,
const nsACString& host, int32_t port) {
newKey.Assign(host);
if (ci->GetAnonymous()) {
newKey.AppendLiteral("~A:");
} else {
newKey.AppendLiteral("~.:");
}
if (ci->GetFallbackConnection()) {
newKey.AppendLiteral("~F:");
} else {
newKey.AppendLiteral("~.:");
}
newKey.AppendInt(port);
newKey.AppendLiteral("/[");
nsAutoCString suffix;
ci->GetOriginAttributes().CreateSuffix(suffix);
newKey.Append(suffix);
newKey.AppendLiteral("]viaORIGIN.FRAME");
}
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
nsCString newKey;
BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
ent, newKey, justKidding, aNoHttp2, aNoHttp3);
if (conn) {
LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
ci->HashKey().get(), conn, newKey.get()));
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
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(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));
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 %s\n",
newConn, newConn->ConnectionInfo()->HashKey().get(),
ent->mCoalescingKeys[i].get()));
mCoalescingHash
.LookupOrInsertWith(
ent->mCoalescingKeys[i],
[] {