Bug 1925181 - Properly set small alloc randomization on Android content processes...
[gecko.git] / netwerk / protocol / http / nsHttpConnectionMgr.cpp
blobae2a73ecaa037800ac65698eb516394dfc083bff
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
7 #include "HttpLog.h"
9 // Log on level :5, instead of default :4.
10 #undef LOG
11 #define LOG(args) LOG5(args)
12 #undef LOG_ENABLED
13 #define LOG_ENABLED() LOG5_ENABLED()
15 #include <algorithm>
16 #include <utility>
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"
32 #include "nsCOMPtr.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"
41 #include "nsIPipe.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"
48 #include "nsNetCID.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 {
59 struct UrlMarker {
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);
82 return schema;
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();
109 if (ioService) {
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 {
162 public:
163 BoolWrapper() = default;
164 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
166 public: // intentional!
167 bool mBool{false};
169 private:
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;
183 nsresult rv =
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
188 // shutting down.
189 mIsShuttingDown = true;
190 mSocketThreadTarget = nullptr;
192 if (NS_FAILED(rv)) {
193 NS_WARNING("unable to post SHUTDOWN message");
194 return rv;
198 // wait for shutdown event to complete
199 SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns,
200 [&, shutdownWrapper]() { return shutdownWrapper->mBool; });
202 return NS_OK;
205 class ConnEvent : public Runnable {
206 public:
207 ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
208 int32_t iparam, ARefBase* vparam)
209 : Runnable("net::ConnEvent"),
210 mMgr(mgr),
211 mHandler(handler),
212 mIParam(iparam),
213 mVParam(vparam) {}
215 NS_IMETHOD Run() override {
216 (mMgr->*mHandler)(mIParam, mVParam);
217 return NS_OK;
220 private:
221 virtual ~ConnEvent() = default;
223 RefPtr<nsHttpConnectionMgr> mMgr;
224 nsConnEventHandler mHandler;
225 int32_t mIParam;
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;
239 if (!target) {
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.
255 if (mTimer) {
256 mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
257 mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
258 } else {
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
265 // need management
266 if (mNumIdleConns ||
267 (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
268 return;
271 LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
273 // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
274 mTimeOfNextWakeUp = UINT64_MAX;
275 if (mTimer) {
276 mTimer->Cancel();
277 mTimer = nullptr;
281 void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
282 LOG(
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 //-----------------------------------------------------------------------------
301 NS_IMETHODIMP
302 nsHttpConnectionMgr::GetName(nsACString& aName) {
303 aName.AssignLiteral("nsHttpConnectionMgr");
304 return NS_OK;
307 //-----------------------------------------------------------------------------
308 // nsHttpConnectionMgr::nsIObserver
309 //-----------------------------------------------------------------------------
311 NS_IMETHODIMP
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) {
321 TimeoutTick();
322 } else if (timer == mTrafficTimer) {
323 Unused << PruneNoTraffic();
324 } else if (timer == mThrottleTicker) {
325 ThrottlerTick();
326 } else if (timer == mDelayedResumeReadTimer) {
327 ResumeBackgroundThrottledTransactions();
328 } else {
329 MOZ_ASSERT(false, "unexpected timer-callback");
330 LOG(("Unexpected timer object\n"));
331 return NS_ERROR_UNEXPECTED;
335 return NS_OK;
338 //-----------------------------------------------------------------------------
340 nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
341 int32_t priority) {
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 {
350 public:
351 NewTransactionData(nsHttpTransaction* trans, int32_t priority,
352 nsHttpTransaction* transWithStickyConn)
353 : mTrans(trans),
354 mPriority(priority),
355 mTransWithStickyConn(transWithStickyConn) {}
357 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
359 RefPtr<nsHttpTransaction> mTrans;
360 int32_t mPriority;
361 RefPtr<nsHttpTransaction> mTransWithStickyConn;
363 private:
364 virtual ~NewTransactionData() = default;
367 nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
368 HttpTransactionShell* trans, int32_t priority,
369 HttpTransactionShell* transWithStickyConn) {
370 LOG(
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,
381 data);
384 nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
385 int32_t priority) {
386 LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
387 priority));
388 return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
389 trans->AsHttpTransaction());
392 void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
393 HttpTransactionShell* trans, const ClassOfService& classOfService) {
394 LOG(
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;
408 if (!target) {
409 NS_WARNING("cannot post event if not initialized");
410 return;
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());
419 }));
422 nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans,
423 nsresult reason) {
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,
450 nullptr);
453 nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
454 nsHttpConnectionInfo* aCI) {
455 if (!aCI) {
456 return NS_ERROR_INVALID_ARG;
459 RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
460 return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
461 ci);
464 nsresult nsHttpConnectionMgr::DoSingleConnectionCleanup(
465 nsHttpConnectionInfo* aCI) {
466 if (!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 {
475 public:
476 SpeculativeConnectArgs() = default;
477 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
479 public: // intentional!
480 RefPtr<SpeculativeTransaction> mTrans;
482 bool mFetchHTTPSRR{false};
484 private:
485 virtual ~SpeculativeConnectArgs() = default;
486 NS_DECL_OWNINGTHREAD
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.
494 net_EnsurePSMInit();
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()) {
508 LOG(
509 ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
510 "address [%s]",
511 ci->Origin()));
512 return NS_OK;
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
531 ? aTransaction
532 : new SpeculativeTransaction(ci, wrappedCallbacks, caps);
533 args->mFetchHTTPSRR = aFetchHTTPSRR;
535 if (overrider) {
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);
551 temp.forget(target);
552 return NS_OK;
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;
566 if (!target) {
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);
577 }));
580 // A structure used to marshall 6 pointers across the various necessary
581 // threads to complete an HTTP upgrade.
582 class nsCompleteUpgradeData : public ARefBase {
583 public:
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;
597 bool mJsWrapped;
599 private:
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;
627 if (aCI) {
628 ci = aCI->Clone();
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,
639 ARefBase* param) {
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,
649 aBucket);
652 nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
653 return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
654 nullptr);
657 void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
658 ARefBase* param) {
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) {
669 iter.Remove();
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;
688 return NS_OK;
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;
706 return NS_OK;
709 HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
710 ConnectionEntry* ent, const nsCString& key, bool justKidding, bool aNoHttp2,
711 bool aNoHttp3) {
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) {
719 return nullptr;
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
728 LOG(
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);
738 listLen--;
739 continue; // without adjusting iterator
742 if (aNoHttp3 && potentialMatch->UsingHttp3()) {
743 j++;
744 continue;
746 if (aNoHttp2 && potentialMatch->UsingSpdy()) {
747 j++;
748 continue;
750 bool couldJoin;
751 if (justKidding) {
752 couldJoin =
753 potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
754 } else {
755 couldJoin =
756 potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
758 if (couldJoin) {
759 LOG(
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();
766 LOG(
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);
779 return nullptr;
782 static void BuildOriginFrameHashKey(nsACString& newKey,
783 nsHttpConnectionInfo* ci,
784 const nsACString& host, int32_t port) {
785 newKey.Assign(host);
786 if (ci->GetAnonymous()) {
787 newKey.AppendLiteral("~A:");
788 } else {
789 newKey.AppendLiteral("~.:");
791 if (ci->GetFallbackConnection()) {
792 newKey.AppendLiteral("~F:");
793 } else {
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 "));
814 return nullptr;
816 // First try and look it up by origin frame
817 nsCString newKey;
818 BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
819 HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
820 ent, newKey, justKidding, aNoHttp2, aNoHttp3);
821 if (conn) {
822 LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
823 ci->HashKey().get(), conn, newKey.get()));
824 return conn;
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()) {
838 return true;
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.
843 NetAddr addr;
844 nsresult rv = conn->GetPeerAddr(&addr);
845 if (NS_FAILED(rv)) {
846 // Err on the side of not coalescing
847 return false;
849 // We don't care about remote port when matching entries.
850 addr.inet.port = 0;
851 return ent->mAddresses.Contains(addr);
854 if (conn) {
855 LOG(("Found connection with matching hash"));
856 if (usableEntry(conn)) {
857 LOG(("> coalescing"));
858 return conn;
859 } else {
860 LOG(("> not coalescing as remote address not present in DNS records"));
865 LOG(("FindCoalescableConnection(%s) no matching conn\n",
866 ci->HashKey().get()));
867 return nullptr;
870 void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
871 HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) {
872 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
873 MOZ_ASSERT(newConn);
874 MOZ_ASSERT(newConn->ConnectionInfo());
875 MOZ_ASSERT(ent);
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.
881 return;
884 HttpConnectionBase* existingConn =
885 FindCoalescableConnection(ent, true, false, false);
886 if (existingConn) {
887 // Prefer http3 connection, but allow an HTTP/2 connection if it is used for
888 // WebSocket.
889 if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
890 RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn);
891 if (connTCP && !connTCP->IsForWebSocket()) {
892 LOG(
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) {
903 LOG(
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();
911 return;
913 } else {
914 LOG(
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();
922 return;
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()) {
929 return;
932 uint32_t keyLen = ent->mCoalescingKeys.Length();
933 for (uint32_t i = 0; i < keyLen; ++i) {
934 LOG((
935 "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
936 newConn, newConn->ConnectionInfo()->HashKey().get(),
937 ent->mCoalescingKeys[i].get()));
939 mCoalescingHash
940 .LookupOrInsertWith(
941 ent->mCoalescingKeys[i],
942 [] {
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,
967 bool usingSpdy,
968 bool disallowHttp3) {
969 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
970 if (!conn->ConnectionInfo()) {
971 return;
973 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
974 if (!ent || !usingSpdy) {
975 return;
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);
991 if (NS_FAILED(rv)) {
992 LOG(
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);
998 if (NS_FAILED(rv)) {
999 LOG(
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()) {
1009 return;
1011 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
1012 if (!ent) {
1013 return;
1016 mNumSpdyHttp3ActiveConns++;
1018 UpdateCoalescingForNewConn(conn, ent, false);
1019 nsresult rv = ProcessPendingQ(ent->mConnInfo);
1020 if (NS_FAILED(rv)) {
1021 LOG(
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)) {
1028 LOG(
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,
1038 bool considerAll) {
1039 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1041 PendingTransactionInfo* pendingTransInfo = nullptr;
1042 nsresult rv;
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,
1055 pendingTransInfo);
1056 if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
1057 if (NS_SUCCEEDED(rv)) {
1058 LOG((" dispatching pending transaction...\n"));
1059 } else {
1060 LOG(
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;
1076 ++i;
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,
1092 bool considerAll) {
1093 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1095 pendingQ.Clear();
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) {
1104 return;
1107 // Only have to get transactions from the queue whose window id is 0.
1108 if (!gHttpHandler->ActiveTabPriority()) {
1109 ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
1110 return;
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.
1123 if (!considerAll) {
1124 ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
1125 maxFocusedWindowConnections);
1127 if (pendingQ.IsEmpty()) {
1128 ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ,
1129 availableConnections);
1131 return;
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);
1162 LOG(
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,
1174 bool considerAll) {
1175 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1177 LOG(
1178 ("nsHttpConnectionMgr::ProcessPendingQForEntry "
1179 "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
1180 " queued=%zu]\n",
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()) {
1191 return false;
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.
1228 if (considerAll) {
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);
1240 return false;
1243 // we're at the active connection limit if any one of the following conditions
1244 // is true:
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,
1249 uint32_t caps) {
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.
1256 return false;
1258 return totalCount > 0;
1261 uint32_t maxPersistConns = MaxPersistConnections(ent);
1263 LOG(
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)) {
1270 LOG((
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 "
1273 "connections.\n"));
1274 return true;
1276 return false;
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,
1286 mMaxConns));
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"));
1293 return true;
1296 bool result = (totalCount >= maxPersistConns);
1297 LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
1298 return result;
1301 // returns NS_OK if a connection was started
1302 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
1303 // ephemeral limits
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)) {
1312 return NS_OK;
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()) {
1322 LOG(
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) -
1343 mMaxConns);
1344 iter.Next();
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
1352 // ASAP.
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) {
1358 goto outerLoopEnd;
1362 outerLoopEnd:;
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,
1372 pendingTransInfo);
1373 if (NS_FAILED(rv)) {
1374 /* hard failure */
1375 LOG(
1376 ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
1377 "CreateDnsAndConnectSocket() hard failure.\n",
1378 ent->mConnInfo->HashKey().get(), trans));
1379 trans->Close(rv);
1380 if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE;
1381 return rv;
1384 return NS_OK;
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();
1400 LOG(
1401 ("nsHttpConnectionMgr::TryDispatchTransaction without conn "
1402 "[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu "
1403 "idle=%zu]\n",
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
1421 // global limits
1422 // 6 - no connection is available - queue it
1424 RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection;
1426 // step 0
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(
1431 ent,
1432 (!StaticPrefs::network_http_http2_enabled() ||
1433 (caps & NS_HTTP_DISALLOW_SPDY)),
1434 (!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
1435 if (conn) {
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) {
1441 LOG((
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
1445 // transaction.
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();
1464 return rv;
1465 } else {
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;
1471 } else {
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);
1479 return NS_OK;
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;
1494 if (NS_SUCCEEDED(
1495 requestContext->GetBlockingTransactionCount(&blockers)) &&
1496 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;
1504 } else {
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();
1510 // step 1
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
1520 // limited.
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();
1525 if (!runNow) {
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
1533 if (!runNow) {
1534 LOG((" blocked due to rate pacing trans=%p\n", trans));
1535 return NS_ERROR_NOT_AVAILABLE;
1539 // step 2
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));
1547 return NS_OK;
1551 // step 3
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;
1562 // step 4
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));
1576 return rv;
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));
1590 return NS_OK;
1595 // step 5
1596 // previously pipelined anything here if allowed but h1 pipelining has been
1597 // removed
1599 // step 6
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
1603 // request
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();
1617 bool urgentTrans =
1618 trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart;
1620 LOG(
1621 ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
1622 "trans=%p, urgent=%d",
1623 ent, trans, urgentTrans));
1625 RefPtr<nsHttpConnection> conn =
1626 ent->GetIdleConnection(respectUrgency, urgentTrans, &onlyUrgent);
1628 if (allUrgent) {
1629 *allUrgent = onlyUrgent;
1632 if (conn) {
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);
1639 return NS_OK;
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();
1650 nsresult rv;
1652 LOG(
1653 ("nsHttpConnectionMgr::DispatchTransaction "
1654 "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
1655 "isHttp3=%d]\n",
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(
1670 elapsed);
1672 } else {
1673 glean::networking::transaction_wait_time.AccumulateRawDuration(elapsed);
1677 PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime,
1678 elapsed);
1680 PROFILER_MARKER(
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()) {
1688 LOG(
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);
1698 } else {
1699 httpVersionkey = "h3"_ns;
1700 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3,
1701 trans->GetPendingTime(), now);
1703 recordPendingTimeForHTTPSRR(httpVersionkey);
1704 trans->SetPendingTime(false);
1706 return rv;
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);
1720 return rv;
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) {
1730 MOZ_ASSERT(ent);
1732 nsresult rv;
1733 MOZ_ASSERT(!conn->UsingSpdy(),
1734 "Spdy Must Not Use DispatchAbstractTransaction");
1735 LOG(
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
1759 return rv;
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);
1771 } else {
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"));
1785 return NS_OK;
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();
1801 if (pushedStream) {
1802 RefPtr<Http2Session> session = pushedStream->Session();
1803 LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans,
1804 session.get()));
1805 return session->AddStream(trans, trans->Priority(), nullptr)
1806 ? NS_OK
1807 : NS_ERROR_UNEXPECTED;
1811 nsresult rv = NS_OK;
1812 nsHttpConnectionInfo* ci = trans->ConnectionInfo();
1813 MOZ_ASSERT(ci);
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);
1821 MOZ_ASSERT(ent);
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();
1838 if (conn) {
1839 MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
1840 LOG(
1841 ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
1842 "sticky connection=%p\n",
1843 trans, conn.get()));
1845 if (!ent->IsInActiveConns(conn)) {
1846 LOG(
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
1863 // connection.
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());
1870 if (!specificEnt) {
1871 RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
1872 specificEnt = new ConnectionEntry(clone);
1873 mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
1876 ent = specificEnt;
1877 bool atLimit = AtActiveConnectionLimit(ent, trans->Caps());
1878 if (atLimit) {
1879 rv = NS_ERROR_NOT_AVAILABLE;
1880 } else {
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();
1891 } else {
1892 rv = DispatchTransaction(ent, trans, connTCP);
1894 } else {
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));
1904 return rv;
1907 if (rv == NS_ERROR_NOT_AVAILABLE) {
1908 if (!pendingTransInfo) {
1909 pendingTransInfo = new PendingTransactionInfo(trans);
1912 ent->InsertTransaction(pendingTransInfo);
1913 return NS_OK;
1916 LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans,
1917 static_cast<uint32_t>(rv)));
1918 return rv;
1921 void nsHttpConnectionMgr::IncrementActiveConnCount() {
1922 mNumActiveConns++;
1923 ActivateTimeoutTick();
1926 void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) {
1927 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
1928 if (mNumActiveConns > 0) {
1929 mNumActiveConns--;
1932 RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
1933 if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--;
1934 ConditionallyStopTimeoutTick();
1937 void nsHttpConnectionMgr::StartedConnect() {
1938 mNumActiveConns++;
1939 ActivateTimeoutTick(); // likely disabled by RecvdConnect()
1942 void nsHttpConnectionMgr::RecvdConnect() {
1943 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
1944 if (mNumActiveConns > 0) {
1945 mNumActiveConns--;
1948 ConditionallyStopTimeoutTick();
1951 void nsHttpConnectionMgr::DispatchSpdyPendingQ(
1952 nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
1953 HttpConnectionBase* connH2, HttpConnectionBase* connH3) {
1954 if (pendingQ.Length() == 0) {
1955 return;
1958 nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
1959 uint32_t index;
1960 // Dispatch all the transactions we can
1961 for (index = 0; index < pendingQ.Length() &&
1962 ((connH3 && connH3->CanDirectlyActivate()) ||
1963 (connH2 && connH2->CanDirectlyActivate()));
1964 ++index) {
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);
1970 continue;
1973 // Try dispatching on HTTP3 first
1974 HttpConnectionBase* conn = nullptr;
1975 if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3) &&
1976 connH3 && connH3->CanDirectlyActivate()) {
1977 conn = connH3;
1978 } else if (!(pendingTransInfo->Transaction()->Caps() &
1979 NS_HTTP_DISALLOW_SPDY) &&
1980 connH2 && connH2->CanDirectlyActivate()) {
1981 conn = connH2;
1982 } else {
1983 leftovers.AppendElement(pendingTransInfo);
1984 continue;
1987 nsresult rv =
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())) {
2023 return;
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())) {
2035 return;
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) {
2062 return nullptr;
2064 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2065 MOZ_ASSERT(ent);
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();
2071 if (conn) {
2072 return conn;
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);
2082 if (existingConn) {
2083 LOG(
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;
2090 LOG(
2091 ("GetH2orH3ActiveConn() request for ent %p %s "
2092 "did not find an active connection\n",
2093 ent, ci->HashKey().get()));
2094 return nullptr;
2097 //-----------------------------------------------------------------------------
2099 void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) {
2100 if (!OnSocketThread()) {
2101 Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
2102 return;
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);
2128 iter.Remove();
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
2143 // connections.
2144 ConditionallyStopPruneDeadConnectionsTimer();
2146 if (mTimeoutTick) {
2147 mTimeoutTick->Cancel();
2148 mTimeoutTick = nullptr;
2149 mTimeoutTickArmed = false;
2151 if (mTimer) {
2152 mTimer->Cancel();
2153 mTimer = nullptr;
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,
2170 ARefBase* param) {
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,
2179 ARefBase* param) {
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,
2189 ARefBase* param) {
2190 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2192 NewTransactionData* data = static_cast<NewTransactionData*>(param);
2193 LOG(
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
2208 // for details.
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,
2221 ARefBase* param) {
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()) {
2229 return;
2231 ConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
2233 if (ent) {
2234 ent->ReschedTransaction(trans);
2238 void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
2239 ClassOfService cos, ARefBase* param) {
2240 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2241 LOG(
2242 ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
2243 "[trans=%p]\n",
2244 param));
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,
2259 ARefBase* param) {
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);
2276 } else {
2277 ConnectionEntry* ent = nullptr;
2278 if (trans->ConnectionInfo()) {
2279 ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
2281 if (ent && ent->RemoveTransFromPendingQ(trans)) {
2282 LOG(
2283 ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
2284 " removed from pending queue\n",
2285 trans));
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.
2296 if (ent) {
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);
2306 if (!ci) {
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);
2312 return;
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)) {
2325 break;
2331 nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci,
2332 nsresult code) {
2333 LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get()));
2335 int32_t intReason = static_cast<int32_t>(code);
2336 return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason,
2337 ci);
2340 void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code,
2341 ARefBase* param) {
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));
2347 if (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);
2384 } else {
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"));
2399 iter.Remove();
2400 continue;
2403 // Otherwise use this opportunity to compact our arrays...
2404 ent->Compact();
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.
2429 return;
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);
2451 } else {
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,
2459 ARefBase* param) {
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,
2475 ARefBase* param) {
2476 LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n"));
2477 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2479 nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
2481 if (!ci) {
2482 return;
2485 ConnectionEntry* entry = mCT.GetWeak(ci->HashKey());
2486 if (entry) {
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
2502 MOZ_ASSERT(conn);
2503 ConnectionEntry* ent = conn->ConnectionInfo()
2504 ? mCT.GetWeak(conn->ConnectionInfo()->HashKey())
2505 : nullptr;
2507 if (!ent) {
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,
2513 &isWildcard);
2514 LOG(
2515 ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
2516 "forced new hash entry %s\n",
2517 conn, conn->ConnectionInfo()->HashKey().get()));
2520 MOZ_ASSERT(ent);
2521 RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
2523 LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent,
2524 conn));
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
2538 // reused.
2539 conn->DontReuse();
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()) {
2546 conn->DontReuse();
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",
2553 conn));
2554 // repeat for flipped anon flag as we share connection entries for spdy
2555 // connections.
2556 RefPtr<nsHttpConnectionInfo> anonInvertedCI(ci->Clone());
2557 anonInvertedCI->SetAnonymous(!ci->GetAnonymous());
2559 ConnectionEntry* ent = mCT.GetWeak(anonInvertedCI->HashKey());
2560 if (ent) {
2561 if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn))) {
2562 } else {
2563 LOG(
2564 ("HttpConnectionBase %p could not be removed from its entry's "
2565 "active list",
2566 conn));
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);
2581 } else {
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());
2601 LOG(
2602 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
2603 "conn=%p listener=%p wrapped=%d\n",
2604 conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
2606 if (!conn) {
2607 // Delay any error reporting to happen in transportAvailableFunc
2608 rv = NS_ERROR_UNEXPECTED;
2609 } else {
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
2631 // thread.
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,
2639 segsize, segcount);
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,
2647 segsize, segcount);
2648 rv = NS_AsyncCopy(data->mSocketIn, pipeOut, gSocketTransportService,
2649 NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
2651 } else {
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.
2660 nsresult rv = aRv;
2662 if (NS_FAILED(rv)) {
2663 rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv);
2664 if (NS_FAILED(rv)) {
2665 LOG(
2666 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
2667 " listener=%p\n",
2668 upgradeData->mUpgradeListener.get()));
2670 return;
2673 rv = upgradeData->mUpgradeListener->OnTransportAvailable(
2674 upgradeData->mSocketTransport, socketIn, socketOut);
2675 if (NS_FAILED(rv)) {
2676 LOG(
2677 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
2678 "failed. listener=%p\n",
2679 upgradeData->mUpgradeListener.get()));
2683 if (data->mJsWrapped) {
2684 LOG(
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));
2691 } else {
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;
2702 switch (name) {
2703 case MAX_CONNECTIONS:
2704 mMaxConns = value;
2705 break;
2706 case MAX_URGENT_START_Q:
2707 mMaxUrgentExcessiveConns = value;
2708 break;
2709 case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
2710 mMaxPersistConnsPerHost = value;
2711 break;
2712 case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
2713 mMaxPersistConnsPerProxy = value;
2714 break;
2715 case MAX_REQUEST_DELAY:
2716 mMaxRequestDelay = value;
2717 break;
2718 case THROTTLING_ENABLED:
2719 SetThrottlingEnabled(!!value);
2720 break;
2721 case THROTTLING_SUSPEND_FOR:
2722 mThrottleSuspendFor = value;
2723 break;
2724 case THROTTLING_RESUME_FOR:
2725 mThrottleResumeFor = value;
2726 break;
2727 case THROTTLING_READ_LIMIT:
2728 mThrottleReadLimit = value;
2729 break;
2730 case THROTTLING_READ_INTERVAL:
2731 mThrottleReadInterval = value;
2732 break;
2733 case THROTTLING_HOLD_TIME:
2734 mThrottleHoldTime = value;
2735 break;
2736 case THROTTLING_MAX_TIME:
2737 mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
2738 break;
2739 case PROXY_BE_CONSERVATIVE:
2740 mBeConservativeForProxy = !!value;
2741 break;
2742 default:
2743 MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
2747 // Read Timeout Tick handlers
2749 void nsHttpConnectionMgr::ActivateTimeoutTick() {
2750 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2751 LOG(
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);
2766 return;
2769 if (!mTimeoutTick) {
2770 mTimeoutTick = NS_NewTimer();
2771 if (!mTimeoutTick) {
2772 NS_WARNING("failed to create timer for http timeout management");
2773 return;
2775 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
2776 if (!mSocketThreadTarget) {
2777 NS_WARNING("cannot activate timout if not initialized or shutdown");
2778 return;
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)
2786 return;
2788 MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
2789 mTimeoutTickArmed = true;
2790 mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
2793 class UINT64Wrapper : public ARefBase {
2794 public:
2795 explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
2796 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
2798 uint64_t GetValue() { return mUint64; }
2800 private:
2801 uint64_t 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,
2808 idWrapper);
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();
2819 } else {
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()) {
2830 return true;
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()) {
2850 return;
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();
2864 bu -= au;
2865 for (const auto& data : mActiveTransactions[true].Values()) {
2866 bt += data->Length();
2868 bt -= at;
2870 // Shows counts of:
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
2892 "(%d) thr=%d",
2893 aTrans, tabId, tabId == mCurrentBrowserId, throttled));
2894 LogActiveTransactions('+');
2896 if (tabId == mCurrentBrowserId) {
2897 mActiveTabTransactionsExist = true;
2898 if (!throttled) {
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) {
2912 return;
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.
2931 return;
2934 LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
2935 "(%d) thr=%d",
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('-');
2943 return;
2946 // To optimize the following logic, always remove the entry when the array is
2947 // empty.
2948 mActiveTransactions[throttled].Remove(tabId);
2949 LogActiveTransactions('-');
2951 if (forActiveTab) {
2952 // Update caches of the active tab transaction existence, since it's now
2953 // affected
2954 if (!throttled) {
2955 mActiveTabUnthrottledTransactionsExist = false;
2957 if (mActiveTabTransactionsExist) {
2958 mActiveTabTransactionsExist =
2959 mActiveTransactions[!throttled].Contains(tabId);
2963 if (!mThrottleEnabled) {
2964 return;
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
2972 // done.
2973 MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
2974 MOZ_ASSERT(!mActiveTabTransactionsExist);
2976 DestroyThrottleTicker();
2977 return;
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"));
2985 return;
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"));
2993 return;
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));
3004 return;
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();
3012 return;
3015 if (forActiveTab) {
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();
3020 return;
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) {
3051 return false;
3053 } else {
3054 if (!mThrottleEnabled) {
3055 return false;
3059 uint64_t tabId = aTrans->BrowserId();
3060 bool forActiveTab = tabId == mCurrentBrowserId;
3061 bool throttled = aTrans->EligibleForThrottling();
3063 bool stop = [=]() {
3064 if (mActiveTabTransactionsExist) {
3065 if (!tabId) {
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));
3070 return 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"));
3076 return true;
3079 if (mActiveTabUnthrottledTransactionsExist) {
3080 // Unthrottled transactions for the active tab take precedence
3081 LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
3082 return throttled;
3085 LOG((" trans for active tab, don't throttle"));
3086 return false;
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",
3097 throttled));
3098 return throttled;
3101 // There are only unthrottled transactions for background tabs: don't
3102 // throttle.
3103 LOG((" backround tab(s) load throttled, don't throttle"));
3104 return false;
3105 }();
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();
3112 return false;
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());
3136 if (!ent) {
3137 // No entry, no pressure.
3138 return false;
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"));
3150 return true;
3153 if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) {
3154 LOG((" there are throttled transactions for both active and bck"));
3155 return true;
3158 if (!mActiveTransactions[true].IsEmpty() &&
3159 !mActiveTransactions[false].IsEmpty()) {
3160 LOG((" there are both throttled and unthrottled transactions"));
3161 return true;
3164 LOG((" nothing to throttle"));
3165 return false;
3168 void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
3169 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3171 LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
3172 if (!IsThrottleTickerNeeded()) {
3173 return;
3176 // There is a new demand to throttle, hence unschedule delayed resume
3177 // of background throttled transastions.
3178 CancelDelayedResumeBackgroundThrottledTransactions();
3180 if (mThrottleTicker) {
3181 return;
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;
3191 } else {
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) {
3210 return;
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);
3247 } else {
3248 if (mThrottleTicker) {
3249 mThrottleTicker->Init(this, mThrottleResumeFor,
3250 nsITimer::TYPE_ONE_SHOT);
3253 ResumeReadOf(mActiveTransactions[false], true);
3254 ResumeReadOf(mActiveTransactions[true]);
3256 } else {
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) {
3282 return;
3284 } else {
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) {
3290 return;
3294 LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
3295 NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this,
3296 mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
3299 void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
3300 if (!mDelayedResumeReadTimer) {
3301 return;
3304 LOG(
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);
3323 } else {
3324 ResumeReadOf(mActiveTransactions[true], true);
3328 void nsHttpConnectionMgr::ResumeReadOf(
3329 nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&
3330 hashtable,
3331 bool excludeForActiveTab) {
3332 for (const auto& entry : hashtable) {
3333 if (excludeForActiveTab && entry.GetKey() == mCurrentBrowserId) {
3334 // These have never been throttled (never stopped reading)
3335 continue;
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) {
3359 if (!trans) {
3360 return;
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,
3389 ARefBase* param) {
3390 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3392 uint64_t id = static_cast<UINT64Wrapper*>(param)->GetValue();
3394 if (mCurrentBrowserId == id) {
3395 // duplicate notification
3396 return;
3399 bool activeTabWasLoading = mActiveTabTransactionsExist;
3401 uint64_t previousId = mCurrentBrowserId;
3402 mCurrentBrowserId = id;
3404 if (gHttpHandler->ActiveTabPriority()) {
3405 NotifyConnectionOfBrowserIdChange(previousId);
3408 LOG(
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;
3425 if (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);
3430 return;
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.
3437 return;
3440 if (!mActiveTransactions[false].IsEmpty()) {
3441 LOG((" resuming unthrottled background transactions"));
3442 ResumeReadOf(mActiveTransactions[false]);
3443 return;
3446 if (!mActiveTransactions[true].IsEmpty()) {
3447 LOG((" resuming throttled background transactions"));
3448 ResumeReadOf(mActiveTransactions[true]);
3449 return;
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);
3470 if (mTimeoutTick) {
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;
3490 // step 1
3491 ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
3492 if (specificEnt && specificEnt->AvailableForDispatchNow()) {
3493 if (aAvailableForDispatchNow) {
3494 *aAvailableForDispatchNow = true;
3496 return specificEnt;
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());
3505 if (invertedEnt) {
3506 HttpConnectionBase* h2orh3conn =
3507 GetH2orH3ActiveConn(invertedEnt, aNoHttp2, aNoHttp3);
3508 if (h2orh3conn && h2orh3conn->IsExperienced() &&
3509 h2orh3conn->NoClientCertAuth()) {
3510 MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3());
3511 LOG(
3512 ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
3513 "connections, ent=%p",
3514 invertedEnt));
3515 return invertedEnt;
3519 if (!specificCI->UsingHttpsProxy()) {
3520 prohibitWildCard = true;
3523 // step 2
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;
3535 return wildCardEnt;
3539 // step 3
3540 if (!specificEnt) {
3541 RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
3542 specificEnt = new ConnectionEntry(clone);
3543 mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
3545 return specificEnt;
3548 void nsHttpConnectionMgr::DoSpeculativeConnection(
3549 SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
3550 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3551 MOZ_ASSERT(aTrans);
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
3562 // echConfig.
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");
3571 MOZ_ASSERT(aTrans);
3572 MOZ_ASSERT(aEnt);
3573 if (!gHttpHandler->Active()) {
3574 // Do nothing if we are shutting down.
3575 return;
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
3582 // used.
3583 if (aFetchHTTPSRR && strategy == ProxyDNSStrategy::ORIGIN &&
3584 NS_SUCCEEDED(aTrans->FetchHTTPSRR())) {
3585 // nsHttpConnectionMgr::DoSpeculativeConnection will be called again
3586 // when HTTPS RR is available.
3587 return;
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 &&
3601 ((ignoreIdle &&
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)
3612 .Add(1);
3613 LOG(
3614 ("DoSpeculativeConnectionInternal Transport socket creation "
3615 "failure: %" PRIx32 "\n",
3616 static_cast<uint32_t>(rv)));
3617 } else {
3618 glean::networking::speculative_connect_outcome.Get("successful"_ns)
3619 .Add(1);
3621 } else {
3622 glean::networking::speculative_connect_outcome
3623 .Get("aborted_socket_limit"_ns)
3624 .Add(1);
3625 LOG(
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");
3635 MOZ_ASSERT(aTrans);
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) {
3647 LOG(
3648 ("nsHttpConnectionMgr::DoFallbackConnection fallback connection is "
3649 "ready for dispatching ent=%p",
3650 ent));
3651 aTrans->InvokeCallback();
3652 return;
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);
3663 LOG(
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.
3674 return true;
3677 if (!proxy) {
3678 // There is no proxy, so be conservative by default.
3679 return true;
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
3690 // origin keys
3691 void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn,
3692 const nsACString& host,
3693 int32_t port) {
3694 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3695 nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr;
3696 if (!ci || !conn->CanDirectlyActivate()) {
3697 return;
3700 nsCString newKey;
3701 BuildOriginFrameHashKey(newKey, ci, host, port);
3702 mCoalescingHash.GetOrInsertNew(newKey, 1)->AppendElement(
3703 do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
3705 LOG(
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()) {
3714 continue;
3716 aArg->AppendElement(ent->GetConnectionData());
3719 return true;
3722 void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) {
3723 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3724 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
3725 if (ent) {
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());
3734 if (!ent) {
3735 LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!"));
3736 return;
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());
3746 if (!ent) {
3747 LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!"));
3748 return;
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());
3760 LOG(
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());
3766 LOG(
3767 ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
3768 "%d)\n",
3769 proxyConn, ent, ent ? ent->mUsingSpdy : 0));
3771 if (!ent || !ent->mUsingSpdy) {
3772 return;
3775 bool isWildcard = false;
3776 ConnectionEntry* wcEnt =
3777 GetOrCreateConnectionEntry(wildCardCI, true, false, false, &isWildcard);
3778 if (wcEnt == ent) {
3779 // nothing to do!
3780 return;
3782 wcEnt->mUsingSpdy = true;
3784 LOG(
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()));
3790 LOG(
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()) {
3807 return false;
3810 // Step 1: Get the transaction's connection entry.
3811 ConnectionEntry* entry = mCT.GetWeak(aHashKey);
3812 if (!entry) {
3813 return false;
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);
3838 int32_t index =
3839 pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
3841 RefPtr<PendingTransactionInfo> info;
3842 if (index != -1) {
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() {
3859 return nullptr;
3862 void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive) {
3863 mNumIdleConns++;
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);
3875 mNumIdleConns--;
3876 ConditionallyStopPruneDeadConnectionsTimer();
3879 // A structure used to marshall objects necessary for ServerCertificateHashaes
3880 class nsStoreServerCertHashesData : public ARefBase {
3881 public:
3882 nsStoreServerCertHashesData(
3883 nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3,
3884 nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes)
3885 : mConnInfo(aConnInfo),
3886 mNoSpdy(aNoSpdy),
3887 mNoHttp3(aNoHttp3),
3888 mServerCertHashes(std::move(aServerCertHashes)) {}
3890 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStoreServerCertHashesData, override)
3892 RefPtr<nsHttpConnectionInfo> mConnInfo;
3893 bool mNoSpdy;
3894 bool mNoHttp3;
3895 nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
3897 private:
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);
3918 bool isWildcard;
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());
3928 if (!connEnt) {
3929 MOZ_ASSERT(0);
3930 return nullptr;
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()) {
3940 return;
3943 nsAutoCString hashKey;
3944 aTrans->GetHashKeyOfConnectionEntry(hashKey);
3945 if (hashKey.IsEmpty()) {
3946 return;
3949 bool foundInPendingQ = RemoveTransFromConnEntry(aTrans, hashKey);
3950 MOZ_DIAGNOSTIC_ASSERT(!foundInPendingQ);
3951 #endif
3954 bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3(
3955 nsHttpConnectionInfo* ci, nsresult aError) {
3956 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
3957 if (!ent) {
3958 return false;
3961 return ent->AllowToRetryDifferentIPFamilyForHttp3(aError);
3964 void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3(
3965 nsHttpConnectionInfo* ci, uint16_t aIPFamily) {
3966 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
3967 if (!ent) {
3968 return;
3971 ent->SetRetryDifferentIPFamilyForHttp3(aIPFamily);
3974 } // namespace mozilla::net