1 /* vim:set ts=4 sw=2 sts=2 et cin: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // HttpLog.h should generally be included first
9 // Log on level :5, instead of default :4.
11 #define LOG(args) LOG5(args)
13 #define LOG_ENABLED() LOG5_ENABLED()
18 #include "ConnectionHandle.h"
19 #include "HttpConnectionUDP.h"
20 #include "NullHttpTransaction.h"
21 #include "SpeculativeTransaction.h"
22 #include "mozilla/Components.h"
23 #include "mozilla/PerfStats.h"
24 #include "mozilla/ProfilerMarkers.h"
25 #include "mozilla/SpinEventLoopUntil.h"
26 #include "mozilla/StaticPrefs_network.h"
27 #include "mozilla/Telemetry.h"
28 #include "mozilla/Unused.h"
29 #include "mozilla/glean/GleanMetrics.h"
30 #include "mozilla/net/DNS.h"
31 #include "mozilla/net/DashboardTypes.h"
33 #include "nsHttpConnectionMgr.h"
34 #include "nsHttpHandler.h"
35 #include "nsIClassOfService.h"
36 #include "nsIDNSByTypeRecord.h"
37 #include "nsIDNSListener.h"
38 #include "nsIDNSRecord.h"
39 #include "nsIDNSService.h"
40 #include "nsIHttpChannelInternal.h"
42 #include "nsIRequestContext.h"
43 #include "nsISocketTransport.h"
44 #include "nsISocketTransportService.h"
45 #include "nsITransport.h"
46 #include "nsIXPConnect.h"
47 #include "nsInterfaceRequestorAgg.h"
49 #include "nsNetSegmentUtils.h"
50 #include "nsNetUtil.h"
51 #include "nsQueryObject.h"
52 #include "nsSocketTransportService2.h"
53 #include "nsStreamUtils.h"
55 using namespace mozilla
;
57 namespace geckoprofiler::markers
{
60 static constexpr Span
<const char> MarkerTypeName() {
61 return MakeStringSpan("Url");
63 static void StreamJSONMarkerData(
64 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
65 const mozilla::ProfilerString8View
& aURL
, const TimeDuration
& aDuration
,
66 uint64_t aChannelId
) {
67 if (aURL
.Length() != 0) {
68 aWriter
.StringProperty("url", aURL
);
70 if (!aDuration
.IsZero()) {
71 aWriter
.DoubleProperty("duration", aDuration
.ToMilliseconds());
73 aWriter
.IntProperty("channelId", static_cast<int64_t>(aChannelId
));
75 static MarkerSchema
MarkerTypeDisplay() {
76 using MS
= MarkerSchema
;
77 MS
schema(MS::Location::MarkerChart
, MS::Location::MarkerTable
);
78 schema
.SetTableLabel("{marker.name} - {marker.data.url}");
79 schema
.AddKeyFormatSearchable("url", MS::Format::Url
,
80 MS::Searchable::Searchable
);
81 schema
.AddKeyLabelFormat("duration", "Duration", MS::Format::Duration
);
86 } // namespace geckoprofiler::markers
88 namespace mozilla::net
{
90 //-----------------------------------------------------------------------------
92 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr
, nsIObserver
, nsINamed
)
94 //-----------------------------------------------------------------------------
96 nsHttpConnectionMgr::nsHttpConnectionMgr() {
97 LOG(("Creating nsHttpConnectionMgr @%p\n", this));
100 nsHttpConnectionMgr::~nsHttpConnectionMgr() {
101 LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
102 MOZ_ASSERT(mCoalescingHash
.Count() == 0);
103 if (mTimeoutTick
) mTimeoutTick
->Cancel();
106 nsresult
nsHttpConnectionMgr::EnsureSocketThreadTarget() {
107 nsCOMPtr
<nsIEventTarget
> sts
;
108 nsCOMPtr
<nsIIOService
> ioService
= components::IO::Service();
110 nsCOMPtr
<nsISocketTransportService
> realSTS
=
111 components::SocketTransport::Service();
112 sts
= do_QueryInterface(realSTS
);
115 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
117 // do nothing if already initialized or if we've shut down
118 if (mSocketThreadTarget
|| mIsShuttingDown
) return NS_OK
;
120 mSocketThreadTarget
= sts
;
122 return sts
? NS_OK
: NS_ERROR_NOT_AVAILABLE
;
125 nsresult
nsHttpConnectionMgr::Init(
126 uint16_t maxUrgentExcessiveConns
, uint16_t maxConns
,
127 uint16_t maxPersistConnsPerHost
, uint16_t maxPersistConnsPerProxy
,
128 uint16_t maxRequestDelay
, bool throttleEnabled
, uint32_t throttleVersion
,
129 uint32_t throttleSuspendFor
, uint32_t throttleResumeFor
,
130 uint32_t throttleReadLimit
, uint32_t throttleReadInterval
,
131 uint32_t throttleHoldTime
, uint32_t throttleMaxTime
,
132 bool beConservativeForProxy
) {
133 LOG(("nsHttpConnectionMgr::Init\n"));
136 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
138 mMaxUrgentExcessiveConns
= maxUrgentExcessiveConns
;
139 mMaxConns
= maxConns
;
140 mMaxPersistConnsPerHost
= maxPersistConnsPerHost
;
141 mMaxPersistConnsPerProxy
= maxPersistConnsPerProxy
;
142 mMaxRequestDelay
= maxRequestDelay
;
144 mThrottleEnabled
= throttleEnabled
;
145 mThrottleVersion
= throttleVersion
;
146 mThrottleSuspendFor
= throttleSuspendFor
;
147 mThrottleResumeFor
= throttleResumeFor
;
148 mThrottleReadLimit
= throttleReadLimit
;
149 mThrottleReadInterval
= throttleReadInterval
;
150 mThrottleHoldTime
= throttleHoldTime
;
151 mThrottleMaxTime
= TimeDuration::FromMilliseconds(throttleMaxTime
);
153 mBeConservativeForProxy
= beConservativeForProxy
;
155 mIsShuttingDown
= false;
158 return EnsureSocketThreadTarget();
161 class BoolWrapper
: public ARefBase
{
163 BoolWrapper() = default;
164 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper
, override
)
166 public: // intentional!
170 virtual ~BoolWrapper() = default;
173 nsresult
nsHttpConnectionMgr::Shutdown() {
174 LOG(("nsHttpConnectionMgr::Shutdown\n"));
176 RefPtr
<BoolWrapper
> shutdownWrapper
= new BoolWrapper();
178 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
180 // do nothing if already shutdown
181 if (!mSocketThreadTarget
) return NS_OK
;
184 PostEvent(&nsHttpConnectionMgr::OnMsgShutdown
, 0, shutdownWrapper
);
186 // release our reference to the STS to prevent further events
187 // from being posted. this is how we indicate that we are
189 mIsShuttingDown
= true;
190 mSocketThreadTarget
= nullptr;
193 NS_WARNING("unable to post SHUTDOWN message");
198 // wait for shutdown event to complete
199 SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns
,
200 [&, shutdownWrapper
]() { return shutdownWrapper
->mBool
; });
205 class ConnEvent
: public Runnable
{
207 ConnEvent(nsHttpConnectionMgr
* mgr
, nsConnEventHandler handler
,
208 int32_t iparam
, ARefBase
* vparam
)
209 : Runnable("net::ConnEvent"),
215 NS_IMETHOD
Run() override
{
216 (mMgr
->*mHandler
)(mIParam
, mVParam
);
221 virtual ~ConnEvent() = default;
223 RefPtr
<nsHttpConnectionMgr
> mMgr
;
224 nsConnEventHandler mHandler
;
226 RefPtr
<ARefBase
> mVParam
;
229 nsresult
nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler
,
230 int32_t iparam
, ARefBase
* vparam
) {
231 Unused
<< EnsureSocketThreadTarget();
233 nsCOMPtr
<nsIEventTarget
> target
;
235 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
236 target
= mSocketThreadTarget
;
240 NS_WARNING("cannot post event if not initialized");
241 return NS_ERROR_NOT_INITIALIZED
;
244 nsCOMPtr
<nsIRunnable
> event
= new ConnEvent(this, handler
, iparam
, vparam
);
245 return target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
248 void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds
) {
249 LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
251 if (!mTimer
) mTimer
= NS_NewTimer();
253 // failure to create a timer is not a fatal error, but idle connections
254 // will not be cleaned up until we try to use them.
256 mTimeOfNextWakeUp
= timeInSeconds
+ NowInSeconds();
257 mTimer
->Init(this, timeInSeconds
* 1000, nsITimer::TYPE_ONE_SHOT
);
259 NS_WARNING("failed to create: timer for pruning the dead connections!");
263 void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() {
264 // Leave the timer in place if there are connections that potentially
267 (mNumActiveConns
&& StaticPrefs::network_http_http2_enabled())) {
271 LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
273 // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
274 mTimeOfNextWakeUp
= UINT64_MAX
;
281 void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
283 ("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
284 "armed=%d active=%d\n",
285 mTimeoutTickArmed
, mNumActiveConns
));
287 if (!mTimeoutTickArmed
) return;
289 if (mNumActiveConns
) return;
291 LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
293 mTimeoutTick
->Cancel();
294 mTimeoutTickArmed
= false;
297 //-----------------------------------------------------------------------------
298 // nsHttpConnectionMgr::nsINamed
299 //-----------------------------------------------------------------------------
302 nsHttpConnectionMgr::GetName(nsACString
& aName
) {
303 aName
.AssignLiteral("nsHttpConnectionMgr");
307 //-----------------------------------------------------------------------------
308 // nsHttpConnectionMgr::nsIObserver
309 //-----------------------------------------------------------------------------
312 nsHttpConnectionMgr::Observe(nsISupports
* subject
, const char* topic
,
313 const char16_t
* data
) {
314 LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic
));
316 if (0 == strcmp(topic
, NS_TIMER_CALLBACK_TOPIC
)) {
317 nsCOMPtr
<nsITimer
> timer
= do_QueryInterface(subject
);
318 if (timer
== mTimer
) {
319 Unused
<< PruneDeadConnections();
320 } else if (timer
== mTimeoutTick
) {
322 } else if (timer
== mTrafficTimer
) {
323 Unused
<< PruneNoTraffic();
324 } else if (timer
== mThrottleTicker
) {
326 } else if (timer
== mDelayedResumeReadTimer
) {
327 ResumeBackgroundThrottledTransactions();
329 MOZ_ASSERT(false, "unexpected timer-callback");
330 LOG(("Unexpected timer object\n"));
331 return NS_ERROR_UNEXPECTED
;
338 //-----------------------------------------------------------------------------
340 nsresult
nsHttpConnectionMgr::AddTransaction(HttpTransactionShell
* trans
,
342 LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans
, priority
));
343 // Make sure a transaction is not in a pending queue.
344 CheckTransInPendingQueue(trans
->AsHttpTransaction());
345 return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction
, priority
,
346 trans
->AsHttpTransaction());
349 class NewTransactionData
: public ARefBase
{
351 NewTransactionData(nsHttpTransaction
* trans
, int32_t priority
,
352 nsHttpTransaction
* transWithStickyConn
)
355 mTransWithStickyConn(transWithStickyConn
) {}
357 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData
, override
)
359 RefPtr
<nsHttpTransaction
> mTrans
;
361 RefPtr
<nsHttpTransaction
> mTransWithStickyConn
;
364 virtual ~NewTransactionData() = default;
367 nsresult
nsHttpConnectionMgr::AddTransactionWithStickyConn(
368 HttpTransactionShell
* trans
, int32_t priority
,
369 HttpTransactionShell
* transWithStickyConn
) {
371 ("nsHttpConnectionMgr::AddTransactionWithStickyConn "
372 "[trans=%p %d transWithStickyConn=%p]\n",
373 trans
, priority
, transWithStickyConn
));
374 // Make sure a transaction is not in a pending queue.
375 CheckTransInPendingQueue(trans
->AsHttpTransaction());
377 RefPtr
<NewTransactionData
> data
=
378 new NewTransactionData(trans
->AsHttpTransaction(), priority
,
379 transWithStickyConn
->AsHttpTransaction());
380 return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn
, 0,
384 nsresult
nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell
* trans
,
386 LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans
,
388 return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction
, priority
,
389 trans
->AsHttpTransaction());
392 void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
393 HttpTransactionShell
* trans
, const ClassOfService
& classOfService
) {
395 ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
396 "classOfService flags=%" PRIu32
" inc=%d]\n",
397 trans
, static_cast<uint32_t>(classOfService
.Flags()),
398 classOfService
.Incremental()));
400 Unused
<< EnsureSocketThreadTarget();
402 nsCOMPtr
<nsIEventTarget
> target
;
404 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
405 target
= mSocketThreadTarget
;
409 NS_WARNING("cannot post event if not initialized");
413 RefPtr
<nsHttpConnectionMgr
> self(this);
414 Unused
<< target
->Dispatch(NS_NewRunnableFunction(
415 "nsHttpConnectionMgr::CallUpdateClassOfServiceOnTransaction",
416 [cos
{classOfService
}, self
{std::move(self
)}, trans
= RefPtr
{trans
}]() {
417 self
->OnMsgUpdateClassOfServiceOnTransaction(
418 cos
, trans
->AsHttpTransaction());
422 nsresult
nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell
* trans
,
424 LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32
"]\n",
425 trans
, static_cast<uint32_t>(reason
)));
426 return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction
,
427 static_cast<int32_t>(reason
), trans
->AsHttpTransaction());
430 nsresult
nsHttpConnectionMgr::PruneDeadConnections() {
431 return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections
);
435 // Called after a timeout. Check for active connections that have had no
436 // traffic since they were "marked" and nuke them.
437 nsresult
nsHttpConnectionMgr::PruneNoTraffic() {
438 LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
439 mPruningNoTraffic
= true;
440 return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic
);
443 nsresult
nsHttpConnectionMgr::VerifyTraffic() {
444 LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
445 return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic
);
448 nsresult
nsHttpConnectionMgr::DoShiftReloadConnectionCleanup() {
449 return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup
, 0,
453 nsresult
nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
454 nsHttpConnectionInfo
* aCI
) {
456 return NS_ERROR_INVALID_ARG
;
459 RefPtr
<nsHttpConnectionInfo
> ci
= aCI
->Clone();
460 return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup
, 0,
464 nsresult
nsHttpConnectionMgr::DoSingleConnectionCleanup(
465 nsHttpConnectionInfo
* aCI
) {
467 return NS_ERROR_INVALID_ARG
;
470 RefPtr
<nsHttpConnectionInfo
> ci
= aCI
->Clone();
471 return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup
, 0, ci
);
474 class SpeculativeConnectArgs
: public ARefBase
{
476 SpeculativeConnectArgs() = default;
477 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs
, override
)
479 public: // intentional!
480 RefPtr
<SpeculativeTransaction
> mTrans
;
482 bool mFetchHTTPSRR
{false};
485 virtual ~SpeculativeConnectArgs() = default;
489 nsresult
nsHttpConnectionMgr::SpeculativeConnect(
490 nsHttpConnectionInfo
* ci
, nsIInterfaceRequestor
* callbacks
, uint32_t caps
,
491 SpeculativeTransaction
* aTransaction
, bool aFetchHTTPSRR
) {
492 if (!IsNeckoChild() && NS_IsMainThread()) {
493 // HACK: make sure PSM gets initialized on the main thread.
497 LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
498 ci
->HashKey().get()));
500 nsCOMPtr
<nsISpeculativeConnectionOverrider
> overrider
=
501 do_GetInterface(callbacks
);
503 bool allow1918
= overrider
? overrider
->GetAllow1918() : false;
505 // Hosts that are Local IP Literals should not be speculatively
506 // connected - Bug 853423.
507 if ((!allow1918
) && ci
&& ci
->HostIsLocalIPLiteral()) {
509 ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
515 nsAutoCString
url(ci
->EndToEndSSL() ? "https://"_ns
: "http://"_ns
);
516 url
+= ci
->GetOrigin();
517 PROFILER_MARKER("SpeculativeConnect", NETWORK
, {}, UrlMarker
, url
,
518 TimeDuration::Zero(), 0);
520 RefPtr
<SpeculativeConnectArgs
> args
= new SpeculativeConnectArgs();
522 // Wrap up the callbacks and the target to ensure they're released on the
523 // target thread properly.
524 nsCOMPtr
<nsIInterfaceRequestor
> wrappedCallbacks
;
525 NS_NewInterfaceRequestorAggregation(callbacks
, nullptr,
526 getter_AddRefs(wrappedCallbacks
));
528 caps
|= ci
->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS
: 0;
529 caps
|= NS_HTTP_ERROR_SOFTLY
;
530 args
->mTrans
= aTransaction
532 : new SpeculativeTransaction(ci
, wrappedCallbacks
, caps
);
533 args
->mFetchHTTPSRR
= aFetchHTTPSRR
;
536 args
->mTrans
->SetParallelSpeculativeConnectLimit(
537 overrider
->GetParallelSpeculativeConnectLimit());
538 args
->mTrans
->SetIgnoreIdle(overrider
->GetIgnoreIdle());
539 args
->mTrans
->SetIsFromPredictor(overrider
->GetIsFromPredictor());
540 args
->mTrans
->SetAllow1918(overrider
->GetAllow1918());
543 return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect
, 0, args
);
546 nsresult
nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget
** target
) {
547 Unused
<< EnsureSocketThreadTarget();
549 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
550 nsCOMPtr
<nsIEventTarget
> temp(mSocketThreadTarget
);
555 nsresult
nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase
* conn
) {
556 LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn
));
558 Unused
<< EnsureSocketThreadTarget();
560 nsCOMPtr
<nsIEventTarget
> target
;
562 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
563 target
= mSocketThreadTarget
;
567 NS_WARNING("cannot post event if not initialized");
568 return NS_ERROR_NOT_INITIALIZED
;
571 RefPtr
<HttpConnectionBase
> connRef(conn
);
572 RefPtr
<nsHttpConnectionMgr
> self(this);
573 return target
->Dispatch(NS_NewRunnableFunction(
574 "nsHttpConnectionMgr::CallReclaimConnection",
575 [conn
{std::move(connRef
)}, self
{std::move(self
)}]() {
576 self
->OnMsgReclaimConnection(conn
);
580 // A structure used to marshall 6 pointers across the various necessary
581 // threads to complete an HTTP upgrade.
582 class nsCompleteUpgradeData
: public ARefBase
{
584 nsCompleteUpgradeData(nsHttpTransaction
* aTrans
,
585 nsIHttpUpgradeListener
* aListener
, bool aJsWrapped
)
586 : mTrans(aTrans
), mUpgradeListener(aListener
), mJsWrapped(aJsWrapped
) {}
588 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData
, override
)
590 RefPtr
<nsHttpTransaction
> mTrans
;
591 nsCOMPtr
<nsIHttpUpgradeListener
> mUpgradeListener
;
593 nsCOMPtr
<nsISocketTransport
> mSocketTransport
;
594 nsCOMPtr
<nsIAsyncInputStream
> mSocketIn
;
595 nsCOMPtr
<nsIAsyncOutputStream
> mSocketOut
;
600 virtual ~nsCompleteUpgradeData() {
601 NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
602 mUpgradeListener
.forget());
606 nsresult
nsHttpConnectionMgr::CompleteUpgrade(
607 HttpTransactionShell
* aTrans
, nsIHttpUpgradeListener
* aUpgradeListener
) {
608 // test if aUpgradeListener is a wrapped JsObject
609 nsCOMPtr
<nsIXPConnectWrappedJS
> wrapper
= do_QueryInterface(aUpgradeListener
);
611 bool wrapped
= !!wrapper
;
613 RefPtr
<nsCompleteUpgradeData
> data
= new nsCompleteUpgradeData(
614 aTrans
->AsHttpTransaction(), aUpgradeListener
, wrapped
);
615 return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade
, 0, data
);
618 nsresult
nsHttpConnectionMgr::UpdateParam(nsParamName name
, uint16_t value
) {
619 uint32_t param
= (uint32_t(name
) << 16) | uint32_t(value
);
620 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam
,
621 static_cast<int32_t>(param
), nullptr);
624 nsresult
nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo
* aCI
) {
625 LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI
->HashKey().get()));
626 RefPtr
<nsHttpConnectionInfo
> ci
;
630 return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ
, 0, ci
);
633 nsresult
nsHttpConnectionMgr::ProcessPendingQ() {
634 LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
635 return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ
, 0, nullptr);
638 void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
640 EventTokenBucket
* tokenBucket
= static_cast<EventTokenBucket
*>(param
);
641 gHttpHandler
->SetRequestTokenBucket(tokenBucket
);
644 nsresult
nsHttpConnectionMgr::UpdateRequestTokenBucket(
645 EventTokenBucket
* aBucket
) {
646 // Call From main thread when a new EventTokenBucket has been made in order
647 // to post the new value to the socket thread.
648 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket
, 0,
652 nsresult
nsHttpConnectionMgr::ClearConnectionHistory() {
653 return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory
, 0,
657 void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
659 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
661 LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory"));
663 for (auto iter
= mCT
.Iter(); !iter
.Done(); iter
.Next()) {
664 RefPtr
<ConnectionEntry
> ent
= iter
.Data();
665 if (ent
->IdleConnectionsLength() == 0 && ent
->ActiveConnsLength() == 0 &&
666 ent
->DnsAndConnectSocketsLength() == 0 &&
667 ent
->UrgentStartQueueLength() == 0 && ent
->PendingQueueLength() == 0 &&
668 !ent
->mDoNotDestroy
) {
674 nsresult
nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection
* conn
) {
675 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
676 LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn
));
678 if (!conn
->ConnectionInfo()) {
679 return NS_ERROR_UNEXPECTED
;
682 ConnectionEntry
* ent
= mCT
.GetWeak(conn
->ConnectionInfo()->HashKey());
684 if (!ent
|| NS_FAILED(ent
->CloseIdleConnection(conn
))) {
685 return NS_ERROR_UNEXPECTED
;
691 nsresult
nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection
* conn
) {
692 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
694 LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn
));
696 if (!conn
->ConnectionInfo()) {
697 return NS_ERROR_UNEXPECTED
;
700 ConnectionEntry
* ent
= mCT
.GetWeak(conn
->ConnectionInfo()->HashKey());
702 if (!ent
|| NS_FAILED(ent
->RemoveIdleConnection(conn
))) {
703 return NS_ERROR_UNEXPECTED
;
709 HttpConnectionBase
* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
710 ConnectionEntry
* ent
, const nsCString
& key
, bool justKidding
, bool aNoHttp2
,
712 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
713 MOZ_ASSERT(!aNoHttp2
|| !aNoHttp3
);
714 MOZ_ASSERT(ent
->mConnInfo
);
715 nsHttpConnectionInfo
* ci
= ent
->mConnInfo
;
717 nsTArray
<nsWeakPtr
>* listOfWeakConns
= mCoalescingHash
.Get(key
);
718 if (!listOfWeakConns
) {
722 uint32_t listLen
= listOfWeakConns
->Length();
723 for (uint32_t j
= 0; j
< listLen
;) {
724 RefPtr
<HttpConnectionBase
> potentialMatch
=
725 do_QueryReferent(listOfWeakConns
->ElementAt(j
));
726 if (!potentialMatch
) {
727 // This is a connection that needs to be removed from the list
729 ("FindCoalescableConnectionByHashKey() found old conn %p that has "
730 "null weak ptr - removing\n",
731 listOfWeakConns
->ElementAt(j
).get()));
732 if (j
!= listLen
- 1) {
733 listOfWeakConns
->Elements()[j
] =
734 listOfWeakConns
->Elements()[listLen
- 1];
736 listOfWeakConns
->RemoveLastElement();
737 MOZ_ASSERT(listOfWeakConns
->Length() == listLen
- 1);
739 continue; // without adjusting iterator
742 if (aNoHttp3
&& potentialMatch
->UsingHttp3()) {
746 if (aNoHttp2
&& potentialMatch
->UsingSpdy()) {
753 potentialMatch
->TestJoinConnection(ci
->GetOrigin(), ci
->OriginPort());
756 potentialMatch
->JoinConnection(ci
->GetOrigin(), ci
->OriginPort());
760 ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
761 "newCI=%s matchedCI=%s join ok\n",
762 potentialMatch
.get(), key
.get(), ci
->HashKey().get(),
763 potentialMatch
->ConnectionInfo()->HashKey().get()));
764 return potentialMatch
.get();
767 ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
768 "newCI=%s matchedCI=%s join failed\n",
769 potentialMatch
.get(), key
.get(), ci
->HashKey().get(),
770 potentialMatch
->ConnectionInfo()->HashKey().get()));
772 ++j
; // bypassed by continue when weakptr fails
775 if (!listLen
) { // shrunk to 0 while iterating
776 LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
777 mCoalescingHash
.Remove(key
);
782 static void BuildOriginFrameHashKey(nsACString
& newKey
,
783 nsHttpConnectionInfo
* ci
,
784 const nsACString
& host
, int32_t port
) {
786 if (ci
->GetAnonymous()) {
787 newKey
.AppendLiteral("~A:");
789 newKey
.AppendLiteral("~.:");
791 if (ci
->GetFallbackConnection()) {
792 newKey
.AppendLiteral("~F:");
794 newKey
.AppendLiteral("~.:");
796 newKey
.AppendInt(port
);
797 newKey
.AppendLiteral("/[");
798 nsAutoCString suffix
;
799 ci
->GetOriginAttributes().CreateSuffix(suffix
);
800 newKey
.Append(suffix
);
801 newKey
.AppendLiteral("]viaORIGIN.FRAME");
804 HttpConnectionBase
* nsHttpConnectionMgr::FindCoalescableConnection(
805 ConnectionEntry
* ent
, bool justKidding
, bool aNoHttp2
, bool aNoHttp3
) {
806 MOZ_ASSERT(!aNoHttp2
|| !aNoHttp3
);
807 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
808 MOZ_ASSERT(ent
->mConnInfo
);
809 nsHttpConnectionInfo
* ci
= ent
->mConnInfo
;
810 LOG(("FindCoalescableConnection %s\n", ci
->HashKey().get()));
812 if (ci
->GetWebTransport()) {
813 LOG(("Don't coalesce a WebTransport conn "));
816 // First try and look it up by origin frame
818 BuildOriginFrameHashKey(newKey
, ci
, ci
->GetOrigin(), ci
->OriginPort());
819 HttpConnectionBase
* conn
= FindCoalescableConnectionByHashKey(
820 ent
, newKey
, justKidding
, aNoHttp2
, aNoHttp3
);
822 LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
823 ci
->HashKey().get(), conn
, newKey
.get()));
827 // now check for DNS based keys
828 // deleted conns (null weak pointers) are removed from list
829 uint32_t keyLen
= ent
->mCoalescingKeys
.Length();
830 for (uint32_t i
= 0; i
< keyLen
; ++i
) {
831 conn
= FindCoalescableConnectionByHashKey(ent
, ent
->mCoalescingKeys
[i
],
832 justKidding
, aNoHttp2
, aNoHttp3
);
834 auto usableEntry
= [&](HttpConnectionBase
* conn
) {
835 // This is allowed by the spec, but other browsers don't coalesce
836 // so agressively, which surprises developers. See bug 1420777.
837 if (StaticPrefs::network_http_http2_aggressive_coalescing()) {
841 // Make sure that the connection's IP address is one that is in
842 // the set of IP addresses in the entry's DNS response.
844 nsresult rv
= conn
->GetPeerAddr(&addr
);
846 // Err on the side of not coalescing
849 // We don't care about remote port when matching entries.
851 return ent
->mAddresses
.Contains(addr
);
855 LOG(("Found connection with matching hash"));
856 if (usableEntry(conn
)) {
857 LOG(("> coalescing"));
860 LOG(("> not coalescing as remote address not present in DNS records"));
865 LOG(("FindCoalescableConnection(%s) no matching conn\n",
866 ci
->HashKey().get()));
870 void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
871 HttpConnectionBase
* newConn
, ConnectionEntry
* ent
, bool aNoHttp3
) {
872 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
874 MOZ_ASSERT(newConn
->ConnectionInfo());
876 MOZ_ASSERT(mCT
.GetWeak(newConn
->ConnectionInfo()->HashKey()) == ent
);
877 LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn
, aNoHttp3
));
878 if (newConn
->ConnectionInfo()->GetWebTransport()) {
879 LOG(("Don't coalesce a WebTransport conn %p", newConn
));
880 // TODO: implement this properly in bug 1815735.
884 HttpConnectionBase
* existingConn
=
885 FindCoalescableConnection(ent
, true, false, false);
887 // Prefer http3 connection, but allow an HTTP/2 connection if it is used for
889 if (newConn
->UsingHttp3() && existingConn
->UsingSpdy()) {
890 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(existingConn
);
891 if (connTCP
&& !connTCP
->IsForWebSocket()) {
893 ("UpdateCoalescingForNewConn() found existing active H2 conn that "
894 "could have served newConn, but new connection is H3, therefore "
895 "close the H2 conncetion"));
896 existingConn
->SetCloseReason(
897 ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING
);
898 existingConn
->DontReuse();
900 } else if (existingConn
->UsingHttp3() && newConn
->UsingSpdy()) {
901 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(newConn
);
902 if (connTCP
&& !connTCP
->IsForWebSocket() && !aNoHttp3
) {
904 ("UpdateCoalescingForNewConn() found existing active H3 conn that "
905 "could have served H2 newConn graceful close of newConn=%p to "
906 "migrate to existingConn %p\n",
907 newConn
, existingConn
));
908 newConn
->SetCloseReason(
909 ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING
);
910 newConn
->DontReuse();
915 ("UpdateCoalescingForNewConn() found existing active conn that could "
916 "have served newConn "
917 "graceful close of newConn=%p to migrate to existingConn %p\n",
918 newConn
, existingConn
));
919 newConn
->SetCloseReason(
920 ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING
);
921 newConn
->DontReuse();
926 // This connection might go into the mCoalescingHash for new transactions to
927 // be coalesced onto if it can accept new transactions
928 if (!newConn
->CanDirectlyActivate()) {
932 uint32_t keyLen
= ent
->mCoalescingKeys
.Length();
933 for (uint32_t i
= 0; i
< keyLen
; ++i
) {
935 "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
936 newConn
, newConn
->ConnectionInfo()->HashKey().get(),
937 ent
->mCoalescingKeys
[i
].get()));
941 ent
->mCoalescingKeys
[i
],
943 LOG(("UpdateCoalescingForNewConn() need new list element\n"));
944 return MakeUnique
<nsTArray
<nsWeakPtr
>>(1);
946 ->AppendElement(do_GetWeakReference(
947 static_cast<nsISupportsWeakReference
*>(newConn
)));
950 // this is a new connection that can be coalesced onto. hooray!
951 // if there are other connection to this entry (e.g.
952 // some could still be handshaking, shutting down, etc..) then close
953 // them down after any transactions that are on them are complete.
954 // This probably happened due to the parallel connection algorithm
955 // that is used only before the host is known to speak h2.
956 ent
->MakeAllDontReuseExcept(newConn
);
959 // This function lets a connection, after completing the NPN phase,
960 // report whether or not it is using spdy through the usingSpdy
961 // argument. It would not be necessary if NPN were driven out of
962 // the connection manager. The connection entry associated with the
963 // connection is then updated to indicate whether or not we want to use
964 // spdy with that host and update the coalescing hash
965 // entries used for de-sharding hostsnames.
966 void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection
* conn
,
968 bool disallowHttp3
) {
969 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
970 if (!conn
->ConnectionInfo()) {
973 ConnectionEntry
* ent
= mCT
.GetWeak(conn
->ConnectionInfo()->HashKey());
974 if (!ent
|| !usingSpdy
) {
978 ent
->mUsingSpdy
= true;
979 mNumSpdyHttp3ActiveConns
++;
981 // adjust timeout timer
982 uint32_t ttl
= conn
->TimeToLive();
983 uint64_t timeOfExpire
= NowInSeconds() + ttl
;
984 if (!mTimer
|| timeOfExpire
< mTimeOfNextWakeUp
) {
985 PruneDeadConnectionsAfter(ttl
);
988 UpdateCoalescingForNewConn(conn
, ent
, disallowHttp3
);
990 nsresult rv
= ProcessPendingQ(ent
->mConnInfo
);
993 ("ReportSpdyConnection conn=%p ent=%p "
994 "failed to process pending queue (%08x)\n",
995 conn
, ent
, static_cast<uint32_t>(rv
)));
997 rv
= PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ
);
1000 ("ReportSpdyConnection conn=%p ent=%p "
1001 "failed to post event (%08x)\n",
1002 conn
, ent
, static_cast<uint32_t>(rv
)));
1006 void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase
* conn
) {
1007 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1008 if (!conn
->ConnectionInfo()) {
1011 ConnectionEntry
* ent
= mCT
.GetWeak(conn
->ConnectionInfo()->HashKey());
1016 mNumSpdyHttp3ActiveConns
++;
1018 UpdateCoalescingForNewConn(conn
, ent
, false);
1019 nsresult rv
= ProcessPendingQ(ent
->mConnInfo
);
1020 if (NS_FAILED(rv
)) {
1022 ("ReportHttp3Connection conn=%p ent=%p "
1023 "failed to process pending queue (%08x)\n",
1024 conn
, ent
, static_cast<uint32_t>(rv
)));
1026 rv
= PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ
);
1027 if (NS_FAILED(rv
)) {
1029 ("ReportHttp3Connection conn=%p ent=%p "
1030 "failed to post event (%08x)\n",
1031 conn
, ent
, static_cast<uint32_t>(rv
)));
1035 //-----------------------------------------------------------------------------
1036 bool nsHttpConnectionMgr::DispatchPendingQ(
1037 nsTArray
<RefPtr
<PendingTransactionInfo
>>& pendingQ
, ConnectionEntry
* ent
,
1039 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1041 PendingTransactionInfo
* pendingTransInfo
= nullptr;
1043 bool dispatchedSuccessfully
= false;
1045 // if !considerAll iterate the pending list until one is dispatched
1046 // successfully. Keep iterating afterwards only until a transaction fails to
1047 // dispatch. if considerAll == true then try and dispatch all items.
1048 for (uint32_t i
= 0; i
< pendingQ
.Length();) {
1049 pendingTransInfo
= pendingQ
[i
];
1051 bool alreadyDnsAndConnectSocketOrWaitingForTLS
=
1052 pendingTransInfo
->IsAlreadyClaimedInitializingConn();
1054 rv
= TryDispatchTransaction(ent
, alreadyDnsAndConnectSocketOrWaitingForTLS
,
1056 if (NS_SUCCEEDED(rv
) || (rv
!= NS_ERROR_NOT_AVAILABLE
)) {
1057 if (NS_SUCCEEDED(rv
)) {
1058 LOG((" dispatching pending transaction...\n"));
1061 (" removing pending transaction based on "
1062 "TryDispatchTransaction returning hard error %" PRIx32
"\n",
1063 static_cast<uint32_t>(rv
)));
1065 if (pendingQ
.RemoveElement(pendingTransInfo
)) {
1066 // pendingTransInfo is now potentially destroyed
1067 dispatchedSuccessfully
= true;
1068 continue; // dont ++i as we just made the array shorter
1071 LOG((" transaction not found in pending queue\n"));
1074 if (dispatchedSuccessfully
&& !considerAll
) break;
1078 return dispatchedSuccessfully
;
1081 uint32_t nsHttpConnectionMgr::MaxPersistConnections(
1082 ConnectionEntry
* ent
) const {
1083 if (ent
->mConnInfo
->UsingHttpProxy() && !ent
->mConnInfo
->UsingConnect()) {
1084 return static_cast<uint32_t>(mMaxPersistConnsPerProxy
);
1087 return static_cast<uint32_t>(mMaxPersistConnsPerHost
);
1090 void nsHttpConnectionMgr::PreparePendingQForDispatching(
1091 ConnectionEntry
* ent
, nsTArray
<RefPtr
<PendingTransactionInfo
>>& pendingQ
,
1093 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1097 uint32_t totalCount
= ent
->TotalActiveConnections();
1098 uint32_t maxPersistConns
= MaxPersistConnections(ent
);
1099 uint32_t availableConnections
=
1100 maxPersistConns
> totalCount
? maxPersistConns
- totalCount
: 0;
1102 // No need to try dispatching if we reach the active connection limit.
1103 if (!availableConnections
) {
1107 // Only have to get transactions from the queue whose window id is 0.
1108 if (!gHttpHandler
->ActiveTabPriority()) {
1109 ent
->AppendPendingQForFocusedWindow(0, pendingQ
, availableConnections
);
1113 uint32_t maxFocusedWindowConnections
=
1114 availableConnections
* gHttpHandler
->FocusedWindowTransactionRatio();
1115 MOZ_ASSERT(maxFocusedWindowConnections
< availableConnections
);
1117 if (!maxFocusedWindowConnections
) {
1118 maxFocusedWindowConnections
= 1;
1121 // Only need to dispatch transactions for either focused or
1122 // non-focused window because considerAll is false.
1124 ent
->AppendPendingQForFocusedWindow(mCurrentBrowserId
, pendingQ
,
1125 maxFocusedWindowConnections
);
1127 if (pendingQ
.IsEmpty()) {
1128 ent
->AppendPendingQForNonFocusedWindows(mCurrentBrowserId
, pendingQ
,
1129 availableConnections
);
1134 uint32_t maxNonFocusedWindowConnections
=
1135 availableConnections
- maxFocusedWindowConnections
;
1136 nsTArray
<RefPtr
<PendingTransactionInfo
>> remainingPendingQ
;
1138 ent
->AppendPendingQForFocusedWindow(mCurrentBrowserId
, pendingQ
,
1139 maxFocusedWindowConnections
);
1141 if (maxNonFocusedWindowConnections
) {
1142 ent
->AppendPendingQForNonFocusedWindows(
1143 mCurrentBrowserId
, remainingPendingQ
, maxNonFocusedWindowConnections
);
1146 // If the slots for either focused or non-focused window are not filled up
1147 // to the availability, try to use the remaining available connections
1148 // for the other slot (with preference for the focused window).
1149 if (remainingPendingQ
.Length() < maxNonFocusedWindowConnections
) {
1150 ent
->AppendPendingQForFocusedWindow(
1151 mCurrentBrowserId
, pendingQ
,
1152 maxNonFocusedWindowConnections
- remainingPendingQ
.Length());
1153 } else if (pendingQ
.Length() < maxFocusedWindowConnections
) {
1154 ent
->AppendPendingQForNonFocusedWindows(
1155 mCurrentBrowserId
, remainingPendingQ
,
1156 maxFocusedWindowConnections
- pendingQ
.Length());
1159 MOZ_ASSERT(pendingQ
.Length() + remainingPendingQ
.Length() <=
1160 availableConnections
);
1163 ("nsHttpConnectionMgr::PreparePendingQForDispatching "
1164 "focused window pendingQ.Length()=%zu"
1165 ", remainingPendingQ.Length()=%zu\n",
1166 pendingQ
.Length(), remainingPendingQ
.Length()));
1168 // Append elements in |remainingPendingQ| to |pendingQ|. The order in
1169 // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
1170 pendingQ
.AppendElements(std::move(remainingPendingQ
));
1173 bool nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry
* ent
,
1175 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1178 ("nsHttpConnectionMgr::ProcessPendingQForEntry "
1179 "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
1181 ent
->mConnInfo
->HashKey().get(), ent
, ent
->ActiveConnsLength(),
1182 ent
->IdleConnectionsLength(), ent
->UrgentStartQueueLength(),
1183 ent
->PendingQueueLength()));
1185 if (LOG_ENABLED()) {
1186 ent
->PrintPendingQ();
1187 ent
->LogConnections();
1190 if (!ent
->PendingQueueLength() && !ent
->UrgentStartQueueLength()) {
1193 ProcessSpdyPendingQ(ent
);
1195 bool dispatchedSuccessfully
= false;
1197 if (ent
->UrgentStartQueueLength()) {
1198 nsTArray
<RefPtr
<PendingTransactionInfo
>> pendingQ
;
1199 ent
->AppendPendingUrgentStartQ(pendingQ
);
1200 dispatchedSuccessfully
= DispatchPendingQ(pendingQ
, ent
, considerAll
);
1201 for (const auto& transactionInfo
: Reversed(pendingQ
)) {
1202 ent
->InsertTransaction(transactionInfo
);
1206 if (dispatchedSuccessfully
&& !considerAll
) {
1207 return dispatchedSuccessfully
;
1210 nsTArray
<RefPtr
<PendingTransactionInfo
>> pendingQ
;
1211 PreparePendingQForDispatching(ent
, pendingQ
, considerAll
);
1213 // The only case that |pendingQ| is empty is when there is no
1214 // connection available for dispatching.
1215 if (pendingQ
.IsEmpty()) {
1216 return dispatchedSuccessfully
;
1219 dispatchedSuccessfully
|= DispatchPendingQ(pendingQ
, ent
, considerAll
);
1221 // Put the leftovers into connection entry, in the same order as they
1222 // were before to keep the natural ordering.
1223 for (const auto& transactionInfo
: Reversed(pendingQ
)) {
1224 ent
->InsertTransaction(transactionInfo
, true);
1227 // Only remove empty pendingQ when considerAll is true.
1229 ent
->RemoveEmptyPendingQ();
1232 return dispatchedSuccessfully
;
1235 bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo
* ci
) {
1236 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1238 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
1239 if (ent
) return ProcessPendingQForEntry(ent
, false);
1243 // we're at the active connection limit if any one of the following conditions
1245 // (1) at max-connections
1246 // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
1247 // (3) keep-alive disabled and at max-connections-per-server
1248 bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry
* ent
,
1250 nsHttpConnectionInfo
* ci
= ent
->mConnInfo
;
1251 uint32_t totalCount
= ent
->TotalActiveConnections();
1253 if (ci
->IsHttp3()) {
1254 if (ci
->GetWebTransport()) {
1255 // TODO: implement this properly in bug 1815735.
1258 return totalCount
> 0;
1261 uint32_t maxPersistConns
= MaxPersistConnections(ent
);
1264 ("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
1265 "totalCount=%u, maxPersistConns=%u]\n",
1266 ci
->HashKey().get(), caps
, totalCount
, maxPersistConns
));
1268 if (caps
& NS_HTTP_URGENT_START
) {
1269 if (totalCount
>= (mMaxUrgentExcessiveConns
+ maxPersistConns
)) {
1271 "The number of total connections are greater than or equal to sum of "
1272 "max urgent-start queue length and the number of max persistent "
1279 // update maxconns if potentially limited by the max socket count
1280 // this requires a dynamic reduction in the max socket count to a point
1281 // lower than the max-connections pref.
1282 uint32_t maxSocketCount
= gHttpHandler
->MaxSocketCount();
1283 if (mMaxConns
> maxSocketCount
) {
1284 mMaxConns
= maxSocketCount
;
1285 LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
1289 // If there are more active connections than the global limit, then we're
1290 // done. Purging idle connections won't get us below it.
1291 if (mNumActiveConns
>= mMaxConns
) {
1292 LOG((" num active conns == max conns\n"));
1296 bool result
= (totalCount
>= maxPersistConns
);
1297 LOG(("AtActiveConnectionLimit result: %s", result
? "true" : "false"));
1301 // returns NS_OK if a connection was started
1302 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
1304 // returns other NS_ERROR on hard failure conditions
1305 nsresult
nsHttpConnectionMgr::MakeNewConnection(
1306 ConnectionEntry
* ent
, PendingTransactionInfo
* pendingTransInfo
) {
1307 LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent
,
1308 pendingTransInfo
->Transaction()));
1309 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1311 if (ent
->FindConnToClaim(pendingTransInfo
)) {
1315 nsHttpTransaction
* trans
= pendingTransInfo
->Transaction();
1317 // If this host is trying to negotiate a SPDY session right now,
1318 // don't create any new connections until the result of the
1319 // negotiation is known.
1320 if (!(trans
->Caps() & NS_HTTP_DISALLOW_SPDY
) &&
1321 (trans
->Caps() & NS_HTTP_ALLOW_KEEPALIVE
) && ent
->RestrictConnections()) {
1323 ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
1324 "Not Available Due to RestrictConnections()\n",
1325 ent
->mConnInfo
->HashKey().get()));
1326 return NS_ERROR_NOT_AVAILABLE
;
1329 // We need to make a new connection. If that is going to exceed the
1330 // global connection limit then try and free up some room by closing
1331 // an idle connection to another host. We know it won't select "ent"
1332 // because we have already determined there are no idle connections
1333 // to our destination
1335 if ((mNumIdleConns
+ mNumActiveConns
+ 1 >= mMaxConns
) && mNumIdleConns
) {
1336 // If the global number of connections is preventing the opening of new
1337 // connections to a host without idle connections, then close them
1338 // regardless of their TTL.
1339 auto iter
= mCT
.ConstIter();
1340 while (mNumIdleConns
+ mNumActiveConns
+ 1 >= mMaxConns
&& !iter
.Done()) {
1341 RefPtr
<ConnectionEntry
> entry
= iter
.Data();
1342 entry
->CloseIdleConnections((mNumIdleConns
+ mNumActiveConns
+ 1) -
1348 if ((mNumIdleConns
+ mNumActiveConns
+ 1 >= mMaxConns
) && mNumActiveConns
&&
1349 StaticPrefs::network_http_http2_enabled()) {
1350 // If the global number of connections is preventing the opening of new
1351 // connections to a host without idle connections, then close any spdy
1353 for (const RefPtr
<ConnectionEntry
>& entry
: mCT
.Values()) {
1354 while (entry
->MakeFirstActiveSpdyConnDontReuse()) {
1355 // Stop on <= (particularly =) because this dontreuse
1356 // causes async close.
1357 if (mNumIdleConns
+ mNumActiveConns
+ 1 <= mMaxConns
) {
1365 if (AtActiveConnectionLimit(ent
, trans
->Caps())) {
1366 return NS_ERROR_NOT_AVAILABLE
;
1369 nsresult rv
= ent
->CreateDnsAndConnectSocket(
1370 trans
, trans
->Caps(), false, false,
1371 trans
->GetClassOfService().Flags() & nsIClassOfService::UrgentStart
, true,
1373 if (NS_FAILED(rv
)) {
1376 ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
1377 "CreateDnsAndConnectSocket() hard failure.\n",
1378 ent
->mConnInfo
->HashKey().get(), trans
));
1380 if (rv
== NS_ERROR_NOT_AVAILABLE
) rv
= NS_ERROR_FAILURE
;
1387 // returns OK if a connection is found for the transaction
1388 // and the transaction is started.
1389 // returns ERROR_NOT_AVAILABLE if no connection can be found and it
1390 // should be queued until circumstances change
1391 // returns other ERROR when transaction has a hard failure and should
1392 // not remain in the pending queue
1393 nsresult
nsHttpConnectionMgr::TryDispatchTransaction(
1394 ConnectionEntry
* ent
, bool onlyReusedConnection
,
1395 PendingTransactionInfo
* pendingTransInfo
) {
1396 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1398 nsHttpTransaction
* trans
= pendingTransInfo
->Transaction();
1401 ("nsHttpConnectionMgr::TryDispatchTransaction without conn "
1402 "[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu "
1404 trans
, ent
->mConnInfo
.get(), ent
->mConnInfo
->HashKey().get(),
1405 uint32_t(trans
->Caps()), onlyReusedConnection
, ent
->ActiveConnsLength(),
1406 ent
->IdleConnectionsLength()));
1408 uint32_t caps
= trans
->Caps();
1410 // 0 - If this should use spdy then dispatch it post haste.
1411 // 1 - If there is connection pressure then see if we can pipeline this on
1412 // a connection of a matching type instead of using a new conn
1413 // 2 - If there is an idle connection, use it!
1414 // 3 - if class == reval or script and there is an open conn of that type
1415 // then pipeline onto shortest pipeline of that class if limits allow
1416 // 4 - If we aren't up against our connection limit,
1417 // then open a new one
1418 // 5 - Try a pipeline if we haven't already - this will be unusual because
1419 // it implies a low connection pressure situation where
1420 // MakeNewConnection() failed.. that is possible, but unlikely, due to
1422 // 6 - no connection is available - queue it
1424 RefPtr
<HttpConnectionBase
> unusedSpdyPersistentConnection
;
1427 // look for existing spdy connection - that's always best because it is
1428 // essentially pipelining without head of line blocking
1430 RefPtr
<HttpConnectionBase
> conn
= GetH2orH3ActiveConn(
1432 (!StaticPrefs::network_http_http2_enabled() ||
1433 (caps
& NS_HTTP_DISALLOW_SPDY
)),
1434 (!nsHttpHandler::IsHttp3Enabled() || (caps
& NS_HTTP_DISALLOW_HTTP3
)));
1436 LOG(("TryingDispatchTransaction: an active h2 connection exists"));
1437 WebSocketSupport wsSupp
= conn
->GetWebSocketSupport();
1438 if (trans
->IsWebsocketUpgrade()) {
1439 LOG(("TryingDispatchTransaction: this is a websocket upgrade"));
1440 if (wsSupp
== WebSocketSupport::NO_SUPPORT
) {
1442 "TryingDispatchTransaction: no support for websockets over Http2"));
1443 // This is a websocket transaction and we already have a h2 connection
1444 // that do not support websockets, we should disable h2 for this
1446 trans
->DisableSpdy();
1447 caps
&= NS_HTTP_DISALLOW_SPDY
;
1448 trans
->MakeSticky();
1449 } else if (wsSupp
== WebSocketSupport::SUPPORTED
) {
1450 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(conn
);
1451 LOG(("TryingDispatchTransaction: websockets over Http2"));
1453 // No limit for number of websockets, dispatch transaction to the tunnel
1454 RefPtr
<nsHttpConnection
> connToTunnel
;
1455 connTCP
->CreateTunnelStream(trans
, getter_AddRefs(connToTunnel
), true);
1456 ent
->InsertIntoH2WebsocketConns(connToTunnel
);
1457 trans
->SetConnection(nullptr);
1458 connToTunnel
->SetInSpdyTunnel(); // tells conn it is already in tunnel
1459 trans
->SetIsHttp2Websocket(true);
1460 nsresult rv
= DispatchTransaction(ent
, trans
, connToTunnel
);
1461 // need to undo NonSticky bypass for transaction reset to continue
1462 // for correct websocket upgrade handling
1463 trans
->MakeSticky();
1466 // if we aren't sure that websockets are supported yet or we are
1467 // already at the connection limit then we queue the transaction
1468 LOG(("TryingDispatchTransaction: unsure if websockets over Http2"));
1469 return NS_ERROR_NOT_AVAILABLE
;
1472 if ((caps
& NS_HTTP_ALLOW_KEEPALIVE
) ||
1473 (caps
& NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE
) ||
1474 !conn
->IsExperienced()) {
1475 LOG((" dispatch to spdy: [conn=%p]\n", conn
.get()));
1476 trans
->RemoveDispatchedAsBlocking(); /* just in case */
1477 nsresult rv
= DispatchTransaction(ent
, trans
, conn
);
1478 NS_ENSURE_SUCCESS(rv
, rv
);
1481 unusedSpdyPersistentConnection
= conn
;
1485 // If this is not a blocking transaction and the request context for it is
1486 // currently processing one or more blocking transactions then we
1487 // need to just leave it in the queue until those are complete unless it is
1488 // explicitly marked as unblocked.
1489 if (!(caps
& NS_HTTP_LOAD_AS_BLOCKING
)) {
1490 if (!(caps
& NS_HTTP_LOAD_UNBLOCKED
)) {
1491 nsIRequestContext
* requestContext
= trans
->RequestContext();
1492 if (requestContext
) {
1493 uint32_t blockers
= 0;
1495 requestContext
->GetBlockingTransactionCount(&blockers
)) &&
1497 // need to wait for blockers to clear
1498 LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
1499 requestContext
, trans
, blockers
));
1500 return NS_ERROR_NOT_AVAILABLE
;
1505 // Mark the transaction and its load group as blocking right now to prevent
1506 // other transactions from being reordered in the queue due to slow syns.
1507 trans
->DispatchedAsBlocking();
1511 // If connection pressure, then we want to favor pipelining of any kind
1512 // h1 pipelining has been removed
1514 // Subject most transactions at high parallelism to rate pacing.
1515 // It will only be actually submitted to the
1516 // token bucket once, and if possible it is granted admission synchronously.
1517 // It is important to leave a transaction in the pending queue when blocked by
1518 // pacing so it can be found on cancel if necessary.
1519 // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
1521 if (gHttpHandler
->UseRequestTokenBucket()) {
1522 // submit even whitelisted transactions to the token bucket though they will
1523 // not be slowed by it
1524 bool runNow
= trans
->TryToRunPacedRequest();
1526 if ((mNumActiveConns
- mNumSpdyHttp3ActiveConns
) <=
1527 gHttpHandler
->RequestTokenBucketMinParallelism()) {
1528 runNow
= true; // white list it
1529 } else if (caps
& (NS_HTTP_LOAD_AS_BLOCKING
| NS_HTTP_LOAD_UNBLOCKED
)) {
1530 runNow
= true; // white list it
1534 LOG((" blocked due to rate pacing trans=%p\n", trans
));
1535 return NS_ERROR_NOT_AVAILABLE
;
1540 // consider an idle persistent connection
1541 bool idleConnsAllUrgent
= false;
1542 if (caps
& NS_HTTP_ALLOW_KEEPALIVE
) {
1543 nsresult rv
= TryDispatchTransactionOnIdleConn(ent
, pendingTransInfo
, true,
1544 &idleConnsAllUrgent
);
1545 if (NS_SUCCEEDED(rv
)) {
1546 LOG((" dispatched step 2 (idle) trans=%p\n", trans
));
1552 // consider pipelining scripts and revalidations
1553 // h1 pipelining has been removed
1555 // Don't dispatch if this transaction is waiting for HTTPS RR.
1556 // This usually happens when the pref "network.dns.force_waiting_https_rr" is
1557 // true or when echConfig is enabled.
1558 if (trans
->WaitingForHTTPSRR()) {
1559 return NS_ERROR_NOT_AVAILABLE
;
1563 if (!onlyReusedConnection
) {
1564 nsresult rv
= MakeNewConnection(ent
, pendingTransInfo
);
1565 if (NS_SUCCEEDED(rv
)) {
1566 // this function returns NOT_AVAILABLE for asynchronous connects
1567 LOG((" dispatched step 4 (async new conn) trans=%p\n", trans
));
1568 return NS_ERROR_NOT_AVAILABLE
;
1571 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
1572 // not available return codes should try next step as they are
1573 // not hard errors. Other codes should stop now
1574 LOG((" failed step 4 (%" PRIx32
") trans=%p\n",
1575 static_cast<uint32_t>(rv
), trans
));
1579 // repeat step 2 when there are only idle connections and all are urgent,
1580 // don't respect urgency so that non-urgent transaction will be allowed
1581 // to dispatch on an urgent-start-only marked connection to avoid
1582 // dispatch deadlocks
1583 if (!(trans
->GetClassOfService().Flags() &
1584 nsIClassOfService::UrgentStart
) &&
1585 idleConnsAllUrgent
&&
1586 ent
->ActiveConnsLength() < MaxPersistConnections(ent
)) {
1587 rv
= TryDispatchTransactionOnIdleConn(ent
, pendingTransInfo
, false);
1588 if (NS_SUCCEEDED(rv
)) {
1589 LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans
));
1596 // previously pipelined anything here if allowed but h1 pipelining has been
1600 if (unusedSpdyPersistentConnection
) {
1601 // to avoid deadlocks, we need to throw away this perfectly valid SPDY
1602 // connection to make room for a new one that can service a no KEEPALIVE
1604 unusedSpdyPersistentConnection
->DontReuse();
1607 LOG((" not dispatched (queued) trans=%p\n", trans
));
1608 return NS_ERROR_NOT_AVAILABLE
; /* queue it */
1611 nsresult
nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn(
1612 ConnectionEntry
* ent
, PendingTransactionInfo
* pendingTransInfo
,
1613 bool respectUrgency
, bool* allUrgent
) {
1614 bool onlyUrgent
= !!ent
->IdleConnectionsLength();
1616 nsHttpTransaction
* trans
= pendingTransInfo
->Transaction();
1618 trans
->GetClassOfService().Flags() & nsIClassOfService::UrgentStart
;
1621 ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
1622 "trans=%p, urgent=%d",
1623 ent
, trans
, urgentTrans
));
1625 RefPtr
<nsHttpConnection
> conn
=
1626 ent
->GetIdleConnection(respectUrgency
, urgentTrans
, &onlyUrgent
);
1629 *allUrgent
= onlyUrgent
;
1633 // This will update the class of the connection to be the class of
1634 // the transaction dispatched on it.
1635 ent
->InsertIntoActiveConns(conn
);
1636 nsresult rv
= DispatchTransaction(ent
, trans
, conn
);
1637 NS_ENSURE_SUCCESS(rv
, rv
);
1642 return NS_ERROR_NOT_AVAILABLE
;
1645 nsresult
nsHttpConnectionMgr::DispatchTransaction(ConnectionEntry
* ent
,
1646 nsHttpTransaction
* trans
,
1647 HttpConnectionBase
* conn
) {
1648 uint32_t caps
= trans
->Caps();
1649 int32_t priority
= trans
->Priority();
1653 ("nsHttpConnectionMgr::DispatchTransaction "
1654 "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
1656 ent
->mConnInfo
->HashKey().get(), ent
, trans
, caps
, conn
, priority
,
1657 conn
->UsingSpdy(), conn
->UsingHttp3()));
1659 // It is possible for a rate-paced transaction to be dispatched independent
1660 // of the token bucket when the amount of parallelization has changed or
1661 // when a muxed connection (e.g. h2) becomes available.
1662 trans
->CancelPacing(NS_OK
);
1664 TimeStamp now
= TimeStamp::Now();
1665 TimeDuration elapsed
= now
- trans
->GetPendingTime();
1666 auto recordPendingTimeForHTTPSRR
= [&](nsCString
& aKey
) {
1667 uint32_t stage
= trans
->HTTPSSVCReceivedStage();
1668 if (HTTPS_RR_IS_USED(stage
)) {
1669 glean::networking::transaction_wait_time_https_rr
.AccumulateRawDuration(
1673 glean::networking::transaction_wait_time
.AccumulateRawDuration(elapsed
);
1677 PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime
,
1681 "DispatchTransaction", NETWORK
,
1682 MarkerOptions(MarkerThreadId::MainThread(),
1683 MarkerTiming::Interval(trans
->GetPendingTime(), now
)),
1684 UrlMarker
, trans
->GetUrl(), elapsed
, trans
->ChannelId());
1686 nsAutoCString
httpVersionkey("h1"_ns
);
1687 if (conn
->UsingSpdy() || conn
->UsingHttp3()) {
1689 ("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
1690 "Connection host = %s\n",
1691 trans
->ConnectionInfo()->Origin(), conn
->ConnectionInfo()->Origin()));
1692 rv
= conn
->Activate(trans
, caps
, priority
);
1693 if (NS_SUCCEEDED(rv
) && !trans
->GetPendingTime().IsNull()) {
1694 if (conn
->UsingSpdy()) {
1695 httpVersionkey
= "h2"_ns
;
1696 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY
,
1697 trans
->GetPendingTime(), now
);
1699 httpVersionkey
= "h3"_ns
;
1700 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3
,
1701 trans
->GetPendingTime(), now
);
1703 recordPendingTimeForHTTPSRR(httpVersionkey
);
1704 trans
->SetPendingTime(false);
1709 MOZ_ASSERT(conn
&& !conn
->Transaction(),
1710 "DispatchTranaction() on non spdy active connection");
1712 rv
= DispatchAbstractTransaction(ent
, trans
, caps
, conn
, priority
);
1714 if (NS_SUCCEEDED(rv
) && !trans
->GetPendingTime().IsNull()) {
1715 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP
,
1716 trans
->GetPendingTime(), now
);
1717 recordPendingTimeForHTTPSRR(httpVersionkey
);
1718 trans
->SetPendingTime(false);
1723 // Use this method for dispatching nsAHttpTransction's. It can only safely be
1724 // used upon first use of a connection when NPN has not negotiated SPDY vs
1725 // HTTP/1 yet as multiplexing onto an existing SPDY session requires a
1726 // concrete nsHttpTransaction
1727 nsresult
nsHttpConnectionMgr::DispatchAbstractTransaction(
1728 ConnectionEntry
* ent
, nsAHttpTransaction
* aTrans
, uint32_t caps
,
1729 HttpConnectionBase
* conn
, int32_t priority
) {
1733 MOZ_ASSERT(!conn
->UsingSpdy(),
1734 "Spdy Must Not Use DispatchAbstractTransaction");
1736 ("nsHttpConnectionMgr::DispatchAbstractTransaction "
1737 "[ci=%s trans=%p caps=%x conn=%p]\n",
1738 ent
->mConnInfo
->HashKey().get(), aTrans
, caps
, conn
));
1740 RefPtr
<nsAHttpTransaction
> transaction(aTrans
);
1741 RefPtr
<ConnectionHandle
> handle
= new ConnectionHandle(conn
);
1743 // give the transaction the indirect reference to the connection.
1744 transaction
->SetConnection(handle
);
1746 rv
= conn
->Activate(transaction
, caps
, priority
);
1747 if (NS_FAILED(rv
)) {
1748 LOG((" conn->Activate failed [rv=%" PRIx32
"]\n",
1749 static_cast<uint32_t>(rv
)));
1750 DebugOnly
<nsresult
> rv_remove
= ent
->RemoveActiveConnection(conn
);
1751 MOZ_ASSERT(NS_SUCCEEDED(rv_remove
));
1753 // sever back references to connection, and do so without triggering
1754 // a call to ReclaimConnection ;-)
1755 transaction
->SetConnection(nullptr);
1756 handle
->Reset(); // destroy the connection
1762 void nsHttpConnectionMgr::ReportProxyTelemetry(ConnectionEntry
* ent
) {
1763 enum { PROXY_NONE
= 1, PROXY_HTTP
= 2, PROXY_SOCKS
= 3, PROXY_HTTPS
= 4 };
1765 if (!ent
->mConnInfo
->UsingProxy()) {
1766 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE
, PROXY_NONE
);
1767 } else if (ent
->mConnInfo
->UsingHttpsProxy()) {
1768 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE
, PROXY_HTTPS
);
1769 } else if (ent
->mConnInfo
->UsingHttpProxy()) {
1770 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE
, PROXY_HTTP
);
1772 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE
, PROXY_SOCKS
);
1776 nsresult
nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction
* trans
) {
1777 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1779 // since "adds" and "cancels" are processed asynchronously and because
1780 // various events might trigger an "add" directly on the socket thread,
1781 // we must take care to avoid dispatching a transaction that has already
1782 // been canceled (see bug 190001).
1783 if (NS_FAILED(trans
->Status())) {
1784 LOG((" transaction was canceled... dropping event!\n"));
1788 // Make sure a transaction is not in a pending queue.
1789 CheckTransInPendingQueue(trans
);
1791 trans
->SetPendingTime();
1793 PROFILER_MARKER("ProcessNewTransaction", NETWORK
,
1794 MarkerThreadId::MainThread(), UrlMarker
, trans
->GetUrl(),
1795 TimeDuration::Zero(), trans
->ChannelId());
1797 RefPtr
<Http2PushedStreamWrapper
> pushedStreamWrapper
=
1798 trans
->GetPushedStream();
1799 if (pushedStreamWrapper
) {
1800 Http2PushedStream
* pushedStream
= pushedStreamWrapper
->GetStream();
1802 RefPtr
<Http2Session
> session
= pushedStream
->Session();
1803 LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans
,
1805 return session
->AddStream(trans
, trans
->Priority(), nullptr)
1807 : NS_ERROR_UNEXPECTED
;
1811 nsresult rv
= NS_OK
;
1812 nsHttpConnectionInfo
* ci
= trans
->ConnectionInfo();
1814 MOZ_ASSERT(!ci
->IsHttp3() || !(trans
->Caps() & NS_HTTP_DISALLOW_HTTP3
));
1816 bool isWildcard
= false;
1817 ConnectionEntry
* ent
= GetOrCreateConnectionEntry(
1818 ci
, trans
->Caps() & NS_HTTP_DISALLOW_HTTP2_PROXY
,
1819 trans
->Caps() & NS_HTTP_DISALLOW_SPDY
,
1820 trans
->Caps() & NS_HTTP_DISALLOW_HTTP3
, &isWildcard
);
1823 if (gHttpHandler
->EchConfigEnabled(ci
->IsHttp3())) {
1824 ent
->MaybeUpdateEchConfig(ci
);
1827 ReportProxyTelemetry(ent
);
1829 // Check if the transaction already has a sticky reference to a connection.
1830 // If so, then we can just use it directly by transferring its reference
1831 // to the new connection variable instead of searching for a new one
1833 nsAHttpConnection
* wrappedConnection
= trans
->Connection();
1834 RefPtr
<HttpConnectionBase
> conn
;
1835 RefPtr
<PendingTransactionInfo
> pendingTransInfo
;
1836 if (wrappedConnection
) conn
= wrappedConnection
->TakeHttpConnection();
1839 MOZ_ASSERT(trans
->Caps() & NS_HTTP_STICKY_CONNECTION
);
1841 ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
1842 "sticky connection=%p\n",
1843 trans
, conn
.get()));
1845 if (!ent
->IsInActiveConns(conn
)) {
1847 ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
1848 "sticky connection=%p needs to go on the active list\n",
1849 trans
, conn
.get()));
1851 // make sure it isn't on the idle list - we expect this to be an
1852 // unknown fresh connection
1853 MOZ_ASSERT(!ent
->IsInIdleConnections(conn
));
1854 MOZ_ASSERT(!conn
->IsExperienced());
1856 ent
->InsertIntoActiveConns(conn
); // make it active
1859 trans
->SetConnection(nullptr);
1860 rv
= DispatchTransaction(ent
, trans
, conn
);
1861 } else if (isWildcard
) {
1862 // We have a HTTP/2 session to the proxy, create a new tunneled
1864 RefPtr
<HttpConnectionBase
> conn
= GetH2orH3ActiveConn(ent
, false, true);
1865 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(conn
);
1866 if (ci
->UsingHttpsProxy() && ci
->UsingConnect()) {
1867 LOG(("About to create new tunnel conn from [%p]", connTCP
.get()));
1868 ConnectionEntry
* specificEnt
= mCT
.GetWeak(ci
->HashKey());
1871 RefPtr
<nsHttpConnectionInfo
> clone(ci
->Clone());
1872 specificEnt
= new ConnectionEntry(clone
);
1873 mCT
.InsertOrUpdate(clone
->HashKey(), RefPtr
{specificEnt
});
1877 bool atLimit
= AtActiveConnectionLimit(ent
, trans
->Caps());
1879 rv
= NS_ERROR_NOT_AVAILABLE
;
1881 RefPtr
<nsHttpConnection
> newTunnel
;
1882 connTCP
->CreateTunnelStream(trans
, getter_AddRefs(newTunnel
));
1884 ent
->InsertIntoActiveConns(newTunnel
);
1885 trans
->SetConnection(nullptr);
1886 newTunnel
->SetInSpdyTunnel();
1887 rv
= DispatchTransaction(ent
, trans
, newTunnel
);
1888 // need to undo the bypass for transaction reset for proxy
1889 trans
->MakeNonRestartable();
1892 rv
= DispatchTransaction(ent
, trans
, connTCP
);
1895 if (!ent
->AllowHttp2()) {
1896 trans
->DisableSpdy();
1898 pendingTransInfo
= new PendingTransactionInfo(trans
);
1899 rv
= TryDispatchTransaction(ent
, false, pendingTransInfo
);
1902 if (NS_SUCCEEDED(rv
)) {
1903 LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans
));
1907 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
1908 if (!pendingTransInfo
) {
1909 pendingTransInfo
= new PendingTransactionInfo(trans
);
1912 ent
->InsertTransaction(pendingTransInfo
);
1916 LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32
"\n", trans
,
1917 static_cast<uint32_t>(rv
)));
1921 void nsHttpConnectionMgr::IncrementActiveConnCount() {
1923 ActivateTimeoutTick();
1926 void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase
* conn
) {
1927 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns
> 0);
1928 if (mNumActiveConns
> 0) {
1932 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(conn
);
1933 if (!connTCP
|| connTCP
->EverUsedSpdy()) mNumSpdyHttp3ActiveConns
--;
1934 ConditionallyStopTimeoutTick();
1937 void nsHttpConnectionMgr::StartedConnect() {
1939 ActivateTimeoutTick(); // likely disabled by RecvdConnect()
1942 void nsHttpConnectionMgr::RecvdConnect() {
1943 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns
> 0);
1944 if (mNumActiveConns
> 0) {
1948 ConditionallyStopTimeoutTick();
1951 void nsHttpConnectionMgr::DispatchSpdyPendingQ(
1952 nsTArray
<RefPtr
<PendingTransactionInfo
>>& pendingQ
, ConnectionEntry
* ent
,
1953 HttpConnectionBase
* connH2
, HttpConnectionBase
* connH3
) {
1954 if (pendingQ
.Length() == 0) {
1958 nsTArray
<RefPtr
<PendingTransactionInfo
>> leftovers
;
1960 // Dispatch all the transactions we can
1961 for (index
= 0; index
< pendingQ
.Length() &&
1962 ((connH3
&& connH3
->CanDirectlyActivate()) ||
1963 (connH2
&& connH2
->CanDirectlyActivate()));
1965 PendingTransactionInfo
* pendingTransInfo
= pendingQ
[index
];
1967 // We can not dispatch NS_HTTP_ALLOW_KEEPALIVE transactions.
1968 if (!(pendingTransInfo
->Transaction()->Caps() & NS_HTTP_ALLOW_KEEPALIVE
)) {
1969 leftovers
.AppendElement(pendingTransInfo
);
1973 // Try dispatching on HTTP3 first
1974 HttpConnectionBase
* conn
= nullptr;
1975 if (!(pendingTransInfo
->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3
) &&
1976 connH3
&& connH3
->CanDirectlyActivate()) {
1978 } else if (!(pendingTransInfo
->Transaction()->Caps() &
1979 NS_HTTP_DISALLOW_SPDY
) &&
1980 connH2
&& connH2
->CanDirectlyActivate()) {
1983 leftovers
.AppendElement(pendingTransInfo
);
1988 DispatchTransaction(ent
, pendingTransInfo
->Transaction(), conn
);
1989 if (NS_FAILED(rv
)) {
1990 // this cannot happen, but if due to some bug it does then
1991 // close the transaction
1992 MOZ_ASSERT(false, "Dispatch SPDY Transaction");
1993 LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
1994 pendingTransInfo
->Transaction()));
1995 pendingTransInfo
->Transaction()->Close(rv
);
1999 // Slurp up the rest of the pending queue into our leftovers bucket (we
2000 // might have some left if conn->CanDirectlyActivate returned false)
2001 for (; index
< pendingQ
.Length(); ++index
) {
2002 PendingTransactionInfo
* pendingTransInfo
= pendingQ
[index
];
2003 leftovers
.AppendElement(pendingTransInfo
);
2006 // Put the leftovers back in the pending queue and get rid of the
2007 // transactions we dispatched
2008 pendingQ
= std::move(leftovers
);
2011 // This function tries to dispatch the pending h2 or h3 transactions on
2012 // the connection entry sent in as an argument. It will do so on the
2013 // active h2 or h3 connection either in that same entry or from the
2014 // coalescing hash table
2015 void nsHttpConnectionMgr::ProcessSpdyPendingQ(ConnectionEntry
* ent
) {
2016 // Look for one HTTP2 and one HTTP3 connection.
2017 // We may have transactions that have NS_HTTP_DISALLOW_SPDY/HTTP3 set
2018 // and we may need an alternative.
2019 HttpConnectionBase
* connH3
= GetH2orH3ActiveConn(ent
, true, false);
2020 HttpConnectionBase
* connH2
= GetH2orH3ActiveConn(ent
, false, true);
2021 if ((!connH3
|| !connH3
->CanDirectlyActivate()) &&
2022 (!connH2
|| !connH2
->CanDirectlyActivate())) {
2026 nsTArray
<RefPtr
<PendingTransactionInfo
>> urgentQ
;
2027 ent
->AppendPendingUrgentStartQ(urgentQ
);
2028 DispatchSpdyPendingQ(urgentQ
, ent
, connH2
, connH3
);
2029 for (const auto& transactionInfo
: Reversed(urgentQ
)) {
2030 ent
->InsertTransaction(transactionInfo
);
2033 if ((!connH3
|| !connH3
->CanDirectlyActivate()) &&
2034 (!connH2
|| !connH2
->CanDirectlyActivate())) {
2038 nsTArray
<RefPtr
<PendingTransactionInfo
>> pendingQ
;
2039 // XXX Get all transactions for SPDY currently.
2040 ent
->AppendPendingQForNonFocusedWindows(0, pendingQ
);
2041 DispatchSpdyPendingQ(pendingQ
, ent
, connH2
, connH3
);
2043 // Put the leftovers back in the pending queue.
2044 for (const auto& transactionInfo
: pendingQ
) {
2045 ent
->InsertTransaction(transactionInfo
);
2049 void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase
*) {
2050 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2051 LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
2052 for (const auto& entry
: mCT
.Values()) {
2053 ProcessSpdyPendingQ(entry
.get());
2057 // Given a connection entry, return an active h2 or h3 connection
2058 // that can be directly activated or null.
2059 HttpConnectionBase
* nsHttpConnectionMgr::GetH2orH3ActiveConn(
2060 ConnectionEntry
* ent
, bool aNoHttp2
, bool aNoHttp3
) {
2061 if (aNoHttp2
&& aNoHttp3
) {
2064 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2067 // First look at ent. If protocol that ent provides is no forbidden,
2068 // i.e. ent use HTTP3 and !aNoHttp3 or en uses HTTP over TCP and !aNoHttp2.
2069 if ((!aNoHttp3
&& ent
->IsHttp3()) || (!aNoHttp2
&& !ent
->IsHttp3())) {
2070 HttpConnectionBase
* conn
= ent
->GetH2orH3ActiveConn();
2076 nsHttpConnectionInfo
* ci
= ent
->mConnInfo
;
2078 // there was no active HTTP2/3 connection in the connection entry, but
2079 // there might be one in the hash table for coalescing
2080 HttpConnectionBase
* existingConn
=
2081 FindCoalescableConnection(ent
, false, aNoHttp2
, aNoHttp3
);
2084 ("GetH2orH3ActiveConn() request for ent %p %s "
2085 "found an active connection %p in the coalescing hashtable\n",
2086 ent
, ci
->HashKey().get(), existingConn
));
2087 return existingConn
;
2091 ("GetH2orH3ActiveConn() request for ent %p %s "
2092 "did not find an active connection\n",
2093 ent
, ci
->HashKey().get()));
2097 //-----------------------------------------------------------------------------
2099 void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase
*) {
2100 if (!OnSocketThread()) {
2101 Unused
<< PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections
);
2105 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2106 LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
2107 for (auto iter
= mCT
.Iter(); !iter
.Done(); iter
.Next()) {
2108 RefPtr
<ConnectionEntry
> ent
= iter
.Data();
2110 // Close all active connections.
2111 ent
->CloseActiveConnections();
2113 // Close all idle connections.
2114 ent
->CloseIdleConnections();
2116 // Close websocket "fake" connections
2117 ent
->CloseH2WebsocketConnections();
2119 ent
->ClosePendingConnections();
2121 // Close all pending transactions.
2122 ent
->CancelAllTransactions(NS_ERROR_ABORT
);
2124 // Close all half open tcp connections.
2125 ent
->CloseAllDnsAndConnectSockets();
2127 MOZ_ASSERT(!ent
->mDoNotDestroy
);
2131 mActiveTransactions
[false].Clear();
2132 mActiveTransactions
[true].Clear();
2135 void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase
* param
) {
2136 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2137 LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
2139 gHttpHandler
->StopRequestTokenBucket();
2140 AbortAndCloseAllConnections(0, nullptr);
2142 // If all idle connections are removed we can stop pruning dead
2144 ConditionallyStopPruneDeadConnectionsTimer();
2147 mTimeoutTick
->Cancel();
2148 mTimeoutTick
= nullptr;
2149 mTimeoutTickArmed
= false;
2155 if (mTrafficTimer
) {
2156 mTrafficTimer
->Cancel();
2157 mTrafficTimer
= nullptr;
2159 DestroyThrottleTicker();
2161 mCoalescingHash
.Clear();
2163 // signal shutdown complete
2164 nsCOMPtr
<nsIRunnable
> runnable
=
2165 new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm
, 0, param
);
2166 NS_DispatchToMainThread(runnable
);
2169 void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority
,
2171 MOZ_ASSERT(NS_IsMainThread());
2172 LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
2174 BoolWrapper
* shutdown
= static_cast<BoolWrapper
*>(param
);
2175 shutdown
->mBool
= true;
2178 void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority
,
2180 nsHttpTransaction
* trans
= static_cast<nsHttpTransaction
*>(param
);
2182 LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans
));
2183 trans
->SetPriority(priority
);
2184 nsresult rv
= ProcessNewTransaction(trans
);
2185 if (NS_FAILED(rv
)) trans
->Close(rv
); // for whatever its worth
2188 void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority
,
2190 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2192 NewTransactionData
* data
= static_cast<NewTransactionData
*>(param
);
2194 ("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn "
2195 "[trans=%p, transWithStickyConn=%p, conn=%p]\n",
2196 data
->mTrans
.get(), data
->mTransWithStickyConn
.get(),
2197 data
->mTransWithStickyConn
->Connection()));
2199 MOZ_ASSERT(data
->mTransWithStickyConn
&&
2200 data
->mTransWithStickyConn
->Caps() & NS_HTTP_STICKY_CONNECTION
);
2202 data
->mTrans
->SetPriority(data
->mPriority
);
2204 RefPtr
<nsAHttpConnection
> conn
= data
->mTransWithStickyConn
->Connection();
2205 if (conn
&& conn
->IsPersistent()) {
2206 // This is so far a workaround to only reuse persistent
2207 // connection for authentication retry. See bug 459620 comment 4
2209 LOG((" Reuse connection [%p] for transaction [%p]", conn
.get(),
2210 data
->mTrans
.get()));
2211 data
->mTrans
->SetConnection(conn
);
2214 nsresult rv
= ProcessNewTransaction(data
->mTrans
);
2215 if (NS_FAILED(rv
)) {
2216 data
->mTrans
->Close(rv
); // for whatever its worth
2220 void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority
,
2222 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2223 LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param
));
2225 RefPtr
<nsHttpTransaction
> trans
= static_cast<nsHttpTransaction
*>(param
);
2226 trans
->SetPriority(priority
);
2228 if (!trans
->ConnectionInfo()) {
2231 ConnectionEntry
* ent
= mCT
.GetWeak(trans
->ConnectionInfo()->HashKey());
2234 ent
->ReschedTransaction(trans
);
2238 void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
2239 ClassOfService cos
, ARefBase
* param
) {
2240 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2242 ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
2246 nsHttpTransaction
* trans
= static_cast<nsHttpTransaction
*>(param
);
2248 ClassOfService previous
= trans
->GetClassOfService();
2249 trans
->SetClassOfService(cos
);
2251 // incremental change alone will not trigger a reschedule
2252 if ((previous
.Flags() ^ cos
.Flags()) &
2253 (NS_HTTP_LOAD_AS_BLOCKING
| NS_HTTP_LOAD_UNBLOCKED
)) {
2254 Unused
<< RescheduleTransaction(trans
, trans
->Priority());
2258 void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason
,
2260 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2261 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param
));
2263 nsresult closeCode
= static_cast<nsresult
>(reason
);
2265 // caller holds a ref to param/trans on stack
2266 nsHttpTransaction
* trans
= static_cast<nsHttpTransaction
*>(param
);
2269 // if the transaction owns a connection and the transaction is not done,
2270 // then ask the connection to close the transaction. otherwise, close the
2271 // transaction directly (removing it from the pending queue first).
2273 RefPtr
<nsAHttpConnection
> conn(trans
->Connection());
2274 if (conn
&& !trans
->IsDone()) {
2275 conn
->CloseTransaction(trans
, closeCode
);
2277 ConnectionEntry
* ent
= nullptr;
2278 if (trans
->ConnectionInfo()) {
2279 ent
= mCT
.GetWeak(trans
->ConnectionInfo()->HashKey());
2281 if (ent
&& ent
->RemoveTransFromPendingQ(trans
)) {
2283 ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
2284 " removed from pending queue\n",
2288 trans
->Close(closeCode
);
2290 // Cancel is a pretty strong signal that things might be hanging
2291 // so we want to cancel any null transactions related to this connection
2292 // entry. They are just optimizations, but they aren't hooked up to
2293 // anything that might get canceled from the rest of gecko, so best
2294 // to assume that's what was meant by the cancel we did receive if
2295 // it only applied to something in the queue.
2297 ent
->CloseAllActiveConnsWithNullTransactcion(closeCode
);
2302 void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase
* param
) {
2303 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2304 nsHttpConnectionInfo
* ci
= static_cast<nsHttpConnectionInfo
*>(param
);
2307 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
2308 // Try and dispatch everything
2309 for (const auto& entry
: mCT
.Values()) {
2310 Unused
<< ProcessPendingQForEntry(entry
.get(), true);
2315 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
2316 ci
->HashKey().get()));
2318 // start by processing the queue identified by the given connection info.
2319 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
2320 if (!(ent
&& ProcessPendingQForEntry(ent
, false))) {
2321 // if we reach here, it means that we couldn't dispatch a transaction
2322 // for the specified connection info. walk the connection table...
2323 for (const auto& entry
: mCT
.Values()) {
2324 if (ProcessPendingQForEntry(entry
.get(), false)) {
2331 nsresult
nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo
* ci
,
2333 LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci
->HashKey().get()));
2335 int32_t intReason
= static_cast<int32_t>(code
);
2336 return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions
, intReason
,
2340 void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code
,
2342 nsresult reason
= static_cast<nsresult
>(code
);
2343 nsHttpConnectionInfo
* ci
= static_cast<nsHttpConnectionInfo
*>(param
);
2344 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
2345 LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
2346 ci
->HashKey().get(), ent
));
2348 ent
->CancelAllTransactions(reason
);
2352 void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase
*) {
2353 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2354 LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
2356 // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
2357 mTimeOfNextWakeUp
= UINT64_MAX
;
2359 // check canreuse() for all idle connections plus any active connections on
2360 // connection entries that are using spdy.
2361 if (mNumIdleConns
||
2362 (mNumActiveConns
&& StaticPrefs::network_http_http2_enabled())) {
2363 for (auto iter
= mCT
.Iter(); !iter
.Done(); iter
.Next()) {
2364 RefPtr
<ConnectionEntry
> ent
= iter
.Data();
2366 LOG((" pruning [ci=%s]\n", ent
->mConnInfo
->HashKey().get()));
2368 // Find out how long it will take for next idle connection to not
2369 // be reusable anymore.
2370 uint32_t timeToNextExpire
= ent
->PruneDeadConnections();
2372 // If time to next expire found is shorter than time to next
2373 // wake-up, we need to change the time for next wake-up.
2374 if (timeToNextExpire
!= UINT32_MAX
) {
2375 uint32_t now
= NowInSeconds();
2376 uint64_t timeOfNextExpire
= now
+ timeToNextExpire
;
2377 // If pruning of dead connections is not already scheduled to
2378 // happen or time found for next connection to expire is is
2379 // before mTimeOfNextWakeUp, we need to schedule the pruning to
2380 // happen after timeToNextExpire.
2381 if (!mTimer
|| timeOfNextExpire
< mTimeOfNextWakeUp
) {
2382 PruneDeadConnectionsAfter(timeToNextExpire
);
2385 ConditionallyStopPruneDeadConnectionsTimer();
2388 ent
->RemoveEmptyPendingQ();
2390 // If this entry is empty, we have too many entries busy then
2391 // we can clean it up and restart
2392 if (mCT
.Count() > 125 && ent
->IdleConnectionsLength() == 0 &&
2393 ent
->ActiveConnsLength() == 0 &&
2394 ent
->DnsAndConnectSocketsLength() == 0 &&
2395 ent
->PendingQueueLength() == 0 &&
2396 ent
->UrgentStartQueueLength() == 0 && !ent
->mDoNotDestroy
&&
2397 (!ent
->mUsingSpdy
|| mCT
.Count() > 300)) {
2398 LOG((" removing empty connection entry\n"));
2403 // Otherwise use this opportunity to compact our arrays...
2409 void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase
*) {
2410 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2411 LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
2413 // Prune connections without traffic
2414 for (const RefPtr
<ConnectionEntry
>& ent
: mCT
.Values()) {
2415 // Close the connections with no registered traffic.
2416 ent
->PruneNoTraffic();
2419 mPruningNoTraffic
= false; // not pruning anymore
2422 void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase
*) {
2423 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2424 LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
2426 if (mPruningNoTraffic
) {
2427 // Called in the time gap when the timeout to prune notraffic
2428 // connections has triggered but the pruning hasn't happened yet.
2432 mCoalescingHash
.Clear();
2434 // Mark connections for traffic verification
2435 for (const auto& entry
: mCT
.Values()) {
2436 entry
->VerifyTraffic();
2439 // If the timer is already there. we just re-init it
2440 if (!mTrafficTimer
) {
2441 mTrafficTimer
= NS_NewTimer();
2444 // failure to create a timer is not a fatal error, but dead
2445 // connections will not be cleaned up as nicely
2446 if (mTrafficTimer
) {
2447 // Give active connections time to get more traffic before killing
2448 // them off. Default: 5000 milliseconds
2449 mTrafficTimer
->Init(this, gHttpHandler
->NetworkChangedTimeout(),
2450 nsITimer::TYPE_ONE_SHOT
);
2452 NS_WARNING("failed to create timer for VerifyTraffic!");
2454 // Calling ActivateTimeoutTick to ensure the next timeout tick is 1s.
2455 ActivateTimeoutTick();
2458 void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
2460 LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
2461 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2463 mCoalescingHash
.Clear();
2465 nsHttpConnectionInfo
* ci
= static_cast<nsHttpConnectionInfo
*>(param
);
2467 for (const auto& entry
: mCT
.Values()) {
2468 entry
->ClosePersistentConnections();
2471 if (ci
) ResetIPFamilyPreference(ci
);
2474 void nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup(int32_t,
2476 LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n"));
2477 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2479 nsHttpConnectionInfo
* ci
= static_cast<nsHttpConnectionInfo
*>(param
);
2485 ConnectionEntry
* entry
= mCT
.GetWeak(ci
->HashKey());
2487 entry
->ClosePersistentConnections();
2490 ResetIPFamilyPreference(ci
);
2493 void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase
* conn
) {
2494 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2497 // 1) remove the connection from the active list
2498 // 2) if keep-alive, add connection to idle list
2499 // 3) post event to process the pending transaction queue
2503 ConnectionEntry
* ent
= conn
->ConnectionInfo()
2504 ? mCT
.GetWeak(conn
->ConnectionInfo()->HashKey())
2508 // this can happen if the connection is made outside of the
2509 // connection manager and is being "reclaimed" for use with
2510 // future transactions. HTTP/2 tunnels work like this.
2511 bool isWildcard
= false;
2512 ent
= GetOrCreateConnectionEntry(conn
->ConnectionInfo(), true, false, false,
2515 ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
2516 "forced new hash entry %s\n",
2517 conn
, conn
->ConnectionInfo()->HashKey().get()));
2521 RefPtr
<nsHttpConnectionInfo
> ci(ent
->mConnInfo
);
2523 LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent
,
2526 // If the connection is in the active list, remove that entry
2527 // and the reference held by the mActiveConns list.
2528 // This is never the final reference on conn as the event context
2529 // is also holding one that is released at the end of this function.
2531 RefPtr
<nsHttpConnection
> connTCP
= do_QueryObject(conn
);
2532 if (!connTCP
|| connTCP
->EverUsedSpdy()) {
2533 // Spdyand Http3 connections aren't reused in the traditional HTTP way in
2534 // the idleconns list, they are actively multplexed as active
2535 // conns. Even when they have 0 transactions on them they are
2536 // considered active connections. So when one is reclaimed it
2537 // is really complete and is meant to be shut down and not
2542 // a connection that still holds a reference to a transaction was
2543 // not closed naturally (i.e. it was reset or aborted) and is
2544 // therefore not something that should be reused.
2545 if (conn
->Transaction()) {
2549 if (NS_SUCCEEDED(ent
->RemoveActiveConnection(conn
)) ||
2550 NS_SUCCEEDED(ent
->RemovePendingConnection(conn
))) {
2551 } else if (!connTCP
|| connTCP
->EverUsedSpdy()) {
2552 LOG(("HttpConnectionBase %p not found in its connection entry, try ^anon",
2554 // repeat for flipped anon flag as we share connection entries for spdy
2556 RefPtr
<nsHttpConnectionInfo
> anonInvertedCI(ci
->Clone());
2557 anonInvertedCI
->SetAnonymous(!ci
->GetAnonymous());
2559 ConnectionEntry
* ent
= mCT
.GetWeak(anonInvertedCI
->HashKey());
2561 if (NS_SUCCEEDED(ent
->RemoveActiveConnection(conn
))) {
2564 ("HttpConnectionBase %p could not be removed from its entry's "
2571 if (connTCP
&& connTCP
->CanReuse()) {
2572 LOG((" adding connection to idle list\n"));
2573 // Keep The idle connection list sorted with the connections that
2574 // have moved the largest data pipelines at the front because these
2575 // connections have the largest cwnds on the server.
2577 // The linear search is ok here because the number of idleconns
2578 // in a single entry is generally limited to a small number (i.e. 6)
2580 ent
->InsertIntoIdleConnections(connTCP
);
2582 if (ent
->IsInH2WebsocketConns(conn
)) {
2583 ent
->RemoveH2WebsocketConns(conn
);
2585 LOG((" connection cannot be reused; closing connection\n"));
2586 conn
->SetCloseReason(ConnectionCloseReason::CANT_REUSED
);
2587 conn
->Close(NS_ERROR_ABORT
);
2590 OnMsgProcessPendingQ(0, ci
);
2593 void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase
* param
) {
2594 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2596 nsresult rv
= NS_OK
;
2597 nsCompleteUpgradeData
* data
= static_cast<nsCompleteUpgradeData
*>(param
);
2598 MOZ_ASSERT(data
->mTrans
&& data
->mTrans
->Caps() & NS_HTTP_STICKY_CONNECTION
);
2600 RefPtr
<nsAHttpConnection
> conn(data
->mTrans
->Connection());
2602 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
2603 "conn=%p listener=%p wrapped=%d\n",
2604 conn
.get(), data
->mUpgradeListener
.get(), data
->mJsWrapped
));
2607 // Delay any error reporting to happen in transportAvailableFunc
2608 rv
= NS_ERROR_UNEXPECTED
;
2610 MOZ_ASSERT(!data
->mSocketTransport
);
2611 rv
= conn
->TakeTransport(getter_AddRefs(data
->mSocketTransport
),
2612 getter_AddRefs(data
->mSocketIn
),
2613 getter_AddRefs(data
->mSocketOut
));
2615 if (NS_FAILED(rv
)) {
2616 LOG((" conn->TakeTransport failed with %" PRIx32
,
2617 static_cast<uint32_t>(rv
)));
2621 RefPtr
<nsCompleteUpgradeData
> upgradeData(data
);
2623 nsCOMPtr
<nsIAsyncInputStream
> socketIn
;
2624 nsCOMPtr
<nsIAsyncOutputStream
> socketOut
;
2626 // If this is for JS, the input and output sockets need to be piped over the
2627 // socket thread. Otherwise, the JS may attempt to read and/or write the
2628 // sockets on the main thread, which could cause network I/O on the main
2629 // thread. This is particularly bad in the case of TLS connections, because
2630 // PSM and NSS rely on those connections only being used on the socket
2632 if (data
->mJsWrapped
) {
2633 nsCOMPtr
<nsIAsyncInputStream
> pipeIn
;
2634 uint32_t segsize
= 0;
2635 uint32_t segcount
= 0;
2636 net_ResolveSegmentParams(segsize
, segcount
);
2637 if (NS_SUCCEEDED(rv
)) {
2638 NS_NewPipe2(getter_AddRefs(pipeIn
), getter_AddRefs(socketOut
), true, true,
2640 rv
= NS_AsyncCopy(pipeIn
, data
->mSocketOut
, gSocketTransportService
,
2641 NS_ASYNCCOPY_VIA_READSEGMENTS
, segsize
);
2644 nsCOMPtr
<nsIAsyncOutputStream
> pipeOut
;
2645 if (NS_SUCCEEDED(rv
)) {
2646 NS_NewPipe2(getter_AddRefs(socketIn
), getter_AddRefs(pipeOut
), true, true,
2648 rv
= NS_AsyncCopy(data
->mSocketIn
, pipeOut
, gSocketTransportService
,
2649 NS_ASYNCCOPY_VIA_WRITESEGMENTS
, segsize
);
2652 socketIn
= upgradeData
->mSocketIn
;
2653 socketOut
= upgradeData
->mSocketOut
;
2656 auto transportAvailableFunc
= [upgradeData
{std::move(upgradeData
)}, socketIn
,
2657 socketOut
, aRv(rv
)]() {
2658 // Handle any potential previous errors first
2659 // and call OnUpgradeFailed if necessary.
2662 if (NS_FAILED(rv
)) {
2663 rv
= upgradeData
->mUpgradeListener
->OnUpgradeFailed(rv
);
2664 if (NS_FAILED(rv
)) {
2666 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
2668 upgradeData
->mUpgradeListener
.get()));
2673 rv
= upgradeData
->mUpgradeListener
->OnTransportAvailable(
2674 upgradeData
->mSocketTransport
, socketIn
, socketOut
);
2675 if (NS_FAILED(rv
)) {
2677 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
2678 "failed. listener=%p\n",
2679 upgradeData
->mUpgradeListener
.get()));
2683 if (data
->mJsWrapped
) {
2685 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
2686 "conn=%p listener=%p wrapped=%d pass to main thread\n",
2687 conn
.get(), data
->mUpgradeListener
.get(), data
->mJsWrapped
));
2688 NS_DispatchToMainThread(
2689 NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade",
2690 transportAvailableFunc
));
2692 transportAvailableFunc();
2696 void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam
, ARefBase
*) {
2697 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2698 uint32_t param
= static_cast<uint32_t>(inParam
);
2699 uint16_t name
= ((param
) & 0xFFFF0000) >> 16;
2700 uint16_t value
= param
& 0x0000FFFF;
2703 case MAX_CONNECTIONS
:
2706 case MAX_URGENT_START_Q
:
2707 mMaxUrgentExcessiveConns
= value
;
2709 case MAX_PERSISTENT_CONNECTIONS_PER_HOST
:
2710 mMaxPersistConnsPerHost
= value
;
2712 case MAX_PERSISTENT_CONNECTIONS_PER_PROXY
:
2713 mMaxPersistConnsPerProxy
= value
;
2715 case MAX_REQUEST_DELAY
:
2716 mMaxRequestDelay
= value
;
2718 case THROTTLING_ENABLED
:
2719 SetThrottlingEnabled(!!value
);
2721 case THROTTLING_SUSPEND_FOR
:
2722 mThrottleSuspendFor
= value
;
2724 case THROTTLING_RESUME_FOR
:
2725 mThrottleResumeFor
= value
;
2727 case THROTTLING_READ_LIMIT
:
2728 mThrottleReadLimit
= value
;
2730 case THROTTLING_READ_INTERVAL
:
2731 mThrottleReadInterval
= value
;
2733 case THROTTLING_HOLD_TIME
:
2734 mThrottleHoldTime
= value
;
2736 case THROTTLING_MAX_TIME
:
2737 mThrottleMaxTime
= TimeDuration::FromMilliseconds(value
);
2739 case PROXY_BE_CONSERVATIVE
:
2740 mBeConservativeForProxy
= !!value
;
2743 MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
2747 // Read Timeout Tick handlers
2749 void nsHttpConnectionMgr::ActivateTimeoutTick() {
2750 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2752 ("nsHttpConnectionMgr::ActivateTimeoutTick() "
2753 "this=%p mTimeoutTick=%p\n",
2754 this, mTimeoutTick
.get()));
2756 // The timer tick should be enabled if it is not already pending.
2757 // Upon running the tick will rearm itself if there are active
2758 // connections available.
2760 if (mTimeoutTick
&& mTimeoutTickArmed
) {
2761 // make sure we get one iteration on a quick tick
2762 if (mTimeoutTickNext
> 1) {
2763 mTimeoutTickNext
= 1;
2764 mTimeoutTick
->SetDelay(1000);
2769 if (!mTimeoutTick
) {
2770 mTimeoutTick
= NS_NewTimer();
2771 if (!mTimeoutTick
) {
2772 NS_WARNING("failed to create timer for http timeout management");
2775 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
2776 if (!mSocketThreadTarget
) {
2777 NS_WARNING("cannot activate timout if not initialized or shutdown");
2780 mTimeoutTick
->SetTarget(mSocketThreadTarget
);
2783 if (mIsShuttingDown
) { // Atomic
2784 // don't set a timer to generate an event if we're shutting down
2785 // (and mSocketThreadTarget might be null or garbage if we're shutting down)
2788 MOZ_ASSERT(!mTimeoutTickArmed
, "timer tick armed");
2789 mTimeoutTickArmed
= true;
2790 mTimeoutTick
->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK
);
2793 class UINT64Wrapper
: public ARefBase
{
2795 explicit UINT64Wrapper(uint64_t aUint64
) : mUint64(aUint64
) {}
2796 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper
, override
)
2798 uint64_t GetValue() { return mUint64
; }
2802 virtual ~UINT64Wrapper() = default;
2805 nsresult
nsHttpConnectionMgr::UpdateCurrentBrowserId(uint64_t aId
) {
2806 RefPtr
<UINT64Wrapper
> idWrapper
= new UINT64Wrapper(aId
);
2807 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId
, 0,
2811 void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable
) {
2812 LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable
));
2813 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2815 mThrottleEnabled
= aEnable
;
2817 if (mThrottleEnabled
) {
2818 EnsureThrottleTickerIfNeeded();
2820 DestroyThrottleTicker();
2821 ResumeReadOf(mActiveTransactions
[false]);
2822 ResumeReadOf(mActiveTransactions
[true]);
2826 bool nsHttpConnectionMgr::InThrottlingTimeWindow() {
2827 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2829 if (mThrottlingWindowEndsAt
.IsNull()) {
2832 return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt
;
2835 void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker
) {
2836 LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
2838 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2840 mThrottlingWindowEndsAt
= TimeStamp::NowLoRes() + mThrottleMaxTime
;
2842 if (!mThrottleTicker
&& MOZ_LIKELY(aEnsureTicker
) &&
2843 MOZ_LIKELY(mThrottleEnabled
)) {
2844 EnsureThrottleTickerIfNeeded();
2848 void nsHttpConnectionMgr::LogActiveTransactions(char operation
) {
2849 if (!LOG_ENABLED()) {
2853 nsTArray
<RefPtr
<nsHttpTransaction
>>* trs
= nullptr;
2854 uint32_t au
, at
, bu
= 0, bt
= 0;
2856 trs
= mActiveTransactions
[false].Get(mCurrentBrowserId
);
2857 au
= trs
? trs
->Length() : 0;
2858 trs
= mActiveTransactions
[true].Get(mCurrentBrowserId
);
2859 at
= trs
? trs
->Length() : 0;
2861 for (const auto& data
: mActiveTransactions
[false].Values()) {
2862 bu
+= data
->Length();
2865 for (const auto& data
: mActiveTransactions
[true].Values()) {
2866 bt
+= data
->Length();
2871 // - unthrottled transaction for the active tab
2872 // - throttled transaction for the active tab
2873 // - unthrottled transaction for background tabs
2874 // - throttled transaction for background tabs
2875 LOG(("Active transactions %c[%u,%u,%u,%u]", operation
, au
, at
, bu
, bt
));
2878 void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction
* aTrans
) {
2879 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2881 uint64_t tabId
= aTrans
->BrowserId();
2882 bool throttled
= aTrans
->EligibleForThrottling();
2884 nsTArray
<RefPtr
<nsHttpTransaction
>>* transactions
=
2885 mActiveTransactions
[throttled
].GetOrInsertNew(tabId
);
2887 MOZ_ASSERT(!transactions
->Contains(aTrans
));
2889 transactions
->AppendElement(aTrans
);
2891 LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64
2893 aTrans
, tabId
, tabId
== mCurrentBrowserId
, throttled
));
2894 LogActiveTransactions('+');
2896 if (tabId
== mCurrentBrowserId
) {
2897 mActiveTabTransactionsExist
= true;
2899 mActiveTabUnthrottledTransactionsExist
= true;
2903 // Shift the throttling window to the future (actually, makes sure
2904 // that throttling will engage when there is anything to throttle.)
2905 // The |false| argument means we don't need this call to ensure
2906 // the ticker, since we do it just below. Calling
2907 // EnsureThrottleTickerIfNeeded directly does a bit more than call
2908 // from inside of TouchThrottlingTimeWindow.
2909 TouchThrottlingTimeWindow(false);
2911 if (!mThrottleEnabled
) {
2915 EnsureThrottleTickerIfNeeded();
2918 void nsHttpConnectionMgr::RemoveActiveTransaction(
2919 nsHttpTransaction
* aTrans
, Maybe
<bool> const& aOverride
) {
2920 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2922 uint64_t tabId
= aTrans
->BrowserId();
2923 bool forActiveTab
= tabId
== mCurrentBrowserId
;
2924 bool throttled
= aOverride
.valueOr(aTrans
->EligibleForThrottling());
2926 nsTArray
<RefPtr
<nsHttpTransaction
>>* transactions
=
2927 mActiveTransactions
[throttled
].Get(tabId
);
2929 if (!transactions
|| !transactions
->RemoveElement(aTrans
)) {
2930 // Was not tracked as active, probably just ignore.
2934 LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
2936 aTrans
, tabId
, forActiveTab
, throttled
));
2938 if (!transactions
->IsEmpty()) {
2939 // There are still transactions of the type, hence nothing in the throttling
2940 // conditions has changed and we don't need to update "Exists" caches nor we
2941 // need to wake any now throttled transactions.
2942 LogActiveTransactions('-');
2946 // To optimize the following logic, always remove the entry when the array is
2948 mActiveTransactions
[throttled
].Remove(tabId
);
2949 LogActiveTransactions('-');
2952 // Update caches of the active tab transaction existence, since it's now
2955 mActiveTabUnthrottledTransactionsExist
= false;
2957 if (mActiveTabTransactionsExist
) {
2958 mActiveTabTransactionsExist
=
2959 mActiveTransactions
[!throttled
].Contains(tabId
);
2963 if (!mThrottleEnabled
) {
2967 bool unthrottledExist
= !mActiveTransactions
[false].IsEmpty();
2968 bool throttledExist
= !mActiveTransactions
[true].IsEmpty();
2970 if (!unthrottledExist
&& !throttledExist
) {
2971 // Nothing active globally, just get rid of the timer completely and we are
2973 MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist
);
2974 MOZ_ASSERT(!mActiveTabTransactionsExist
);
2976 DestroyThrottleTicker();
2980 if (mThrottleVersion
== 1) {
2981 if (!mThrottlingInhibitsReading
) {
2982 // There is then nothing to wake up. Affected transactions will not be
2983 // put to sleep automatically on next tick.
2984 LOG((" reading not currently inhibited"));
2989 if (mActiveTabUnthrottledTransactionsExist
) {
2990 // There are still unthrottled transactions for the active tab, hence the
2991 // state is unaffected and we don't need to do anything (nothing to wake).
2992 LOG((" there are unthrottled for the active tab"));
2996 if (mActiveTabTransactionsExist
) {
2997 // There are only trottled transactions for the active tab.
2998 // If the last transaction we just removed was a non-throttled for the
2999 // active tab we can wake the throttled transactions for the active tab.
3000 if (forActiveTab
&& !throttled
) {
3001 LOG((" resuming throttled for active tab"));
3002 ResumeReadOf(mActiveTransactions
[true].Get(mCurrentBrowserId
));
3007 if (!unthrottledExist
) {
3008 // There are no unthrottled transactions for any tab. Resume all throttled,
3009 // all are only for background tabs.
3010 LOG((" delay resuming throttled for background tabs"));
3011 DelayedResumeBackgroundThrottledTransactions();
3016 // Removing the last transaction for the active tab frees up the unthrottled
3017 // background tabs transactions.
3018 LOG((" delay resuming unthrottled for background tabs"));
3019 DelayedResumeBackgroundThrottledTransactions();
3023 LOG((" not resuming anything"));
3026 void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction
* aTrans
) {
3027 LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans
));
3029 // First remove then add. In case of a download that is the only active
3030 // transaction and has just been marked as download (goes unthrottled to
3031 // throttled), adding first would cause it to be throttled for first few
3032 // milliseconds - becuause it would appear as if there were both throttled
3033 // and unthrottled transactions at the time.
3035 Maybe
<bool> reversed
;
3036 reversed
.emplace(!aTrans
->EligibleForThrottling());
3037 RemoveActiveTransaction(aTrans
, reversed
);
3039 AddActiveTransaction(aTrans
);
3041 LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans
));
3044 bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction
* aTrans
) {
3045 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3047 LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans
));
3049 if (mThrottleVersion
== 1) {
3050 if (!mThrottlingInhibitsReading
|| !mThrottleEnabled
) {
3054 if (!mThrottleEnabled
) {
3059 uint64_t tabId
= aTrans
->BrowserId();
3060 bool forActiveTab
= tabId
== mCurrentBrowserId
;
3061 bool throttled
= aTrans
->EligibleForThrottling();
3064 if (mActiveTabTransactionsExist
) {
3066 // Chrome initiated and unidentified transactions just respect
3067 // their throttle flag, when something for the active tab is happening.
3068 // This also includes downloads.
3069 LOG((" active tab loads, trans is tab-less, throttled=%d", throttled
));
3072 if (!forActiveTab
) {
3073 // This is a background tab request, we want them to always throttle
3074 // when there are transactions running for the ative tab.
3075 LOG((" active tab loads, trans not of the active tab"));
3079 if (mActiveTabUnthrottledTransactionsExist
) {
3080 // Unthrottled transactions for the active tab take precedence
3081 LOG((" active tab loads unthrottled, trans throttled=%d", throttled
));
3085 LOG((" trans for active tab, don't throttle"));
3089 MOZ_ASSERT(!forActiveTab
);
3091 if (!mActiveTransactions
[false].IsEmpty()) {
3092 // This means there are unthrottled active transactions for background
3093 // tabs. If we are here, there can't be any transactions for the active
3094 // tab. (If there is no transaction for a tab id, there is no entry for it
3095 // in the hashtable.)
3096 LOG((" backround tab(s) load unthrottled, trans throttled=%d",
3101 // There are only unthrottled transactions for background tabs: don't
3103 LOG((" backround tab(s) load throttled, don't throttle"));
3107 if (forActiveTab
&& !stop
) {
3108 // This is an active-tab transaction and is allowed to read. Hence,
3109 // prolong the throttle time window to make sure all 'lower-decks'
3110 // transactions will actually throttle.
3111 TouchThrottlingTimeWindow();
3115 // Only stop reading when in the configured throttle max-time (aka time
3116 // window). This window is prolonged (restarted) by a call to
3117 // TouchThrottlingTimeWindow called on new transaction activation or on
3118 // receive of response bytes of an active tab transaction.
3119 bool inWindow
= InThrottlingTimeWindow();
3121 LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d", stop
, inWindow
,
3122 !!mDelayedResumeReadTimer
));
3124 if (!forActiveTab
) {
3125 // If the delayed background resume timer exists, background transactions
3126 // are scheduled to be woken after a delay, hence leave them throttled.
3127 inWindow
= inWindow
|| mDelayedResumeReadTimer
;
3130 return stop
&& inWindow
;
3133 bool nsHttpConnectionMgr::IsConnEntryUnderPressure(
3134 nsHttpConnectionInfo
* connInfo
) {
3135 ConnectionEntry
* ent
= mCT
.GetWeak(connInfo
->HashKey());
3137 // No entry, no pressure.
3141 return ent
->PendingQueueLengthForWindow(mCurrentBrowserId
) > 0;
3144 bool nsHttpConnectionMgr::IsThrottleTickerNeeded() {
3145 LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
3147 if (mActiveTabUnthrottledTransactionsExist
&&
3148 mActiveTransactions
[false].Count() > 1) {
3149 LOG((" there are unthrottled transactions for both active and bck"));
3153 if (mActiveTabTransactionsExist
&& mActiveTransactions
[true].Count() > 1) {
3154 LOG((" there are throttled transactions for both active and bck"));
3158 if (!mActiveTransactions
[true].IsEmpty() &&
3159 !mActiveTransactions
[false].IsEmpty()) {
3160 LOG((" there are both throttled and unthrottled transactions"));
3164 LOG((" nothing to throttle"));
3168 void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
3169 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3171 LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
3172 if (!IsThrottleTickerNeeded()) {
3176 // There is a new demand to throttle, hence unschedule delayed resume
3177 // of background throttled transastions.
3178 CancelDelayedResumeBackgroundThrottledTransactions();
3180 if (mThrottleTicker
) {
3184 mThrottleTicker
= NS_NewTimer();
3185 if (mThrottleTicker
) {
3186 if (mThrottleVersion
== 1) {
3187 MOZ_ASSERT(!mThrottlingInhibitsReading
);
3189 mThrottleTicker
->Init(this, mThrottleSuspendFor
, nsITimer::TYPE_ONE_SHOT
);
3190 mThrottlingInhibitsReading
= true;
3192 mThrottleTicker
->Init(this, mThrottleReadInterval
,
3193 nsITimer::TYPE_ONE_SHOT
);
3197 LogActiveTransactions('^');
3200 // Can be called with or without the monitor held
3201 void nsHttpConnectionMgr::DestroyThrottleTicker() {
3202 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3204 // Nothing to throttle, hence no need for this timer anymore.
3205 CancelDelayedResumeBackgroundThrottledTransactions();
3207 MOZ_ASSERT(!mThrottleEnabled
|| !IsThrottleTickerNeeded());
3209 if (!mThrottleTicker
) {
3213 LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
3214 mThrottleTicker
->Cancel();
3215 mThrottleTicker
= nullptr;
3217 if (mThrottleVersion
== 1) {
3218 mThrottlingInhibitsReading
= false;
3221 LogActiveTransactions('v');
3224 void nsHttpConnectionMgr::ThrottlerTick() {
3225 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3227 if (mThrottleVersion
== 1) {
3228 mThrottlingInhibitsReading
= !mThrottlingInhibitsReading
;
3230 LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d",
3231 mThrottlingInhibitsReading
));
3233 // If there are only background transactions to be woken after a delay, keep
3234 // the ticker so that we woke them only for the resume-for interval and then
3235 // throttle them again until the background-resume delay passes.
3236 if (!mThrottlingInhibitsReading
&& !mDelayedResumeReadTimer
&&
3237 (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
3238 LOG((" last tick"));
3239 mThrottleTicker
= nullptr;
3242 if (mThrottlingInhibitsReading
) {
3243 if (mThrottleTicker
) {
3244 mThrottleTicker
->Init(this, mThrottleSuspendFor
,
3245 nsITimer::TYPE_ONE_SHOT
);
3248 if (mThrottleTicker
) {
3249 mThrottleTicker
->Init(this, mThrottleResumeFor
,
3250 nsITimer::TYPE_ONE_SHOT
);
3253 ResumeReadOf(mActiveTransactions
[false], true);
3254 ResumeReadOf(mActiveTransactions
[true]);
3257 LOG(("nsHttpConnectionMgr::ThrottlerTick"));
3259 // If there are only background transactions to be woken after a delay, keep
3260 // the ticker so that we still keep the low read limit for that time.
3261 if (!mDelayedResumeReadTimer
&&
3262 (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
3263 LOG((" last tick"));
3264 mThrottleTicker
= nullptr;
3267 if (mThrottleTicker
) {
3268 mThrottleTicker
->Init(this, mThrottleReadInterval
,
3269 nsITimer::TYPE_ONE_SHOT
);
3272 ResumeReadOf(mActiveTransactions
[false], true);
3273 ResumeReadOf(mActiveTransactions
[true]);
3277 void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() {
3278 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3280 if (mThrottleVersion
== 1) {
3281 if (mDelayedResumeReadTimer
) {
3285 // If the mThrottleTicker doesn't exist, there is nothing currently
3286 // being throttled. Hence, don't invoke the hold time interval.
3287 // This is called also when a single download transaction becomes
3288 // marked as throttleable. We would otherwise block it unnecessarily.
3289 if (mDelayedResumeReadTimer
|| !mThrottleTicker
) {
3294 LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
3295 NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer
), this,
3296 mThrottleHoldTime
, nsITimer::TYPE_ONE_SHOT
);
3299 void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
3300 if (!mDelayedResumeReadTimer
) {
3305 ("nsHttpConnectionMgr::"
3306 "CancelDelayedResumeBackgroundThrottledTransactions"));
3307 mDelayedResumeReadTimer
->Cancel();
3308 mDelayedResumeReadTimer
= nullptr;
3311 void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() {
3312 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3314 LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
3315 mDelayedResumeReadTimer
= nullptr;
3317 if (!IsThrottleTickerNeeded()) {
3318 DestroyThrottleTicker();
3321 if (!mActiveTransactions
[false].IsEmpty()) {
3322 ResumeReadOf(mActiveTransactions
[false], true);
3324 ResumeReadOf(mActiveTransactions
[true], true);
3328 void nsHttpConnectionMgr::ResumeReadOf(
3329 nsClassHashtable
<nsUint64HashKey
, nsTArray
<RefPtr
<nsHttpTransaction
>>>&
3331 bool excludeForActiveTab
) {
3332 for (const auto& entry
: hashtable
) {
3333 if (excludeForActiveTab
&& entry
.GetKey() == mCurrentBrowserId
) {
3334 // These have never been throttled (never stopped reading)
3337 ResumeReadOf(entry
.GetWeak());
3341 void nsHttpConnectionMgr::ResumeReadOf(
3342 nsTArray
<RefPtr
<nsHttpTransaction
>>* transactions
) {
3343 MOZ_ASSERT(transactions
);
3345 for (const auto& trans
: *transactions
) {
3346 trans
->ResumeReading();
3350 void nsHttpConnectionMgr::NotifyConnectionOfBrowserIdChange(
3351 uint64_t previousId
) {
3352 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3354 nsTArray
<RefPtr
<nsHttpTransaction
>>* transactions
= nullptr;
3355 nsTArray
<RefPtr
<nsAHttpConnection
>> connections
;
3357 auto addConnectionHelper
=
3358 [&connections
](nsTArray
<RefPtr
<nsHttpTransaction
>>* trans
) {
3363 for (const auto& t
: *trans
) {
3364 RefPtr
<nsAHttpConnection
> conn
= t
->Connection();
3365 if (conn
&& !connections
.Contains(conn
)) {
3366 connections
.AppendElement(conn
);
3371 // Get unthrottled transactions with the previous and current window id.
3372 transactions
= mActiveTransactions
[false].Get(previousId
);
3373 addConnectionHelper(transactions
);
3374 transactions
= mActiveTransactions
[false].Get(mCurrentBrowserId
);
3375 addConnectionHelper(transactions
);
3377 // Get throttled transactions with the previous and current window id.
3378 transactions
= mActiveTransactions
[true].Get(previousId
);
3379 addConnectionHelper(transactions
);
3380 transactions
= mActiveTransactions
[true].Get(mCurrentBrowserId
);
3381 addConnectionHelper(transactions
);
3383 for (const auto& conn
: connections
) {
3384 conn
->CurrentBrowserIdChanged(mCurrentBrowserId
);
3388 void nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId(int32_t aLoading
,
3390 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3392 uint64_t id
= static_cast<UINT64Wrapper
*>(param
)->GetValue();
3394 if (mCurrentBrowserId
== id
) {
3395 // duplicate notification
3399 bool activeTabWasLoading
= mActiveTabTransactionsExist
;
3401 uint64_t previousId
= mCurrentBrowserId
;
3402 mCurrentBrowserId
= id
;
3404 if (gHttpHandler
->ActiveTabPriority()) {
3405 NotifyConnectionOfBrowserIdChange(previousId
);
3409 ("nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId"
3410 " id=%" PRIx64
"\n",
3411 mCurrentBrowserId
));
3413 nsTArray
<RefPtr
<nsHttpTransaction
>>* transactions
= nullptr;
3415 // Update the "Exists" caches and resume any transactions that now deserve it,
3416 // changing the active tab changes the conditions for throttling.
3417 transactions
= mActiveTransactions
[false].Get(mCurrentBrowserId
);
3418 mActiveTabUnthrottledTransactionsExist
= !!transactions
;
3420 if (!mActiveTabUnthrottledTransactionsExist
) {
3421 transactions
= mActiveTransactions
[true].Get(mCurrentBrowserId
);
3423 mActiveTabTransactionsExist
= !!transactions
;
3426 // This means there are some transactions for this newly activated tab,
3427 // resume them but anything else.
3428 LOG((" resuming newly activated tab transactions"));
3429 ResumeReadOf(transactions
);
3433 if (!activeTabWasLoading
) {
3434 // There were no transactions for the previously active tab, hence
3435 // all remaning transactions, if there were, were all unthrottled,
3436 // no need to wake them.
3440 if (!mActiveTransactions
[false].IsEmpty()) {
3441 LOG((" resuming unthrottled background transactions"));
3442 ResumeReadOf(mActiveTransactions
[false]);
3446 if (!mActiveTransactions
[true].IsEmpty()) {
3447 LOG((" resuming throttled background transactions"));
3448 ResumeReadOf(mActiveTransactions
[true]);
3452 DestroyThrottleTicker();
3455 void nsHttpConnectionMgr::TimeoutTick() {
3456 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3457 MOZ_ASSERT(mTimeoutTick
, "no readtimeout tick");
3459 LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns
));
3460 // The next tick will be between 1 second and 1 hr
3461 // Set it to the max value here, and the TimeoutTick()s can
3462 // reduce it to their local needs.
3463 mTimeoutTickNext
= 3600; // 1hr
3465 for (const RefPtr
<ConnectionEntry
>& ent
: mCT
.Values()) {
3466 uint32_t timeoutTickNext
= ent
->TimeoutTick();
3467 mTimeoutTickNext
= std::min(mTimeoutTickNext
, timeoutTickNext
);
3471 mTimeoutTickNext
= std::max(mTimeoutTickNext
, 1U);
3472 mTimeoutTick
->SetDelay(mTimeoutTickNext
* 1000);
3476 // GetOrCreateConnectionEntry finds a ent for a particular CI for use in
3477 // dispatching a transaction according to these rules
3478 // 1] use an ent that matches the ci that can be dispatched immediately
3479 // 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
3480 // 3] otherwise create an ent that matches ci and make new conn on it
3482 ConnectionEntry
* nsHttpConnectionMgr::GetOrCreateConnectionEntry(
3483 nsHttpConnectionInfo
* specificCI
, bool prohibitWildCard
, bool aNoHttp2
,
3484 bool aNoHttp3
, bool* aIsWildcard
, bool* aAvailableForDispatchNow
) {
3485 if (aAvailableForDispatchNow
) {
3486 *aAvailableForDispatchNow
= false;
3488 *aIsWildcard
= false;
3491 ConnectionEntry
* specificEnt
= mCT
.GetWeak(specificCI
->HashKey());
3492 if (specificEnt
&& specificEnt
->AvailableForDispatchNow()) {
3493 if (aAvailableForDispatchNow
) {
3494 *aAvailableForDispatchNow
= true;
3499 // step 1 repeated for an inverted anonymous flag; we return an entry
3500 // only when it has an h2 established connection that is not authenticated
3501 // with a client certificate.
3502 RefPtr
<nsHttpConnectionInfo
> anonInvertedCI(specificCI
->Clone());
3503 anonInvertedCI
->SetAnonymous(!specificCI
->GetAnonymous());
3504 ConnectionEntry
* invertedEnt
= mCT
.GetWeak(anonInvertedCI
->HashKey());
3506 HttpConnectionBase
* h2orh3conn
=
3507 GetH2orH3ActiveConn(invertedEnt
, aNoHttp2
, aNoHttp3
);
3508 if (h2orh3conn
&& h2orh3conn
->IsExperienced() &&
3509 h2orh3conn
->NoClientCertAuth()) {
3510 MOZ_ASSERT(h2orh3conn
->UsingSpdy() || h2orh3conn
->UsingHttp3());
3512 ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
3513 "connections, ent=%p",
3519 if (!specificCI
->UsingHttpsProxy()) {
3520 prohibitWildCard
= true;
3524 if (!prohibitWildCard
&& aNoHttp3
) {
3525 RefPtr
<nsHttpConnectionInfo
> wildCardProxyCI
;
3526 DebugOnly
<nsresult
> rv
=
3527 specificCI
->CreateWildCard(getter_AddRefs(wildCardProxyCI
));
3528 MOZ_ASSERT(NS_SUCCEEDED(rv
));
3529 ConnectionEntry
* wildCardEnt
= mCT
.GetWeak(wildCardProxyCI
->HashKey());
3530 if (wildCardEnt
&& wildCardEnt
->AvailableForDispatchNow()) {
3531 if (aAvailableForDispatchNow
) {
3532 *aAvailableForDispatchNow
= true;
3534 *aIsWildcard
= true;
3541 RefPtr
<nsHttpConnectionInfo
> clone(specificCI
->Clone());
3542 specificEnt
= new ConnectionEntry(clone
);
3543 mCT
.InsertOrUpdate(clone
->HashKey(), RefPtr
{specificEnt
});
3548 void nsHttpConnectionMgr::DoSpeculativeConnection(
3549 SpeculativeTransaction
* aTrans
, bool aFetchHTTPSRR
) {
3550 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3553 bool isWildcard
= false;
3554 ConnectionEntry
* ent
= GetOrCreateConnectionEntry(
3555 aTrans
->ConnectionInfo(), false, aTrans
->Caps() & NS_HTTP_DISALLOW_SPDY
,
3556 aTrans
->Caps() & NS_HTTP_DISALLOW_HTTP3
, &isWildcard
);
3557 if (!aFetchHTTPSRR
&&
3558 gHttpHandler
->EchConfigEnabled(aTrans
->ConnectionInfo()->IsHttp3())) {
3559 // This happens when this is called from
3560 // SpeculativeTransaction::OnHTTPSRRAvailable. We have to update this
3561 // entry's echConfig so that the newly created connection can use the latest
3563 ent
->MaybeUpdateEchConfig(aTrans
->ConnectionInfo());
3565 DoSpeculativeConnectionInternal(ent
, aTrans
, aFetchHTTPSRR
);
3568 void nsHttpConnectionMgr::DoSpeculativeConnectionInternal(
3569 ConnectionEntry
* aEnt
, SpeculativeTransaction
* aTrans
, bool aFetchHTTPSRR
) {
3570 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3573 if (!gHttpHandler
->Active()) {
3574 // Do nothing if we are shutting down.
3578 ProxyDNSStrategy strategy
= GetProxyDNSStrategyHelper(
3579 aEnt
->mConnInfo
->ProxyType(), aEnt
->mConnInfo
->ProxyFlag());
3580 // Speculative connections can be triggered by non-Necko consumers,
3581 // so add an extra check to ensure HTTPS RR isn't fetched when a proxy is
3583 if (aFetchHTTPSRR
&& strategy
== ProxyDNSStrategy::ORIGIN
&&
3584 NS_SUCCEEDED(aTrans
->FetchHTTPSRR())) {
3585 // nsHttpConnectionMgr::DoSpeculativeConnection will be called again
3586 // when HTTPS RR is available.
3590 uint32_t parallelSpeculativeConnectLimit
=
3591 aTrans
->ParallelSpeculativeConnectLimit()
3592 ? *aTrans
->ParallelSpeculativeConnectLimit()
3593 : gHttpHandler
->ParallelSpeculativeConnectLimit();
3594 bool ignoreIdle
= aTrans
->IgnoreIdle() ? *aTrans
->IgnoreIdle() : false;
3595 bool isFromPredictor
=
3596 aTrans
->IsFromPredictor() ? *aTrans
->IsFromPredictor() : false;
3597 bool allow1918
= aTrans
->Allow1918() ? *aTrans
->Allow1918() : false;
3599 bool keepAlive
= aTrans
->Caps() & NS_HTTP_ALLOW_KEEPALIVE
;
3600 if (mNumDnsAndConnectSockets
< parallelSpeculativeConnectLimit
&&
3602 (aEnt
->IdleConnectionsLength() < parallelSpeculativeConnectLimit
)) ||
3603 !aEnt
->IdleConnectionsLength()) &&
3604 !(keepAlive
&& aEnt
->RestrictConnections()) &&
3605 !AtActiveConnectionLimit(aEnt
, aTrans
->Caps())) {
3606 nsresult rv
= aEnt
->CreateDnsAndConnectSocket(aTrans
, aTrans
->Caps(), true,
3607 isFromPredictor
, false,
3608 allow1918
, nullptr);
3609 if (NS_FAILED(rv
)) {
3610 glean::networking::speculative_connect_outcome
3611 .Get("aborted_socket_fail"_ns
)
3614 ("DoSpeculativeConnectionInternal Transport socket creation "
3615 "failure: %" PRIx32
"\n",
3616 static_cast<uint32_t>(rv
)));
3618 glean::networking::speculative_connect_outcome
.Get("successful"_ns
)
3622 glean::networking::speculative_connect_outcome
3623 .Get("aborted_socket_limit"_ns
)
3626 ("DoSpeculativeConnectionInternal Transport ci=%s "
3627 "not created due to existing connection count:%d",
3628 aEnt
->mConnInfo
->HashKey().get(), parallelSpeculativeConnectLimit
));
3632 void nsHttpConnectionMgr::DoFallbackConnection(SpeculativeTransaction
* aTrans
,
3633 bool aFetchHTTPSRR
) {
3634 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3637 LOG(("nsHttpConnectionMgr::DoFallbackConnection"));
3639 bool availableForDispatchNow
= false;
3640 bool aIsWildcard
= false;
3641 ConnectionEntry
* ent
= GetOrCreateConnectionEntry(
3642 aTrans
->ConnectionInfo(), false, aTrans
->Caps() & NS_HTTP_DISALLOW_SPDY
,
3643 aTrans
->Caps() & NS_HTTP_DISALLOW_HTTP3
, &aIsWildcard
,
3644 &availableForDispatchNow
);
3646 if (availableForDispatchNow
) {
3648 ("nsHttpConnectionMgr::DoFallbackConnection fallback connection is "
3649 "ready for dispatching ent=%p",
3651 aTrans
->InvokeCallback();
3655 DoSpeculativeConnectionInternal(ent
, aTrans
, aFetchHTTPSRR
);
3658 void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase
* param
) {
3659 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3661 SpeculativeConnectArgs
* args
= static_cast<SpeculativeConnectArgs
*>(param
);
3664 ("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s, "
3665 "mFetchHTTPSRR=%d]\n",
3666 args
->mTrans
->ConnectionInfo()->HashKey().get(), args
->mFetchHTTPSRR
));
3667 DoSpeculativeConnection(args
->mTrans
, args
->mFetchHTTPSRR
);
3670 bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo
* proxy
) {
3671 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3672 if (mBeConservativeForProxy
) {
3673 // The pref says to be conservative for proxies.
3678 // There is no proxy, so be conservative by default.
3682 // Be conservative only if there is no proxy host set either.
3683 // This logic was copied from nsSSLIOLayerAddToSocket.
3684 nsAutoCString proxyHost
;
3685 proxy
->GetHost(proxyHost
);
3686 return proxyHost
.IsEmpty();
3689 // register a connection to receive CanJoinConnection() for particular
3691 void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase
* conn
,
3692 const nsACString
& host
,
3694 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3695 nsHttpConnectionInfo
* ci
= conn
? conn
->ConnectionInfo() : nullptr;
3696 if (!ci
|| !conn
->CanDirectlyActivate()) {
3701 BuildOriginFrameHashKey(newKey
, ci
, host
, port
);
3702 mCoalescingHash
.GetOrInsertNew(newKey
, 1)->AppendElement(
3703 do_GetWeakReference(static_cast<nsISupportsWeakReference
*>(conn
)));
3706 ("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
3707 "Established New Coalescing Key %s to %p %s\n",
3708 newKey
.get(), conn
, ci
->HashKey().get()));
3711 bool nsHttpConnectionMgr::GetConnectionData(nsTArray
<HttpRetParams
>* aArg
) {
3712 for (const RefPtr
<ConnectionEntry
>& ent
: mCT
.Values()) {
3713 if (ent
->mConnInfo
->GetPrivate()) {
3716 aArg
->AppendElement(ent
->GetConnectionData());
3722 void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo
* ci
) {
3723 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3724 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
3726 ent
->ResetIPFamilyPreference();
3730 void nsHttpConnectionMgr::ExcludeHttp2(const nsHttpConnectionInfo
* ci
) {
3731 LOG(("nsHttpConnectionMgr::ExcludeHttp2 excluding ci %s",
3732 ci
->HashKey().BeginReading()));
3733 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
3735 LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!"));
3739 ent
->DisallowHttp2();
3742 void nsHttpConnectionMgr::ExcludeHttp3(const nsHttpConnectionInfo
* ci
) {
3743 LOG(("nsHttpConnectionMgr::ExcludeHttp3 exclude ci %s",
3744 ci
->HashKey().BeginReading()));
3745 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
3747 LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!"));
3751 ent
->DontReuseHttp3Conn();
3754 void nsHttpConnectionMgr::MoveToWildCardConnEntry(
3755 nsHttpConnectionInfo
* specificCI
, nsHttpConnectionInfo
* wildCardCI
,
3756 HttpConnectionBase
* proxyConn
) {
3757 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3758 MOZ_ASSERT(specificCI
->UsingHttpsProxy());
3761 ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
3762 "change CI from %s to %s\n",
3763 proxyConn
, specificCI
->HashKey().get(), wildCardCI
->HashKey().get()));
3765 ConnectionEntry
* ent
= mCT
.GetWeak(specificCI
->HashKey());
3767 ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
3769 proxyConn
, ent
, ent
? ent
->mUsingSpdy
: 0));
3771 if (!ent
|| !ent
->mUsingSpdy
) {
3775 bool isWildcard
= false;
3776 ConnectionEntry
* wcEnt
=
3777 GetOrCreateConnectionEntry(wildCardCI
, true, false, false, &isWildcard
);
3782 wcEnt
->mUsingSpdy
= true;
3785 ("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
3786 "idle=%zu active=%zu half=%zu pending=%zu\n",
3787 ent
, ent
->IdleConnectionsLength(), ent
->ActiveConnsLength(),
3788 ent
->DnsAndConnectSocketsLength(), ent
->PendingQueueLength()));
3791 ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
3792 "idle=%zu active=%zu half=%zu pending=%zu\n",
3793 wcEnt
, wcEnt
->IdleConnectionsLength(), wcEnt
->ActiveConnsLength(),
3794 wcEnt
->DnsAndConnectSocketsLength(), wcEnt
->PendingQueueLength()));
3796 ent
->MoveConnection(proxyConn
, wcEnt
);
3799 bool nsHttpConnectionMgr::RemoveTransFromConnEntry(nsHttpTransaction
* aTrans
,
3800 const nsACString
& aHashKey
) {
3801 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3803 LOG(("nsHttpConnectionMgr::RemoveTransFromConnEntry: trans=%p ci=%s", aTrans
,
3804 PromiseFlatCString(aHashKey
).get()));
3806 if (aHashKey
.IsEmpty()) {
3810 // Step 1: Get the transaction's connection entry.
3811 ConnectionEntry
* entry
= mCT
.GetWeak(aHashKey
);
3816 // Step 2: Try to find the undispatched transaction.
3817 return entry
->RemoveTransFromPendingQ(aTrans
);
3820 void nsHttpConnectionMgr::IncreaseNumDnsAndConnectSockets() {
3821 mNumDnsAndConnectSockets
++;
3824 void nsHttpConnectionMgr::DecreaseNumDnsAndConnectSockets() {
3825 MOZ_ASSERT(mNumDnsAndConnectSockets
);
3826 if (mNumDnsAndConnectSockets
) { // just in case
3827 mNumDnsAndConnectSockets
--;
3831 already_AddRefed
<PendingTransactionInfo
>
3832 nsHttpConnectionMgr::FindTransactionHelper(bool removeWhenFound
,
3833 ConnectionEntry
* aEnt
,
3834 nsAHttpTransaction
* aTrans
) {
3835 nsTArray
<RefPtr
<PendingTransactionInfo
>>* pendingQ
=
3836 aEnt
->GetTransactionPendingQHelper(aTrans
);
3839 pendingQ
? pendingQ
->IndexOf(aTrans
, 0, PendingComparator()) : -1;
3841 RefPtr
<PendingTransactionInfo
> info
;
3843 info
= (*pendingQ
)[index
];
3844 if (removeWhenFound
) {
3845 pendingQ
->RemoveElementAt(index
);
3848 return info
.forget();
3851 already_AddRefed
<ConnectionEntry
> nsHttpConnectionMgr::FindConnectionEntry(
3852 const nsHttpConnectionInfo
* ci
) {
3853 return mCT
.Get(ci
->HashKey());
3856 nsHttpConnectionMgr
* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; }
3858 HttpConnectionMgrParent
* nsHttpConnectionMgr::AsHttpConnectionMgrParent() {
3862 void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive
) {
3865 // If the added connection was first idle connection or has shortest
3866 // time to live among the watched connections, pruning dead
3867 // connections needs to be done when it can't be reused anymore.
3868 if (!mTimer
|| NowInSeconds() + timeToLive
< mTimeOfNextWakeUp
) {
3869 PruneDeadConnectionsAfter(timeToLive
);
3873 void nsHttpConnectionMgr::DecrementNumIdleConns() {
3874 MOZ_ASSERT(mNumIdleConns
);
3876 ConditionallyStopPruneDeadConnectionsTimer();
3879 // A structure used to marshall objects necessary for ServerCertificateHashaes
3880 class nsStoreServerCertHashesData
: public ARefBase
{
3882 nsStoreServerCertHashesData(
3883 nsHttpConnectionInfo
* aConnInfo
, bool aNoSpdy
, bool aNoHttp3
,
3884 nsTArray
<RefPtr
<nsIWebTransportHash
>>&& aServerCertHashes
)
3885 : mConnInfo(aConnInfo
),
3888 mServerCertHashes(std::move(aServerCertHashes
)) {}
3890 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStoreServerCertHashesData
, override
)
3892 RefPtr
<nsHttpConnectionInfo
> mConnInfo
;
3895 nsTArray
<RefPtr
<nsIWebTransportHash
>> mServerCertHashes
;
3898 virtual ~nsStoreServerCertHashesData() = default;
3901 // The connection manager needs to know the hashes used for a WebTransport
3902 // connection authenticated with serverCertHashes
3903 nsresult
nsHttpConnectionMgr::StoreServerCertHashes(
3904 nsHttpConnectionInfo
* aConnInfo
, bool aNoSpdy
, bool aNoHttp3
,
3905 nsTArray
<RefPtr
<nsIWebTransportHash
>>&& aServerCertHashes
) {
3906 RefPtr
<nsHttpConnectionInfo
> ci
= aConnInfo
->Clone();
3907 RefPtr
<nsStoreServerCertHashesData
> data
= new nsStoreServerCertHashesData(
3908 ci
, aNoSpdy
, aNoHttp3
, std::move(aServerCertHashes
));
3909 return PostEvent(&nsHttpConnectionMgr::OnMsgStoreServerCertHashes
, 0, data
);
3912 void nsHttpConnectionMgr::OnMsgStoreServerCertHashes(int32_t, ARefBase
* param
) {
3913 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3915 nsStoreServerCertHashesData
* data
=
3916 static_cast<nsStoreServerCertHashesData
*>(param
);
3919 ConnectionEntry
* connEnt
= GetOrCreateConnectionEntry(
3920 data
->mConnInfo
, true, data
->mNoSpdy
, data
->mNoHttp3
, &isWildcard
);
3921 MOZ_ASSERT(!isWildcard
, "No webtransport with wildcard");
3922 connEnt
->SetServerCertHashes(std::move(data
->mServerCertHashes
));
3925 const nsTArray
<RefPtr
<nsIWebTransportHash
>>*
3926 nsHttpConnectionMgr::GetServerCertHashes(nsHttpConnectionInfo
* aConnInfo
) {
3927 ConnectionEntry
* connEnt
= mCT
.GetWeak(aConnInfo
->HashKey());
3932 return &connEnt
->GetServerCertHashes();
3935 void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction
* aTrans
) {
3936 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
3937 // We only do this check on socket thread. When this function is called on
3938 // main thread, the transaction is newly created, so we can skip this check.
3939 if (!OnSocketThread()) {
3943 nsAutoCString hashKey
;
3944 aTrans
->GetHashKeyOfConnectionEntry(hashKey
);
3945 if (hashKey
.IsEmpty()) {
3949 bool foundInPendingQ
= RemoveTransFromConnEntry(aTrans
, hashKey
);
3950 MOZ_DIAGNOSTIC_ASSERT(!foundInPendingQ
);
3954 bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3(
3955 nsHttpConnectionInfo
* ci
, nsresult aError
) {
3956 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
3961 return ent
->AllowToRetryDifferentIPFamilyForHttp3(aError
);
3964 void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3(
3965 nsHttpConnectionInfo
* ci
, uint16_t aIPFamily
) {
3966 ConnectionEntry
* ent
= mCT
.GetWeak(ci
->HashKey());
3971 ent
->SetRetryDifferentIPFamilyForHttp3(aIPFamily
);
3974 } // namespace mozilla::net