nsHttpConnectionMgr.cpp (137958B)
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/. */ 5 6 // HttpLog.h should generally be included first 7 #include "HttpLog.h" 8 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() 14 15 #include <algorithm> 16 #include <utility> 17 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/glean/NetwerkProtocolHttpMetrics.h" 28 #include "mozilla/glean/NetwerkMetrics.h" 29 #include "mozilla/net/DNS.h" 30 #include "mozilla/net/DashboardTypes.h" 31 #include "nsCOMPtr.h" 32 #include "nsHttpConnectionMgr.h" 33 #include "nsHttpHandler.h" 34 #include "nsIClassOfService.h" 35 #include "nsIDNSByTypeRecord.h" 36 #include "nsIDNSListener.h" 37 #include "nsIDNSRecord.h" 38 #include "nsIDNSService.h" 39 #include "nsIHttpChannelInternal.h" 40 #include "nsIPipe.h" 41 #include "nsIRequestContext.h" 42 #include "nsISocketTransport.h" 43 #include "nsISocketTransportService.h" 44 #include "nsITransport.h" 45 #include "nsIXPConnect.h" 46 #include "nsInterfaceRequestorAgg.h" 47 #include "nsNetCID.h" 48 #include "nsNetSegmentUtils.h" 49 #include "nsNetUtil.h" 50 #include "nsQueryObject.h" 51 #include "nsSocketTransportService2.h" 52 #include "nsStreamUtils.h" 53 54 using namespace mozilla; 55 56 namespace geckoprofiler::markers { 57 58 struct UrlMarker { 59 static constexpr Span<const char> MarkerTypeName() { 60 return MakeStringSpan("Url"); 61 } 62 static void StreamJSONMarkerData( 63 mozilla::baseprofiler::SpliceableJSONWriter& aWriter, 64 const mozilla::ProfilerString8View& aURL, const TimeDuration& aDuration, 65 uint64_t aChannelId) { 66 if (aURL.Length() != 0) { 67 aWriter.StringProperty("url", aURL); 68 } 69 if (!aDuration.IsZero()) { 70 aWriter.DoubleProperty("duration", aDuration.ToMilliseconds()); 71 } 72 aWriter.IntProperty("channelId", static_cast<int64_t>(aChannelId)); 73 } 74 static MarkerSchema MarkerTypeDisplay() { 75 using MS = MarkerSchema; 76 MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable); 77 schema.SetTableLabel("{marker.data.url}"); 78 schema.AddKeyFormat("url", MS::Format::Url, MS::PayloadFlags::Searchable); 79 schema.AddKeyLabelFormat("duration", "Duration", MS::Format::Duration); 80 return schema; 81 } 82 }; 83 84 } // namespace geckoprofiler::markers 85 86 namespace mozilla::net { 87 88 //----------------------------------------------------------------------------- 89 90 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver, nsINamed) 91 92 //----------------------------------------------------------------------------- 93 94 nsHttpConnectionMgr::nsHttpConnectionMgr() { 95 LOG(("Creating nsHttpConnectionMgr @%p\n", this)); 96 } 97 98 nsHttpConnectionMgr::~nsHttpConnectionMgr() { 99 LOG(("Destroying nsHttpConnectionMgr @%p\n", this)); 100 MOZ_ASSERT(mCoalescingHash.Count() == 0); 101 if (mTimeoutTick) mTimeoutTick->Cancel(); 102 } 103 104 nsresult nsHttpConnectionMgr::EnsureSocketThreadTarget() { 105 nsCOMPtr<nsIEventTarget> sts; 106 nsCOMPtr<nsIIOService> ioService = components::IO::Service(); 107 if (ioService) { 108 nsCOMPtr<nsISocketTransportService> realSTS = 109 components::SocketTransport::Service(); 110 sts = do_QueryInterface(realSTS); 111 } 112 113 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 114 115 // do nothing if already initialized or if we've shut down 116 if (mSocketThreadTarget || mIsShuttingDown) return NS_OK; 117 118 mSocketThreadTarget = sts; 119 120 return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE; 121 } 122 123 nsresult nsHttpConnectionMgr::Init( 124 uint16_t maxUrgentExcessiveConns, uint16_t maxConns, 125 uint16_t maxPersistConnsPerHost, uint16_t maxPersistConnsPerProxy, 126 uint16_t maxRequestDelay, bool throttleEnabled, uint32_t throttleSuspendFor, 127 uint32_t throttleResumeFor, uint32_t throttleHoldTime, 128 uint32_t throttleMaxTime, bool beConservativeForProxy) { 129 LOG(("nsHttpConnectionMgr::Init\n")); 130 131 { 132 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 133 134 mMaxUrgentExcessiveConns = maxUrgentExcessiveConns; 135 mMaxConns = maxConns; 136 mMaxPersistConnsPerHost = maxPersistConnsPerHost; 137 mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; 138 mMaxRequestDelay = maxRequestDelay; 139 140 mThrottleEnabled = throttleEnabled; 141 mThrottleSuspendFor = throttleSuspendFor; 142 mThrottleResumeFor = throttleResumeFor; 143 mThrottleHoldTime = throttleHoldTime; 144 mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime); 145 146 mBeConservativeForProxy = beConservativeForProxy; 147 148 mIsShuttingDown = false; 149 } 150 151 return EnsureSocketThreadTarget(); 152 } 153 154 class BoolWrapper : public ARefBase { 155 public: 156 BoolWrapper() = default; 157 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override) 158 159 public: // intentional! 160 bool mBool{false}; 161 162 private: 163 virtual ~BoolWrapper() = default; 164 }; 165 166 nsresult nsHttpConnectionMgr::Shutdown() { 167 LOG(("nsHttpConnectionMgr::Shutdown\n")); 168 169 RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper(); 170 { 171 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 172 173 // do nothing if already shutdown 174 if (!mSocketThreadTarget) return NS_OK; 175 176 nsresult rv = 177 PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, 0, shutdownWrapper); 178 179 // release our reference to the STS to prevent further events 180 // from being posted. this is how we indicate that we are 181 // shutting down. 182 mIsShuttingDown = true; 183 mSocketThreadTarget = nullptr; 184 185 if (NS_FAILED(rv)) { 186 NS_WARNING("unable to post SHUTDOWN message"); 187 return rv; 188 } 189 } 190 191 // wait for shutdown event to complete 192 SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns, 193 [&, shutdownWrapper]() { return shutdownWrapper->mBool; }); 194 195 return NS_OK; 196 } 197 198 class ConnEvent : public Runnable { 199 public: 200 ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler, 201 int32_t iparam, ARefBase* vparam) 202 : Runnable("net::ConnEvent"), 203 mMgr(mgr), 204 mHandler(handler), 205 mIParam(iparam), 206 mVParam(vparam) {} 207 208 NS_IMETHOD Run() override { 209 (mMgr->*mHandler)(mIParam, mVParam); 210 return NS_OK; 211 } 212 213 private: 214 virtual ~ConnEvent() = default; 215 216 RefPtr<nsHttpConnectionMgr> mMgr; 217 nsConnEventHandler mHandler; 218 int32_t mIParam; 219 RefPtr<ARefBase> mVParam; 220 }; 221 222 nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, 223 int32_t iparam, ARefBase* vparam) { 224 (void)EnsureSocketThreadTarget(); 225 226 nsCOMPtr<nsIEventTarget> target; 227 { 228 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 229 target = mSocketThreadTarget; 230 } 231 232 if (!target) { 233 NS_WARNING("cannot post event if not initialized"); 234 return NS_ERROR_NOT_INITIALIZED; 235 } 236 237 nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam); 238 return target->Dispatch(event, NS_DISPATCH_NORMAL); 239 } 240 241 void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) { 242 LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n")); 243 244 if (!mTimer) mTimer = NS_NewTimer(); 245 246 // failure to create a timer is not a fatal error, but idle connections 247 // will not be cleaned up until we try to use them. 248 if (mTimer) { 249 mTimeOfNextWakeUp = timeInSeconds + NowInSeconds(); 250 mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT); 251 } else { 252 NS_WARNING("failed to create: timer for pruning the dead connections!"); 253 } 254 } 255 256 void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { 257 // Leave the timer in place if there are connections that potentially 258 // need management 259 if (mNumIdleConns || 260 (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) { 261 return; 262 } 263 264 LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); 265 266 // Reset mTimeOfNextWakeUp so that we can find a new shortest value. 267 mTimeOfNextWakeUp = UINT64_MAX; 268 if (mTimer) { 269 mTimer->Cancel(); 270 mTimer = nullptr; 271 } 272 } 273 274 void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() { 275 LOG( 276 ("nsHttpConnectionMgr::ConditionallyStopTimeoutTick " 277 "armed=%d active=%d\n", 278 mTimeoutTickArmed, mNumActiveConns)); 279 280 if (!mTimeoutTickArmed) return; 281 282 if (mNumActiveConns) return; 283 284 LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n")); 285 286 mTimeoutTick->Cancel(); 287 mTimeoutTickArmed = false; 288 } 289 290 //----------------------------------------------------------------------------- 291 // nsHttpConnectionMgr::nsINamed 292 //----------------------------------------------------------------------------- 293 294 NS_IMETHODIMP 295 nsHttpConnectionMgr::GetName(nsACString& aName) { 296 aName.AssignLiteral("nsHttpConnectionMgr"); 297 return NS_OK; 298 } 299 300 //----------------------------------------------------------------------------- 301 // nsHttpConnectionMgr::nsIObserver 302 //----------------------------------------------------------------------------- 303 304 NS_IMETHODIMP 305 nsHttpConnectionMgr::Observe(nsISupports* subject, const char* topic, 306 const char16_t* data) { 307 LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic)); 308 309 if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) { 310 nsCOMPtr<nsITimer> timer = do_QueryInterface(subject); 311 if (timer == mTimer) { 312 (void)PruneDeadConnections(); 313 } else if (timer == mTimeoutTick) { 314 TimeoutTick(); 315 } else if (timer == mTrafficTimer) { 316 (void)PruneNoTraffic(); 317 } else if (timer == mThrottleTicker) { 318 ThrottlerTick(); 319 } else if (timer == mDelayedResumeReadTimer) { 320 ResumeBackgroundThrottledTransactions(); 321 } else { 322 MOZ_ASSERT(false, "unexpected timer-callback"); 323 LOG(("Unexpected timer object\n")); 324 return NS_ERROR_UNEXPECTED; 325 } 326 } 327 328 return NS_OK; 329 } 330 331 //----------------------------------------------------------------------------- 332 333 nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans, 334 int32_t priority) { 335 LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority)); 336 // Make sure a transaction is not in a pending queue. 337 CheckTransInPendingQueue(trans->AsHttpTransaction()); 338 return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, 339 trans->AsHttpTransaction()); 340 } 341 342 class NewTransactionData : public ARefBase { 343 public: 344 NewTransactionData(nsHttpTransaction* trans, int32_t priority, 345 nsHttpTransaction* transWithStickyConn) 346 : mTrans(trans), 347 mPriority(priority), 348 mTransWithStickyConn(transWithStickyConn) {} 349 350 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override) 351 352 RefPtr<nsHttpTransaction> mTrans; 353 int32_t mPriority; 354 RefPtr<nsHttpTransaction> mTransWithStickyConn; 355 356 private: 357 virtual ~NewTransactionData() = default; 358 }; 359 360 nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn( 361 HttpTransactionShell* trans, int32_t priority, 362 HttpTransactionShell* transWithStickyConn) { 363 LOG( 364 ("nsHttpConnectionMgr::AddTransactionWithStickyConn " 365 "[trans=%p %d transWithStickyConn=%p]\n", 366 trans, priority, transWithStickyConn)); 367 // Make sure a transaction is not in a pending queue. 368 CheckTransInPendingQueue(trans->AsHttpTransaction()); 369 370 RefPtr<NewTransactionData> data = 371 new NewTransactionData(trans->AsHttpTransaction(), priority, 372 transWithStickyConn->AsHttpTransaction()); 373 return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0, 374 data); 375 } 376 377 nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans, 378 int32_t priority) { 379 LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, 380 priority)); 381 return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, 382 trans->AsHttpTransaction()); 383 } 384 385 void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction( 386 HttpTransactionShell* trans, const ClassOfService& classOfService) { 387 LOG( 388 ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p " 389 "classOfService flags=%" PRIu32 " inc=%d]\n", 390 trans, static_cast<uint32_t>(classOfService.Flags()), 391 classOfService.Incremental())); 392 393 (void)EnsureSocketThreadTarget(); 394 395 nsCOMPtr<nsIEventTarget> target; 396 { 397 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 398 target = mSocketThreadTarget; 399 } 400 401 if (!target) { 402 NS_WARNING("cannot post event if not initialized"); 403 return; 404 } 405 406 RefPtr<nsHttpConnectionMgr> self(this); 407 (void)target->Dispatch(NS_NewRunnableFunction( 408 "nsHttpConnectionMgr::CallUpdateClassOfServiceOnTransaction", 409 [cos{classOfService}, self{std::move(self)}, trans = RefPtr{trans}]() { 410 self->OnMsgUpdateClassOfServiceOnTransaction( 411 cos, trans->AsHttpTransaction()); 412 })); 413 } 414 415 nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans, 416 nsresult reason) { 417 LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n", 418 trans, static_cast<uint32_t>(reason))); 419 return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction, 420 static_cast<int32_t>(reason), trans->AsHttpTransaction()); 421 } 422 423 nsresult nsHttpConnectionMgr::PruneDeadConnections() { 424 return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections); 425 } 426 427 // 428 // Called after a timeout. Check for active connections that have had no 429 // traffic since they were "marked" and nuke them. 430 nsresult nsHttpConnectionMgr::PruneNoTraffic() { 431 LOG(("nsHttpConnectionMgr::PruneNoTraffic\n")); 432 mPruningNoTraffic = true; 433 return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic); 434 } 435 436 nsresult nsHttpConnectionMgr::VerifyTraffic() { 437 LOG(("nsHttpConnectionMgr::VerifyTraffic\n")); 438 return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic); 439 } 440 441 nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanup() { 442 return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0, 443 nullptr); 444 } 445 446 nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo( 447 nsHttpConnectionInfo* aCI) { 448 if (!aCI) { 449 return NS_ERROR_INVALID_ARG; 450 } 451 452 RefPtr<nsHttpConnectionInfo> ci = aCI->Clone(); 453 return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0, 454 ci); 455 } 456 457 nsresult nsHttpConnectionMgr::DoSingleConnectionCleanup( 458 nsHttpConnectionInfo* aCI) { 459 if (!aCI) { 460 return NS_ERROR_INVALID_ARG; 461 } 462 463 RefPtr<nsHttpConnectionInfo> ci = aCI->Clone(); 464 return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup, 0, ci); 465 } 466 467 class SpeculativeConnectArgs : public ARefBase { 468 public: 469 SpeculativeConnectArgs() = default; 470 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override) 471 472 public: // intentional! 473 RefPtr<SpeculativeTransaction> mTrans; 474 475 bool mFetchHTTPSRR{false}; 476 477 private: 478 virtual ~SpeculativeConnectArgs() = default; 479 NS_DECL_OWNINGTHREAD 480 }; 481 482 nsresult nsHttpConnectionMgr::SpeculativeConnect( 483 nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps, 484 SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) { 485 if (!IsNeckoChild() && NS_IsMainThread()) { 486 // HACK: make sure PSM gets initialized on the main thread. 487 net_EnsurePSMInit(); 488 } 489 490 LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n", 491 ci->HashKey().get())); 492 493 nsCOMPtr<nsISpeculativeConnectionOverrider> overrider = 494 do_GetInterface(callbacks); 495 496 bool allow1918 = overrider ? overrider->GetAllow1918() : false; 497 498 // Hosts that are Local IP Literals should not be speculatively 499 // connected - Bug 853423. 500 if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) { 501 LOG( 502 ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 " 503 "address [%s]", 504 ci->Origin())); 505 return NS_OK; 506 } 507 508 nsAutoCString url(ci->EndToEndSSL() ? "https://"_ns : "http://"_ns); 509 url += ci->GetOrigin(); 510 PROFILER_MARKER("SpeculativeConnect", NETWORK, {}, UrlMarker, url, 511 TimeDuration::Zero(), 0); 512 513 RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs(); 514 515 // Wrap up the callbacks and the target to ensure they're released on the 516 // target thread properly. 517 nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks; 518 NS_NewInterfaceRequestorAggregation(callbacks, nullptr, 519 getter_AddRefs(wrappedCallbacks)); 520 521 caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0; 522 caps |= NS_HTTP_ERROR_SOFTLY; 523 args->mTrans = aTransaction 524 ? aTransaction 525 : new SpeculativeTransaction(ci, wrappedCallbacks, caps); 526 args->mFetchHTTPSRR = aFetchHTTPSRR; 527 528 if (overrider) { 529 args->mTrans->SetParallelSpeculativeConnectLimit( 530 overrider->GetParallelSpeculativeConnectLimit()); 531 args->mTrans->SetIgnoreIdle(overrider->GetIgnoreIdle()); 532 args->mTrans->SetAllow1918(overrider->GetAllow1918()); 533 } 534 535 return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args); 536 } 537 538 nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) { 539 (void)EnsureSocketThreadTarget(); 540 541 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 542 nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget); 543 temp.forget(target); 544 return NS_OK; 545 } 546 547 nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) { 548 LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn)); 549 550 (void)EnsureSocketThreadTarget(); 551 552 nsCOMPtr<nsIEventTarget> target; 553 { 554 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 555 target = mSocketThreadTarget; 556 } 557 558 if (!target) { 559 NS_WARNING("cannot post event if not initialized"); 560 return NS_ERROR_NOT_INITIALIZED; 561 } 562 563 RefPtr<HttpConnectionBase> connRef(conn); 564 RefPtr<nsHttpConnectionMgr> self(this); 565 return target->Dispatch(NS_NewRunnableFunction( 566 "nsHttpConnectionMgr::CallReclaimConnection", 567 [conn{std::move(connRef)}, self{std::move(self)}]() { 568 self->OnMsgReclaimConnection(conn); 569 })); 570 } 571 572 // A structure used to marshall 6 pointers across the various necessary 573 // threads to complete an HTTP upgrade. 574 class nsCompleteUpgradeData : public ARefBase { 575 public: 576 nsCompleteUpgradeData(nsHttpTransaction* aTrans, 577 nsIHttpUpgradeListener* aListener, bool aJsWrapped) 578 : mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {} 579 580 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData, override) 581 582 RefPtr<nsHttpTransaction> mTrans; 583 nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener; 584 585 nsCOMPtr<nsISocketTransport> mSocketTransport; 586 nsCOMPtr<nsIAsyncInputStream> mSocketIn; 587 nsCOMPtr<nsIAsyncOutputStream> mSocketOut; 588 589 bool mJsWrapped; 590 591 private: 592 virtual ~nsCompleteUpgradeData() { 593 NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener", 594 mUpgradeListener.forget()); 595 } 596 }; 597 598 nsresult nsHttpConnectionMgr::CompleteUpgrade( 599 HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) { 600 // test if aUpgradeListener is a wrapped JsObject 601 nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener); 602 603 bool wrapped = !!wrapper; 604 605 RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData( 606 aTrans->AsHttpTransaction(), aUpgradeListener, wrapped); 607 return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data); 608 } 609 610 nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) { 611 uint32_t param = (uint32_t(name) << 16) | uint32_t(value); 612 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 613 static_cast<int32_t>(param), nullptr); 614 } 615 616 nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo* aCI) { 617 LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get())); 618 RefPtr<nsHttpConnectionInfo> ci; 619 if (aCI) { 620 ci = aCI->Clone(); 621 } 622 return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci); 623 } 624 625 nsresult nsHttpConnectionMgr::ProcessPendingQ() { 626 LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n")); 627 return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr); 628 } 629 630 void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, 631 ARefBase* param) { 632 EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param); 633 gHttpHandler->SetRequestTokenBucket(tokenBucket); 634 } 635 636 nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket( 637 EventTokenBucket* aBucket) { 638 // Call From main thread when a new EventTokenBucket has been made in order 639 // to post the new value to the socket thread. 640 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0, 641 aBucket); 642 } 643 644 nsresult nsHttpConnectionMgr::ClearConnectionHistory() { 645 return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0, 646 nullptr); 647 } 648 649 void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t, 650 ARefBase* param) { 651 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 652 653 LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory")); 654 655 for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) { 656 RefPtr<ConnectionEntry> ent = iter.Data(); 657 if (ent->IdleConnectionsLength() == 0 && ent->ActiveConnsLength() == 0 && 658 ent->DnsAndConnectSocketsLength() == 0 && 659 ent->UrgentStartQueueLength() == 0 && ent->PendingQueueLength() == 0 && 660 !ent->mDoNotDestroy) { 661 iter.Remove(); 662 } 663 } 664 } 665 666 nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection* conn) { 667 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 668 LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn)); 669 670 if (!conn->ConnectionInfo()) { 671 return NS_ERROR_UNEXPECTED; 672 } 673 674 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey()); 675 676 if (!ent || NS_FAILED(ent->CloseIdleConnection(conn))) { 677 return NS_ERROR_UNEXPECTED; 678 } 679 680 return NS_OK; 681 } 682 683 nsresult nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection* conn) { 684 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 685 686 LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn)); 687 688 if (!conn->ConnectionInfo()) { 689 return NS_ERROR_UNEXPECTED; 690 } 691 692 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey()); 693 694 if (!ent || NS_FAILED(ent->RemoveIdleConnection(conn))) { 695 return NS_ERROR_UNEXPECTED; 696 } 697 698 return NS_OK; 699 } 700 701 HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey( 702 ConnectionEntry* ent, const nsCString& key, bool justKidding, bool aNoHttp2, 703 bool aNoHttp3) { 704 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 705 MOZ_ASSERT(!aNoHttp2 || !aNoHttp3); 706 MOZ_ASSERT(ent->mConnInfo); 707 nsHttpConnectionInfo* ci = ent->mConnInfo; 708 709 nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key); 710 if (!listOfWeakConns) { 711 return nullptr; 712 } 713 714 uint32_t listLen = listOfWeakConns->Length(); 715 for (uint32_t j = 0; j < listLen;) { 716 RefPtr<HttpConnectionBase> potentialMatch = 717 do_QueryReferent(listOfWeakConns->ElementAt(j)); 718 if (!potentialMatch) { 719 // This is a connection that needs to be removed from the list 720 LOG( 721 ("FindCoalescableConnectionByHashKey() found old conn %p that has " 722 "null weak ptr - removing\n", 723 listOfWeakConns->ElementAt(j).get())); 724 if (j != listLen - 1) { 725 listOfWeakConns->Elements()[j] = 726 listOfWeakConns->Elements()[listLen - 1]; 727 } 728 listOfWeakConns->RemoveLastElement(); 729 MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1); 730 listLen--; 731 continue; // without adjusting iterator 732 } 733 734 if (aNoHttp3 && potentialMatch->UsingHttp3()) { 735 j++; 736 continue; 737 } 738 if (aNoHttp2 && potentialMatch->UsingSpdy()) { 739 j++; 740 continue; 741 } 742 bool couldJoin; 743 if (justKidding) { 744 couldJoin = 745 potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort()); 746 } else { 747 couldJoin = 748 potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort()); 749 } 750 if (couldJoin) { 751 LOG( 752 ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s " 753 "newCI=%s matchedCI=%s join ok\n", 754 potentialMatch.get(), key.get(), ci->HashKey().get(), 755 potentialMatch->ConnectionInfo()->HashKey().get())); 756 return potentialMatch.get(); 757 } 758 LOG( 759 ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s " 760 "newCI=%s matchedCI=%s join failed\n", 761 potentialMatch.get(), key.get(), ci->HashKey().get(), 762 potentialMatch->ConnectionInfo()->HashKey().get())); 763 764 ++j; // bypassed by continue when weakptr fails 765 } 766 767 if (!listLen) { // shrunk to 0 while iterating 768 LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n")); 769 mCoalescingHash.Remove(key); 770 } 771 return nullptr; 772 } 773 774 HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection( 775 ConnectionEntry* ent, bool justKidding, bool aNoHttp2, bool aNoHttp3) { 776 MOZ_ASSERT(!aNoHttp2 || !aNoHttp3); 777 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 778 MOZ_ASSERT(ent->mConnInfo); 779 nsHttpConnectionInfo* ci = ent->mConnInfo; 780 LOG(("FindCoalescableConnection %s\n", ci->HashKey().get())); 781 782 if (ci->GetWebTransport()) { 783 LOG(("Don't coalesce a WebTransport conn ")); 784 return nullptr; 785 } 786 // First try and look it up by origin frame 787 HttpConnectionBase* conn = FindCoalescableConnectionByHashKey( 788 ent, ent->OriginFrameHashKey(), justKidding, aNoHttp2, aNoHttp3); 789 if (conn) { 790 LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n", 791 ci->HashKey().get(), conn, ent->OriginFrameHashKey().get())); 792 return conn; 793 } 794 795 // now check for DNS based keys 796 // deleted conns (null weak pointers) are removed from list 797 uint32_t keyLen = ent->mCoalescingKeys.Length(); 798 for (uint32_t i = 0; i < keyLen; ++i) { 799 conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i], 800 justKidding, aNoHttp2, aNoHttp3); 801 802 auto usableEntry = [&](HttpConnectionBase* conn) { 803 // This is allowed by the spec, but other browsers don't coalesce 804 // so agressively, which surprises developers. See bug 1420777. 805 if (StaticPrefs::network_http_http2_aggressive_coalescing()) { 806 return true; 807 } 808 809 // Make sure that the connection's IP address is one that is in 810 // the set of IP addresses in the entry's DNS response. 811 NetAddr addr; 812 nsresult rv = conn->GetPeerAddr(&addr); 813 if (NS_FAILED(rv)) { 814 // Err on the side of not coalescing 815 return false; 816 } 817 // We don't care about remote port when matching entries. 818 addr.inet.port = 0; 819 return ent->mAddresses.Contains(addr); 820 }; 821 822 if (conn) { 823 LOG(("Found connection with matching hash")); 824 if (usableEntry(conn)) { 825 LOG(("> coalescing")); 826 return conn; 827 } else { 828 LOG(("> not coalescing as remote address not present in DNS records")); 829 } 830 } 831 } 832 833 LOG(("FindCoalescableConnection(%s) no matching conn\n", 834 ci->HashKey().get())); 835 return nullptr; 836 } 837 838 void nsHttpConnectionMgr::UpdateCoalescingForNewConn( 839 HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) { 840 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 841 MOZ_ASSERT(newConn); 842 MOZ_ASSERT(newConn->ConnectionInfo()); 843 MOZ_ASSERT(ent); 844 MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent); 845 LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn, aNoHttp3)); 846 if (newConn->ConnectionInfo()->GetWebTransport()) { 847 LOG(("Don't coalesce a WebTransport conn %p", newConn)); 848 // TODO: implement this properly in bug 1815735. 849 return; 850 } 851 852 HttpConnectionBase* existingConn = 853 FindCoalescableConnection(ent, true, false, false); 854 if (existingConn) { 855 // Prefer http3 connection, but allow an HTTP/2 connection if it is used for 856 // WebSocket. 857 if (newConn->UsingHttp3() && existingConn->UsingSpdy()) { 858 RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn); 859 if (connTCP && !connTCP->IsForWebSocket()) { 860 LOG( 861 ("UpdateCoalescingForNewConn() found existing active H2 conn that " 862 "could have served newConn, but new connection is H3, therefore " 863 "close the H2 conncetion")); 864 existingConn->SetCloseReason( 865 ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING); 866 existingConn->DontReuse(); 867 } 868 } else if (existingConn->UsingHttp3() && newConn->UsingSpdy()) { 869 RefPtr<nsHttpConnection> connTCP = do_QueryObject(newConn); 870 if (connTCP && !connTCP->IsForWebSocket() && !aNoHttp3) { 871 LOG( 872 ("UpdateCoalescingForNewConn() found existing active H3 conn that " 873 "could have served H2 newConn graceful close of newConn=%p to " 874 "migrate to existingConn %p\n", 875 newConn, existingConn)); 876 newConn->SetCloseReason( 877 ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING); 878 newConn->DontReuse(); 879 return; 880 } 881 } else { 882 LOG( 883 ("UpdateCoalescingForNewConn() found existing active conn that could " 884 "have served newConn " 885 "graceful close of newConn=%p to migrate to existingConn %p\n", 886 newConn, existingConn)); 887 newConn->SetCloseReason( 888 ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING); 889 newConn->DontReuse(); 890 return; 891 } 892 } 893 894 // This connection might go into the mCoalescingHash for new transactions to 895 // be coalesced onto if it can accept new transactions 896 if (!newConn->CanDirectlyActivate()) { 897 return; 898 } 899 900 uint32_t keyLen = ent->mCoalescingKeys.Length(); 901 for (uint32_t i = 0; i < keyLen; ++i) { 902 LOG(( 903 "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n", 904 newConn, newConn->ConnectionInfo()->HashKey().get(), 905 ent->mCoalescingKeys[i].get())); 906 907 mCoalescingHash 908 .LookupOrInsertWith( 909 ent->mCoalescingKeys[i], 910 [] { 911 LOG(("UpdateCoalescingForNewConn() need new list element\n")); 912 return MakeUnique<nsTArray<nsWeakPtr>>(1); 913 }) 914 ->AppendElement(do_GetWeakReference( 915 static_cast<nsISupportsWeakReference*>(newConn))); 916 } 917 918 // this is a new connection that can be coalesced onto. hooray! 919 // if there are other connection to this entry (e.g. 920 // some could still be handshaking, shutting down, etc..) then close 921 // them down after any transactions that are on them are complete. 922 // This probably happened due to the parallel connection algorithm 923 // that is used only before the host is known to speak h2. 924 ent->MakeAllDontReuseExcept(newConn); 925 } 926 927 // This function lets a connection, after completing the NPN phase, 928 // report whether or not it is using spdy through the usingSpdy 929 // argument. It would not be necessary if NPN were driven out of 930 // the connection manager. The connection entry associated with the 931 // connection is then updated to indicate whether or not we want to use 932 // spdy with that host and update the coalescing hash 933 // entries used for de-sharding hostsnames. 934 void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn, 935 bool usingSpdy, 936 bool disallowHttp3) { 937 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 938 if (!conn->ConnectionInfo()) { 939 return; 940 } 941 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey()); 942 if (!ent || !usingSpdy) { 943 return; 944 } 945 946 ent->mUsingSpdy = true; 947 mNumSpdyHttp3ActiveConns++; 948 949 // adjust timeout timer 950 uint32_t ttl = conn->TimeToLive(); 951 uint64_t timeOfExpire = NowInSeconds() + ttl; 952 if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) { 953 PruneDeadConnectionsAfter(ttl); 954 } 955 956 UpdateCoalescingForNewConn(conn, ent, disallowHttp3); 957 958 nsresult rv = ProcessPendingQ(ent->mConnInfo); 959 if (NS_FAILED(rv)) { 960 LOG( 961 ("ReportSpdyConnection conn=%p ent=%p " 962 "failed to process pending queue (%08x)\n", 963 conn, ent, static_cast<uint32_t>(rv))); 964 } 965 rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ); 966 if (NS_FAILED(rv)) { 967 LOG( 968 ("ReportSpdyConnection conn=%p ent=%p " 969 "failed to post event (%08x)\n", 970 conn, ent, static_cast<uint32_t>(rv))); 971 } 972 } 973 974 void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase* conn) { 975 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 976 if (!conn->ConnectionInfo()) { 977 return; 978 } 979 ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey()); 980 if (!ent) { 981 return; 982 } 983 984 mNumSpdyHttp3ActiveConns++; 985 986 UpdateCoalescingForNewConn(conn, ent, false); 987 nsresult rv = ProcessPendingQ(ent->mConnInfo); 988 if (NS_FAILED(rv)) { 989 LOG( 990 ("ReportHttp3Connection conn=%p ent=%p " 991 "failed to process pending queue (%08x)\n", 992 conn, ent, static_cast<uint32_t>(rv))); 993 } 994 rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ); 995 if (NS_FAILED(rv)) { 996 LOG( 997 ("ReportHttp3Connection conn=%p ent=%p " 998 "failed to post event (%08x)\n", 999 conn, ent, static_cast<uint32_t>(rv))); 1000 } 1001 } 1002 1003 //----------------------------------------------------------------------------- 1004 bool nsHttpConnectionMgr::DispatchPendingQ( 1005 nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent, 1006 bool considerAll) { 1007 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1008 1009 PendingTransactionInfo* pendingTransInfo = nullptr; 1010 nsresult rv; 1011 bool dispatchedSuccessfully = false; 1012 1013 // if !considerAll iterate the pending list until one is dispatched 1014 // successfully. Keep iterating afterwards only until a transaction fails to 1015 // dispatch. if considerAll == true then try and dispatch all items. 1016 for (uint32_t i = 0; i < pendingQ.Length();) { 1017 pendingTransInfo = pendingQ[i]; 1018 1019 bool alreadyDnsAndConnectSocketOrWaitingForTLS = 1020 pendingTransInfo->IsAlreadyClaimedInitializingConn(); 1021 1022 // The transaction could be closed after calling TryDispatchTransaction 1023 rv = TryDispatchTransaction(ent, alreadyDnsAndConnectSocketOrWaitingForTLS, 1024 pendingTransInfo); 1025 if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE) || 1026 pendingTransInfo->Transaction()->Closed()) { 1027 if (NS_SUCCEEDED(rv)) { 1028 LOG((" dispatching pending transaction...\n")); 1029 } else { 1030 LOG( 1031 (" removing pending transaction based on " 1032 "TryDispatchTransaction returning hard error %" PRIx32 "\n", 1033 static_cast<uint32_t>(rv))); 1034 if (rv == NS_ERROR_HTTP2_FALLBACK_TO_HTTP1) { 1035 pendingTransInfo->Transaction()->Close( 1036 NS_ERROR_HTTP2_FALLBACK_TO_HTTP1); 1037 } 1038 } 1039 if (pendingQ.RemoveElement(pendingTransInfo)) { 1040 // pendingTransInfo is now potentially destroyed 1041 dispatchedSuccessfully = true; 1042 continue; // dont ++i as we just made the array shorter 1043 } 1044 1045 LOG((" transaction not found in pending queue\n")); 1046 } 1047 1048 if (dispatchedSuccessfully && !considerAll) break; 1049 1050 ++i; 1051 } 1052 return dispatchedSuccessfully; 1053 } 1054 1055 uint32_t nsHttpConnectionMgr::MaxPersistConnections( 1056 ConnectionEntry* ent) const { 1057 if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) { 1058 return static_cast<uint32_t>(mMaxPersistConnsPerProxy); 1059 } 1060 1061 return static_cast<uint32_t>(mMaxPersistConnsPerHost); 1062 } 1063 1064 void nsHttpConnectionMgr::PreparePendingQForDispatching( 1065 ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, 1066 bool considerAll) { 1067 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1068 1069 pendingQ.Clear(); 1070 1071 uint32_t totalCount = ent->TotalActiveConnections(); 1072 uint32_t maxPersistConns = MaxPersistConnections(ent); 1073 uint32_t availableConnections = 1074 maxPersistConns > totalCount ? maxPersistConns - totalCount : 0; 1075 1076 // No need to try dispatching if we reach the active connection limit. 1077 if (!availableConnections) { 1078 return; 1079 } 1080 1081 // Only have to get transactions from the queue whose window id is 0. 1082 if (!StaticPrefs::network_http_active_tab_priority()) { 1083 ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections); 1084 return; 1085 } 1086 1087 uint32_t maxFocusedWindowConnections = 1088 availableConnections * gHttpHandler->FocusedWindowTransactionRatio(); 1089 MOZ_ASSERT(maxFocusedWindowConnections < availableConnections); 1090 1091 if (!maxFocusedWindowConnections) { 1092 maxFocusedWindowConnections = 1; 1093 } 1094 1095 // Only need to dispatch transactions for either focused or 1096 // non-focused window because considerAll is false. 1097 if (!considerAll) { 1098 ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ, 1099 maxFocusedWindowConnections); 1100 1101 if (pendingQ.IsEmpty()) { 1102 ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ, 1103 availableConnections); 1104 } 1105 return; 1106 } 1107 1108 uint32_t maxNonFocusedWindowConnections = 1109 availableConnections - maxFocusedWindowConnections; 1110 nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ; 1111 1112 ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ, 1113 maxFocusedWindowConnections); 1114 1115 if (maxNonFocusedWindowConnections) { 1116 ent->AppendPendingQForNonFocusedWindows( 1117 mCurrentBrowserId, remainingPendingQ, maxNonFocusedWindowConnections); 1118 } 1119 1120 // If the slots for either focused or non-focused window are not filled up 1121 // to the availability, try to use the remaining available connections 1122 // for the other slot (with preference for the focused window). 1123 if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) { 1124 ent->AppendPendingQForFocusedWindow( 1125 mCurrentBrowserId, pendingQ, 1126 maxNonFocusedWindowConnections - remainingPendingQ.Length()); 1127 } else if (pendingQ.Length() < maxFocusedWindowConnections) { 1128 ent->AppendPendingQForNonFocusedWindows( 1129 mCurrentBrowserId, remainingPendingQ, 1130 maxFocusedWindowConnections - pendingQ.Length()); 1131 } 1132 1133 MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <= 1134 availableConnections); 1135 1136 LOG( 1137 ("nsHttpConnectionMgr::PreparePendingQForDispatching " 1138 "focused window pendingQ.Length()=%zu" 1139 ", remainingPendingQ.Length()=%zu\n", 1140 pendingQ.Length(), remainingPendingQ.Length())); 1141 1142 // Append elements in |remainingPendingQ| to |pendingQ|. The order in 1143 // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans]. 1144 pendingQ.AppendElements(std::move(remainingPendingQ)); 1145 } 1146 1147 bool nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry* ent, 1148 bool considerAll) { 1149 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1150 1151 LOG( 1152 ("nsHttpConnectionMgr::ProcessPendingQForEntry " 1153 "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu" 1154 " queued=%zu]\n", 1155 ent->mConnInfo->HashKey().get(), ent, ent->ActiveConnsLength(), 1156 ent->IdleConnectionsLength(), ent->UrgentStartQueueLength(), 1157 ent->PendingQueueLength())); 1158 1159 if (LOG_ENABLED()) { 1160 ent->PrintPendingQ(); 1161 ent->LogConnections(); 1162 } 1163 1164 if (!ent->PendingQueueLength() && !ent->UrgentStartQueueLength()) { 1165 return false; 1166 } 1167 ProcessSpdyPendingQ(ent); 1168 1169 bool dispatchedSuccessfully = false; 1170 1171 if (ent->UrgentStartQueueLength()) { 1172 nsTArray<RefPtr<PendingTransactionInfo>> pendingQ; 1173 ent->AppendPendingUrgentStartQ(pendingQ); 1174 dispatchedSuccessfully = DispatchPendingQ(pendingQ, ent, considerAll); 1175 for (const auto& transactionInfo : Reversed(pendingQ)) { 1176 ent->InsertTransaction(transactionInfo); 1177 } 1178 } 1179 1180 if (dispatchedSuccessfully && !considerAll) { 1181 return dispatchedSuccessfully; 1182 } 1183 1184 nsTArray<RefPtr<PendingTransactionInfo>> pendingQ; 1185 PreparePendingQForDispatching(ent, pendingQ, considerAll); 1186 1187 // The only case that |pendingQ| is empty is when there is no 1188 // connection available for dispatching. 1189 if (pendingQ.IsEmpty()) { 1190 return dispatchedSuccessfully; 1191 } 1192 1193 dispatchedSuccessfully |= DispatchPendingQ(pendingQ, ent, considerAll); 1194 1195 // Put the leftovers into connection entry, in the same order as they 1196 // were before to keep the natural ordering. 1197 for (const auto& transactionInfo : Reversed(pendingQ)) { 1198 ent->InsertTransaction(transactionInfo, true); 1199 } 1200 1201 // Only remove empty pendingQ when considerAll is true. 1202 if (considerAll) { 1203 ent->RemoveEmptyPendingQ(); 1204 } 1205 1206 return dispatchedSuccessfully; 1207 } 1208 1209 bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) { 1210 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1211 1212 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 1213 if (ent) return ProcessPendingQForEntry(ent, false); 1214 return false; 1215 } 1216 1217 // we're at the active connection limit if any one of the following conditions 1218 // is true: 1219 // (1) at max-connections 1220 // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy 1221 // (3) keep-alive disabled and at max-connections-per-server 1222 bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry* ent, 1223 uint32_t caps) { 1224 nsHttpConnectionInfo* ci = ent->mConnInfo; 1225 uint32_t totalCount = ent->TotalActiveConnections(); 1226 1227 if (ci->IsHttp3() || ci->IsHttp3ProxyConnection()) { 1228 if (ci->GetWebTransport()) { 1229 // TODO: implement this properly in bug 1815735. 1230 return false; 1231 } 1232 return totalCount > 0; 1233 } 1234 1235 uint32_t maxPersistConns = MaxPersistConnections(ent); 1236 1237 LOG( 1238 ("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x," 1239 "totalCount=%u, maxPersistConns=%u]\n", 1240 ci->HashKey().get(), caps, totalCount, maxPersistConns)); 1241 1242 if (caps & NS_HTTP_URGENT_START) { 1243 if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) { 1244 LOG(( 1245 "The number of total connections are greater than or equal to sum of " 1246 "max urgent-start queue length and the number of max persistent " 1247 "connections.\n")); 1248 return true; 1249 } 1250 return false; 1251 } 1252 1253 // update maxconns if potentially limited by the max socket count 1254 // this requires a dynamic reduction in the max socket count to a point 1255 // lower than the max-connections pref. 1256 uint32_t maxSocketCount = gHttpHandler->MaxSocketCount(); 1257 if (mMaxConns > maxSocketCount) { 1258 mMaxConns = maxSocketCount; 1259 LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this, 1260 mMaxConns)); 1261 } 1262 1263 // If there are more active connections than the global limit, then we're 1264 // done. Purging idle connections won't get us below it. 1265 if (mNumActiveConns >= mMaxConns) { 1266 LOG((" num active conns == max conns\n")); 1267 return true; 1268 } 1269 1270 bool result = (totalCount >= maxPersistConns); 1271 LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false")); 1272 return result; 1273 } 1274 1275 // returns NS_OK if a connection was started 1276 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to 1277 // ephemeral limits 1278 // returns other NS_ERROR on hard failure conditions 1279 nsresult nsHttpConnectionMgr::MakeNewConnection( 1280 ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) { 1281 LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent, 1282 pendingTransInfo->Transaction())); 1283 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1284 1285 if (ent->FindConnToClaim(pendingTransInfo)) { 1286 return NS_OK; 1287 } 1288 1289 nsHttpTransaction* trans = pendingTransInfo->Transaction(); 1290 1291 // If this host is trying to negotiate a SPDY session right now, 1292 // don't create any new connections until the result of the 1293 // negotiation is known. 1294 if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && 1295 (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && ent->RestrictConnections()) { 1296 LOG( 1297 ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] " 1298 "Not Available Due to RestrictConnections()\n", 1299 ent->mConnInfo->HashKey().get())); 1300 return NS_ERROR_NOT_AVAILABLE; 1301 } 1302 1303 // We need to make a new connection. If that is going to exceed the 1304 // global connection limit then try and free up some room by closing 1305 // an idle connection to another host. We know it won't select "ent" 1306 // because we have already determined there are no idle connections 1307 // to our destination 1308 1309 if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) { 1310 // If the global number of connections is preventing the opening of new 1311 // connections to a host without idle connections, then close them 1312 // regardless of their TTL. 1313 auto iter = mCT.ConstIter(); 1314 while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) { 1315 RefPtr<ConnectionEntry> entry = iter.Data(); 1316 entry->CloseIdleConnections((mNumIdleConns + mNumActiveConns + 1) - 1317 mMaxConns); 1318 iter.Next(); 1319 } 1320 } 1321 1322 if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns && 1323 StaticPrefs::network_http_http2_enabled()) { 1324 // If the global number of connections is preventing the opening of new 1325 // connections to a host without idle connections, then close any spdy 1326 // ASAP. 1327 for (const RefPtr<ConnectionEntry>& entry : mCT.Values()) { 1328 while (entry->MakeFirstActiveSpdyConnDontReuse()) { 1329 // Stop on <= (particularly =) because this dontreuse 1330 // causes async close. 1331 if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) { 1332 goto outerLoopEnd; 1333 } 1334 } 1335 } 1336 outerLoopEnd:; 1337 } 1338 1339 if (AtActiveConnectionLimit(ent, trans->Caps())) { 1340 return NS_ERROR_NOT_AVAILABLE; 1341 } 1342 1343 nsresult rv = ent->CreateDnsAndConnectSocket( 1344 trans, trans->Caps(), false, 1345 trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart, true, 1346 pendingTransInfo); 1347 if (NS_FAILED(rv)) { 1348 /* hard failure */ 1349 LOG( 1350 ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] " 1351 "CreateDnsAndConnectSocket() hard failure.\n", 1352 ent->mConnInfo->HashKey().get(), trans)); 1353 trans->Close(rv); 1354 if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE; 1355 return rv; 1356 } 1357 1358 return NS_OK; 1359 } 1360 1361 // returns OK if a connection is found for the transaction 1362 // and the transaction is started. 1363 // returns ERROR_NOT_AVAILABLE if no connection can be found and it 1364 // should be queued until circumstances change 1365 // returns other ERROR when transaction has a hard failure and should 1366 // not remain in the pending queue 1367 nsresult nsHttpConnectionMgr::TryDispatchTransaction( 1368 ConnectionEntry* ent, bool onlyReusedConnection, 1369 PendingTransactionInfo* pendingTransInfo) { 1370 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1371 1372 nsHttpTransaction* trans = pendingTransInfo->Transaction(); 1373 1374 LOG( 1375 ("nsHttpConnectionMgr::TryDispatchTransaction without conn " 1376 "[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu " 1377 "idle=%zu]\n", 1378 trans, ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(), 1379 uint32_t(trans->Caps()), onlyReusedConnection, ent->ActiveConnsLength(), 1380 ent->IdleConnectionsLength())); 1381 1382 uint32_t caps = trans->Caps(); 1383 1384 // 0 - If this should use spdy then dispatch it post haste. 1385 // 1 - If there is connection pressure then see if we can pipeline this on 1386 // a connection of a matching type instead of using a new conn 1387 // 2 - If there is an idle connection, use it! 1388 // 3 - if class == reval or script and there is an open conn of that type 1389 // then pipeline onto shortest pipeline of that class if limits allow 1390 // 4 - If we aren't up against our connection limit, 1391 // then open a new one 1392 // 5 - Try a pipeline if we haven't already - this will be unusual because 1393 // it implies a low connection pressure situation where 1394 // MakeNewConnection() failed.. that is possible, but unlikely, due to 1395 // global limits 1396 // 6 - no connection is available - queue it 1397 1398 RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection; 1399 1400 // step 0 1401 // look for existing spdy connection - that's always best because it is 1402 // essentially pipelining without head of line blocking 1403 1404 // For WebSocket/WebTransport through H3 proxy, we need to create a TCP 1405 // tunnel through the H3 proxy first. But if there's already an H2 session 1406 // available (from a previously established tunnel), we should use that 1407 // instead of creating a new tunnel. 1408 // The WebSocket transaction doesn't have a connection set (it was queued 1409 // without one in DnsAndConnectSocket::SetupConn to avoid triggering reclaim 1410 // when we clear it here). 1411 if ((trans->IsWebsocketUpgrade() || trans->IsForWebTransport()) && 1412 ent->IsHttp3ProxyConnection()) { 1413 // First check if there's an H2 session available (from existing tunnel) 1414 // This handles the case where the tunnel was already established and the 1415 // WebSocket transaction was reset to wait for H2 negotiation. 1416 // We can't use GetH2orH3ActiveConn because it skips H3 proxy entries when 1417 // looking for H2 connections. We use GetH2TunnelActiveConn to directly 1418 // look for an H2 tunnel connection in the active connections. 1419 RefPtr<nsHttpConnection> h2Tunnel = ent->GetH2TunnelActiveConn(); 1420 if (h2Tunnel) { 1421 LOG( 1422 ("TryDispatchTransaction: WebSocket through H3 proxy - using " 1423 "existing H2 tunnel")); 1424 return TryDispatchExtendedCONNECTransaction(ent, trans, h2Tunnel); 1425 } 1426 1427 // No H2 session available yet - create a tunnel through the H3 proxy 1428 RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(ent, true, false); 1429 RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn); 1430 if (connUDP) { 1431 LOG(("TryDispatchTransaction: WebSocket through HTTP/3 proxy")); 1432 RefPtr<HttpConnectionBase> tunnelConn; 1433 nsresult rv = 1434 connUDP->CreateTunnelStream(trans, getter_AddRefs(tunnelConn), true); 1435 if (NS_FAILED(rv)) { 1436 return rv; 1437 } 1438 ent->InsertIntoActiveConns(tunnelConn); 1439 tunnelConn->SetInTunnel(); 1440 if (trans->IsWebsocketUpgrade()) { 1441 trans->SetIsHttp2Websocket(true); 1442 } 1443 return DispatchTransaction(ent, trans, tunnelConn); 1444 } 1445 } 1446 1447 RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn( 1448 ent, 1449 (!StaticPrefs::network_http_http2_enabled() || 1450 (caps & NS_HTTP_DISALLOW_SPDY)), 1451 (!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3))); 1452 if (conn) { 1453 LOG(("TryingDispatchTransaction: an active h2 connection exists")); 1454 if (trans->IsWebsocketUpgrade() || trans->IsForWebTransport()) { 1455 RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); 1456 if (connTCP) { 1457 return TryDispatchExtendedCONNECTransaction(ent, trans, connTCP); 1458 } 1459 } else { 1460 if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || 1461 (caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) || 1462 !conn->IsExperienced()) { 1463 LOG((" dispatch to spdy: [conn=%p]\n", conn.get())); 1464 trans->RemoveDispatchedAsBlocking(); /* just in case */ 1465 nsresult rv = DispatchTransaction(ent, trans, conn); 1466 NS_ENSURE_SUCCESS(rv, rv); 1467 return NS_OK; 1468 } 1469 unusedSpdyPersistentConnection = conn; 1470 } 1471 } 1472 1473 // If this is not a blocking transaction and the request context for it is 1474 // currently processing one or more blocking transactions then we 1475 // need to just leave it in the queue until those are complete unless it is 1476 // explicitly marked as unblocked. 1477 if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) { 1478 if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) { 1479 nsIRequestContext* requestContext = trans->RequestContext(); 1480 if (requestContext) { 1481 uint32_t blockers = 0; 1482 if (NS_SUCCEEDED( 1483 requestContext->GetBlockingTransactionCount(&blockers)) && 1484 blockers) { 1485 // need to wait for blockers to clear 1486 LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n", 1487 requestContext, trans, blockers)); 1488 return NS_ERROR_NOT_AVAILABLE; 1489 } 1490 } 1491 } 1492 } else { 1493 // Mark the transaction and its load group as blocking right now to prevent 1494 // other transactions from being reordered in the queue due to slow syns. 1495 trans->DispatchedAsBlocking(); 1496 } 1497 1498 // step 1 1499 // If connection pressure, then we want to favor pipelining of any kind 1500 // h1 pipelining has been removed 1501 1502 // Subject most transactions at high parallelism to rate pacing. 1503 // It will only be actually submitted to the 1504 // token bucket once, and if possible it is granted admission synchronously. 1505 // It is important to leave a transaction in the pending queue when blocked by 1506 // pacing so it can be found on cancel if necessary. 1507 // Transactions that cause blocking or bypass it (e.g. js/css) are not rate 1508 // limited. 1509 if (gHttpHandler->UseRequestTokenBucket()) { 1510 // submit even whitelisted transactions to the token bucket though they will 1511 // not be slowed by it 1512 bool runNow = trans->TryToRunPacedRequest(); 1513 if (!runNow) { 1514 if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <= 1515 gHttpHandler->RequestTokenBucketMinParallelism()) { 1516 runNow = true; // white list it 1517 } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) { 1518 runNow = true; // white list it 1519 } 1520 } 1521 if (!runNow) { 1522 LOG((" blocked due to rate pacing trans=%p\n", trans)); 1523 return NS_ERROR_NOT_AVAILABLE; 1524 } 1525 } 1526 1527 // step 2 1528 // consider an idle persistent connection 1529 bool idleConnsAllUrgent = false; 1530 if (caps & NS_HTTP_ALLOW_KEEPALIVE) { 1531 nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, true, 1532 &idleConnsAllUrgent); 1533 if (NS_SUCCEEDED(rv)) { 1534 LOG((" dispatched step 2 (idle) trans=%p\n", trans)); 1535 return NS_OK; 1536 } 1537 1538 if (rv == NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED) { 1539 LOG((" Local network access failure in step 2 (idle) trans=%p ", 1540 trans)); 1541 return NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED; 1542 } 1543 } 1544 1545 // step 3 1546 // consider pipelining scripts and revalidations 1547 // h1 pipelining has been removed 1548 1549 // Don't dispatch if this transaction is waiting for HTTPS RR. 1550 // This usually happens when the pref "network.dns.force_waiting_https_rr" is 1551 // true or when echConfig is enabled. 1552 if (trans->WaitingForHTTPSRR()) { 1553 return NS_ERROR_NOT_AVAILABLE; 1554 } 1555 1556 // step 4 1557 if (!onlyReusedConnection) { 1558 nsresult rv = MakeNewConnection(ent, pendingTransInfo); 1559 if (NS_SUCCEEDED(rv)) { 1560 // this function returns NOT_AVAILABLE for asynchronous connects 1561 LOG((" dispatched step 4 (async new conn) trans=%p\n", trans)); 1562 return NS_ERROR_NOT_AVAILABLE; 1563 } 1564 1565 if (rv != NS_ERROR_NOT_AVAILABLE) { 1566 // not available return codes should try next step as they are 1567 // not hard errors. Other codes should stop now 1568 LOG((" failed step 4 (%" PRIx32 ") trans=%p\n", 1569 static_cast<uint32_t>(rv), trans)); 1570 return rv; 1571 } 1572 1573 // repeat step 2 when there are only idle connections and all are urgent, 1574 // don't respect urgency so that non-urgent transaction will be allowed 1575 // to dispatch on an urgent-start-only marked connection to avoid 1576 // dispatch deadlocks 1577 if (!(trans->GetClassOfService().Flags() & 1578 nsIClassOfService::UrgentStart) && 1579 idleConnsAllUrgent && 1580 ent->ActiveConnsLength() < MaxPersistConnections(ent)) { 1581 rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false); 1582 if (NS_SUCCEEDED(rv)) { 1583 LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans)); 1584 return NS_OK; 1585 } 1586 } 1587 } 1588 1589 // step 5 1590 // previously pipelined anything here if allowed but h1 pipelining has been 1591 // removed 1592 1593 // step 6 1594 if (unusedSpdyPersistentConnection) { 1595 // to avoid deadlocks, we need to throw away this perfectly valid SPDY 1596 // connection to make room for a new one that can service a no KEEPALIVE 1597 // request 1598 unusedSpdyPersistentConnection->DontReuse(); 1599 } 1600 1601 LOG((" not dispatched (queued) trans=%p\n", trans)); 1602 return NS_ERROR_NOT_AVAILABLE; /* queue it */ 1603 } 1604 1605 nsresult nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn( 1606 ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo, 1607 bool respectUrgency, bool* allUrgent) { 1608 bool onlyUrgent = !!ent->IdleConnectionsLength(); 1609 1610 nsHttpTransaction* trans = pendingTransInfo->Transaction(); 1611 bool urgentTrans = 1612 trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart; 1613 1614 LOG( 1615 ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, " 1616 "trans=%p, urgent=%d", 1617 ent, trans, urgentTrans)); 1618 1619 RefPtr<nsHttpConnection> conn = 1620 ent->GetIdleConnection(respectUrgency, urgentTrans, &onlyUrgent); 1621 1622 if (allUrgent) { 1623 *allUrgent = onlyUrgent; 1624 } 1625 1626 if (conn) { 1627 // This will update the class of the connection to be the class of 1628 // the transaction dispatched on it. 1629 ent->InsertIntoActiveConns(conn); 1630 nsresult rv = DispatchTransaction(ent, trans, conn); 1631 NS_ENSURE_SUCCESS(rv, rv); 1632 1633 return NS_OK; 1634 } 1635 1636 return NS_ERROR_NOT_AVAILABLE; 1637 } 1638 1639 nsresult nsHttpConnectionMgr::TryDispatchExtendedCONNECTransaction( 1640 ConnectionEntry* aEnt, nsHttpTransaction* aTrans, nsHttpConnection* aConn) { 1641 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1642 MOZ_ASSERT(aTrans->IsWebsocketUpgrade() || aTrans->IsForWebTransport()); 1643 MOZ_ASSERT(aConn); 1644 1645 ExtendedCONNECTSupport extendedConnect = aConn->GetExtendedCONNECTSupport(); 1646 LOG(("TryingDispatchTransaction: extended CONNECT")); 1647 if (extendedConnect == ExtendedCONNECTSupport::NO_SUPPORT) { 1648 LOG( 1649 ("TryingDispatchTransaction: no support for extended CONNECT over " 1650 "HTTP/2")); 1651 // This is a transaction wants to do extended CONNECT and we already 1652 // have a h2 connection that do not support it, we should disable h2 1653 // for this transaction. 1654 aTrans->DisableSpdy(); 1655 1656 // WebTransport doesn’t allow to fall back to HTTP/1.1. 1657 if (aTrans->IsForWebTransport()) { 1658 return NS_ERROR_CONNECTION_REFUSED; 1659 } 1660 1661 // See the assertion in nsHttpConnectionMgr::OnMsgCompleteUpgrade. 1662 // Transactions used for WebSockets are expected to have 1663 // NS_HTTP_STICKY_CONNECTION, so we also need to call MakeRestartable to 1664 // allow them to restart successfully. 1665 aTrans->MakeSticky(); 1666 aTrans->MakeRestartable(); 1667 1668 return NS_ERROR_HTTP2_FALLBACK_TO_HTTP1; 1669 } else if (extendedConnect == ExtendedCONNECTSupport::SUPPORTED) { 1670 LOG(("TryingDispatchTransaction: extended CONNECT supported")); 1671 1672 // No limit for number of websockets, dispatch transaction to the 1673 // tunnel 1674 RefPtr<HttpConnectionBase> connToTunnel; 1675 nsresult rv = 1676 aConn->CreateTunnelStream(aTrans, getter_AddRefs(connToTunnel), true); 1677 if (rv == NS_ERROR_WEBTRANSPORT_SESSION_LIMIT_EXCEEDED) { 1678 LOG( 1679 ("TryingDispatchTransaction: WebTransport session limit " 1680 "exceeded")); 1681 return rv; 1682 } 1683 aEnt->InsertIntoExtendedCONNECTConns(connToTunnel); 1684 aTrans->SetConnection(nullptr); 1685 connToTunnel->SetInTunnel(); // tells conn it is already in tunnel 1686 if (aTrans->IsWebsocketUpgrade()) { 1687 aTrans->SetIsHttp2Websocket(true); 1688 } 1689 rv = DispatchTransaction(aEnt, aTrans, connToTunnel); 1690 // need to undo NonSticky bypass for transaction reset to continue 1691 // for correct websocket upgrade handling 1692 aTrans->MakeSticky(); 1693 aTrans->SetResettingForTunnelConn(false); 1694 return rv; 1695 } 1696 1697 // if we aren't sure that extended CONNECT is supported yet or we are 1698 // already at the connection limit then we queue the transaction 1699 LOG( 1700 ("TryingDispatchTransaction: unsure if extended CONNECT " 1701 "supported")); 1702 return NS_ERROR_NOT_AVAILABLE; 1703 } 1704 1705 nsresult nsHttpConnectionMgr::DispatchTransaction(ConnectionEntry* ent, 1706 nsHttpTransaction* trans, 1707 HttpConnectionBase* conn) { 1708 uint32_t caps = trans->Caps(); 1709 int32_t priority = trans->Priority(); 1710 nsresult rv; 1711 1712 LOG( 1713 ("nsHttpConnectionMgr::DispatchTransaction " 1714 "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d " 1715 "isHttp3=%d]\n", 1716 ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority, 1717 conn->UsingSpdy(), conn->UsingHttp3())); 1718 1719 // It is possible for a rate-paced transaction to be dispatched independent 1720 // of the token bucket when the amount of parallelization has changed or 1721 // when a muxed connection (e.g. h2) becomes available. 1722 trans->CancelPacing(NS_OK); 1723 1724 TimeStamp now = TimeStamp::Now(); 1725 TimeDuration elapsed = now - trans->GetPendingTime(); 1726 auto recordPendingTimeForHTTPSRR = [&](nsCString& aKey) { 1727 uint32_t stage = trans->HTTPSSVCReceivedStage(); 1728 if (HTTPS_RR_IS_USED(stage)) { 1729 glean::networking::transaction_wait_time_https_rr.AccumulateRawDuration( 1730 elapsed); 1731 1732 } else { 1733 glean::networking::transaction_wait_time.AccumulateRawDuration(elapsed); 1734 } 1735 }; 1736 1737 PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime, 1738 elapsed); 1739 1740 PROFILER_MARKER( 1741 "DispatchTransaction", NETWORK, 1742 MarkerOptions(MarkerThreadId::MainThread(), 1743 MarkerTiming::Interval(trans->GetPendingTime(), now)), 1744 UrlMarker, trans->GetUrl(), elapsed, trans->ChannelId()); 1745 1746 nsAutoCString httpVersionkey("h1"_ns); 1747 if (conn->UsingSpdy() || conn->UsingHttp3()) { 1748 LOG( 1749 ("Spdy Dispatch Transaction via Activate(). Transaction host = %s, " 1750 "Connection host = %s\n", 1751 trans->ConnectionInfo()->Origin(), conn->ConnectionInfo()->Origin())); 1752 rv = conn->Activate(trans, caps, priority); 1753 if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) { 1754 if (conn->UsingSpdy()) { 1755 httpVersionkey = "h2"_ns; 1756 glean::http::transaction_wait_time_spdy.AccumulateRawDuration( 1757 now - trans->GetPendingTime()); 1758 } else { 1759 httpVersionkey = "h3"_ns; 1760 glean::http::transaction_wait_time_http3.AccumulateRawDuration( 1761 now - trans->GetPendingTime()); 1762 } 1763 recordPendingTimeForHTTPSRR(httpVersionkey); 1764 trans->SetPendingTime(false); 1765 } 1766 return rv; 1767 } 1768 1769 MOZ_ASSERT(conn && !conn->Transaction(), 1770 "DispatchTranaction() on non spdy active connection"); 1771 1772 rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority); 1773 1774 if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) { 1775 glean::http::transaction_wait_time_http.AccumulateRawDuration( 1776 now - trans->GetPendingTime()); 1777 recordPendingTimeForHTTPSRR(httpVersionkey); 1778 trans->SetPendingTime(false); 1779 } 1780 return rv; 1781 } 1782 1783 // Use this method for dispatching nsAHttpTransction's. It can only safely be 1784 // used upon first use of a connection when NPN has not negotiated SPDY vs 1785 // HTTP/1 yet as multiplexing onto an existing SPDY session requires a 1786 // concrete nsHttpTransaction 1787 nsresult nsHttpConnectionMgr::DispatchAbstractTransaction( 1788 ConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps, 1789 HttpConnectionBase* conn, int32_t priority) { 1790 MOZ_ASSERT(ent); 1791 1792 nsresult rv; 1793 MOZ_ASSERT(!conn->UsingSpdy(), 1794 "Spdy Must Not Use DispatchAbstractTransaction"); 1795 LOG( 1796 ("nsHttpConnectionMgr::DispatchAbstractTransaction " 1797 "[ci=%s trans=%p caps=%x conn=%p]\n", 1798 ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); 1799 1800 RefPtr<nsAHttpTransaction> transaction(aTrans); 1801 RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn); 1802 1803 // give the transaction the indirect reference to the connection. 1804 transaction->SetConnection(handle); 1805 1806 rv = conn->Activate(transaction, caps, priority); 1807 if (NS_FAILED(rv)) { 1808 LOG((" conn->Activate failed [rv=%" PRIx32 "]\n", 1809 static_cast<uint32_t>(rv))); 1810 DebugOnly<nsresult> rv_remove = ent->RemoveActiveConnection(conn); 1811 MOZ_ASSERT(NS_SUCCEEDED(rv_remove)); 1812 1813 // sever back references to connection, and do so without triggering 1814 // a call to ReclaimConnection ;-) 1815 transaction->SetConnection(nullptr); 1816 handle->Reset(); // destroy the connection 1817 } 1818 1819 return rv; 1820 } 1821 1822 nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) { 1823 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1824 1825 // since "adds" and "cancels" are processed asynchronously and because 1826 // various events might trigger an "add" directly on the socket thread, 1827 // we must take care to avoid dispatching a transaction that has already 1828 // been canceled (see bug 190001). 1829 if (NS_FAILED(trans->Status())) { 1830 LOG((" transaction was canceled... dropping event!\n")); 1831 return NS_OK; 1832 } 1833 1834 // Make sure a transaction is not in a pending queue. 1835 CheckTransInPendingQueue(trans); 1836 1837 trans->SetPendingTime(); 1838 1839 PROFILER_MARKER("ProcessNewTransaction", NETWORK, 1840 MarkerThreadId::MainThread(), UrlMarker, trans->GetUrl(), 1841 TimeDuration::Zero(), trans->ChannelId()); 1842 1843 nsresult rv = NS_OK; 1844 nsHttpConnectionInfo* ci = trans->ConnectionInfo(); 1845 MOZ_ASSERT(ci); 1846 MOZ_ASSERT(!ci->IsHttp3() || !(trans->Caps() & NS_HTTP_DISALLOW_HTTP3)); 1847 1848 bool isWildcard = false; 1849 ConnectionEntry* ent = GetOrCreateConnectionEntry( 1850 ci, trans->Caps() & NS_HTTP_DISALLOW_HTTP2_PROXY, 1851 trans->Caps() & NS_HTTP_DISALLOW_SPDY, 1852 trans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard); 1853 MOZ_ASSERT(ent); 1854 1855 if (nsHttpHandler::EchConfigEnabled(ci->IsHttp3())) { 1856 ent->MaybeUpdateEchConfig(ci); 1857 } 1858 1859 // Check if the transaction already has a sticky reference to a connection. 1860 // If so, then we can just use it directly by transferring its reference 1861 // to the new connection variable instead of searching for a new one 1862 1863 nsAHttpConnection* wrappedConnection = trans->Connection(); 1864 RefPtr<HttpConnectionBase> conn; 1865 RefPtr<PendingTransactionInfo> pendingTransInfo; 1866 if (wrappedConnection) conn = wrappedConnection->TakeHttpConnection(); 1867 1868 if (conn) { 1869 MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION); 1870 LOG( 1871 ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " 1872 "sticky connection=%p\n", 1873 trans, conn.get())); 1874 1875 if (!ent->IsInActiveConns(conn)) { 1876 LOG( 1877 ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " 1878 "sticky connection=%p needs to go on the active list\n", 1879 trans, conn.get())); 1880 1881 // make sure it isn't on the idle list - we expect this to be an 1882 // unknown fresh connection 1883 MOZ_ASSERT(!ent->IsInIdleConnections(conn)); 1884 MOZ_ASSERT(!conn->IsExperienced()); 1885 1886 ent->InsertIntoActiveConns(conn); // make it active 1887 } 1888 1889 trans->SetConnection(nullptr); 1890 rv = DispatchTransaction(ent, trans, conn); 1891 } else if (isWildcard) { 1892 // Determine which connections we want to use. 1893 // - If this is an HTTP/3 proxy connection (`isHttp3Proxy == true`), we only 1894 // want to consider existing HTTP/3 connections, so we set aNoHttp2 = true 1895 // and aNoHttp3 = false. 1896 // - Otherwise (`isHttp3Proxy == false`), we are looking for a regular 1897 // HTTP/2 connection, so we set aNoHttp2 = false and aNoHttp3 = true. 1898 bool isHttp3Proxy = ci->IsHttp3ProxyConnection(); 1899 RefPtr<HttpConnectionBase> conn = 1900 GetH2orH3ActiveConn(ent, isHttp3Proxy, !isHttp3Proxy); 1901 if (ci->UsingHttpsProxy() && ci->UsingConnect()) { 1902 LOG(("About to create new tunnel conn from [%p]", conn.get())); 1903 ConnectionEntry* specificEnt = mCT.GetWeak(ci->HashKey()); 1904 1905 if (!specificEnt) { 1906 RefPtr<nsHttpConnectionInfo> clone(ci->Clone()); 1907 specificEnt = new ConnectionEntry(clone); 1908 mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt}); 1909 } 1910 1911 ent = specificEnt; 1912 bool atLimit = AtActiveConnectionLimit(ent, trans->Caps()); 1913 if (atLimit) { 1914 rv = NS_ERROR_NOT_AVAILABLE; 1915 } else { 1916 RefPtr<HttpConnectionBase> newTunnel; 1917 conn->CreateTunnelStream(trans, getter_AddRefs(newTunnel)); 1918 1919 ent->InsertIntoActiveConns(newTunnel); 1920 trans->SetConnection(nullptr); 1921 newTunnel->SetInTunnel(); 1922 rv = DispatchTransaction(ent, trans, newTunnel); 1923 // need to undo the bypass for transaction reset for proxy 1924 trans->MakeNonRestartable(); 1925 } 1926 } else { 1927 rv = DispatchTransaction(ent, trans, conn); 1928 } 1929 } else { 1930 if (!ent->AllowHttp2()) { 1931 trans->DisableSpdy(); 1932 } 1933 pendingTransInfo = new PendingTransactionInfo(trans); 1934 rv = TryDispatchTransaction(ent, false, pendingTransInfo); 1935 } 1936 1937 if (NS_SUCCEEDED(rv)) { 1938 LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans)); 1939 return rv; 1940 } 1941 1942 if (rv == NS_ERROR_NOT_AVAILABLE) { 1943 if (!pendingTransInfo) { 1944 pendingTransInfo = new PendingTransactionInfo(trans); 1945 } 1946 1947 ent->InsertTransaction(pendingTransInfo); 1948 return NS_OK; 1949 } 1950 1951 LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans, 1952 static_cast<uint32_t>(rv))); 1953 return rv; 1954 } 1955 1956 void nsHttpConnectionMgr::IncrementActiveConnCount() { 1957 mNumActiveConns++; 1958 ActivateTimeoutTick(); 1959 } 1960 1961 void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) { 1962 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0); 1963 if (mNumActiveConns > 0) { 1964 mNumActiveConns--; 1965 } 1966 1967 RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); 1968 if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--; 1969 ConditionallyStopTimeoutTick(); 1970 } 1971 1972 void nsHttpConnectionMgr::StartedConnect() { 1973 mNumActiveConns++; 1974 ActivateTimeoutTick(); // likely disabled by RecvdConnect() 1975 } 1976 1977 void nsHttpConnectionMgr::RecvdConnect() { 1978 MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0); 1979 if (mNumActiveConns > 0) { 1980 mNumActiveConns--; 1981 } 1982 1983 ConditionallyStopTimeoutTick(); 1984 } 1985 1986 void nsHttpConnectionMgr::DispatchSpdyPendingQ( 1987 nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent, 1988 HttpConnectionBase* connH2, HttpConnectionBase* connH3) { 1989 if (pendingQ.Length() == 0) { 1990 return; 1991 } 1992 1993 nsTArray<RefPtr<PendingTransactionInfo>> leftovers; 1994 uint32_t index; 1995 // Dispatch all the transactions we can 1996 for (index = 0; index < pendingQ.Length() && 1997 ((connH3 && connH3->CanDirectlyActivate()) || 1998 (connH2 && connH2->CanDirectlyActivate())); 1999 ++index) { 2000 PendingTransactionInfo* pendingTransInfo = pendingQ[index]; 2001 2002 // We can not dispatch NS_HTTP_ALLOW_KEEPALIVE transactions. 2003 // For WebTransport case, we can't dispatch the transaction here. We need to 2004 // put the transaction back, so TryDispatchTransaction can be called later. 2005 // TODO: this is hacky and should be improved. 2006 if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_ALLOW_KEEPALIVE) || 2007 pendingTransInfo->Transaction()->IsResettingForTunnelConn()) { 2008 leftovers.AppendElement(pendingTransInfo); 2009 continue; 2010 } 2011 2012 // Try dispatching on HTTP3 first 2013 HttpConnectionBase* conn = nullptr; 2014 if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3) && 2015 connH3 && connH3->CanDirectlyActivate()) { 2016 conn = connH3; 2017 } else if (!(pendingTransInfo->Transaction()->Caps() & 2018 NS_HTTP_DISALLOW_SPDY) && 2019 connH2 && connH2->CanDirectlyActivate()) { 2020 conn = connH2; 2021 } else { 2022 leftovers.AppendElement(pendingTransInfo); 2023 continue; 2024 } 2025 2026 nsresult rv = 2027 DispatchTransaction(ent, pendingTransInfo->Transaction(), conn); 2028 if (NS_FAILED(rv)) { 2029 // Dispatching a transaction to an existing HTTP/2 session should not 2030 // fail. The only expected failure here is 2031 // NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED (e.g., when a 2032 // speculative/preconnected HTTP/2 session was created before). Any other 2033 // rv indicates a bug. 2034 MOZ_ASSERT(rv == NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 2035 "Dispatch H2 transaction should only fail with Local Network " 2036 "Access denied"); 2037 LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n", 2038 pendingTransInfo->Transaction())); 2039 pendingTransInfo->Transaction()->Close(rv); 2040 } 2041 } 2042 2043 // Slurp up the rest of the pending queue into our leftovers bucket (we 2044 // might have some left if conn->CanDirectlyActivate returned false) 2045 for (; index < pendingQ.Length(); ++index) { 2046 PendingTransactionInfo* pendingTransInfo = pendingQ[index]; 2047 leftovers.AppendElement(pendingTransInfo); 2048 } 2049 2050 // Put the leftovers back in the pending queue and get rid of the 2051 // transactions we dispatched 2052 pendingQ = std::move(leftovers); 2053 } 2054 2055 // This function tries to dispatch the pending h2 or h3 transactions on 2056 // the connection entry sent in as an argument. It will do so on the 2057 // active h2 or h3 connection either in that same entry or from the 2058 // coalescing hash table 2059 void nsHttpConnectionMgr::ProcessSpdyPendingQ(ConnectionEntry* ent) { 2060 // Look for one HTTP2 and one HTTP3 connection. 2061 // We may have transactions that have NS_HTTP_DISALLOW_SPDY/HTTP3 set 2062 // and we may need an alternative. 2063 HttpConnectionBase* connH3 = GetH2orH3ActiveConn(ent, true, false); 2064 HttpConnectionBase* connH2 = GetH2orH3ActiveConn(ent, false, true); 2065 if ((!connH3 || !connH3->CanDirectlyActivate()) && 2066 (!connH2 || !connH2->CanDirectlyActivate())) { 2067 return; 2068 } 2069 2070 nsTArray<RefPtr<PendingTransactionInfo>> urgentQ; 2071 ent->AppendPendingUrgentStartQ(urgentQ); 2072 DispatchSpdyPendingQ(urgentQ, ent, connH2, connH3); 2073 for (const auto& transactionInfo : Reversed(urgentQ)) { 2074 ent->InsertTransaction(transactionInfo); 2075 } 2076 2077 if ((!connH3 || !connH3->CanDirectlyActivate()) && 2078 (!connH2 || !connH2->CanDirectlyActivate())) { 2079 return; 2080 } 2081 2082 nsTArray<RefPtr<PendingTransactionInfo>> pendingQ; 2083 // XXX Get all transactions for SPDY currently. 2084 ent->AppendPendingQForNonFocusedWindows(0, pendingQ); 2085 DispatchSpdyPendingQ(pendingQ, ent, connH2, connH3); 2086 2087 // Put the leftovers back in the pending queue. 2088 for (const auto& transactionInfo : pendingQ) { 2089 ent->InsertTransaction(transactionInfo); 2090 } 2091 } 2092 2093 void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*) { 2094 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2095 LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n")); 2096 for (const auto& entry : mCT.Values()) { 2097 ProcessSpdyPendingQ(entry.get()); 2098 } 2099 } 2100 2101 // Given a connection entry, return an active h2 or h3 connection 2102 // that can be directly activated or null. 2103 HttpConnectionBase* nsHttpConnectionMgr::GetH2orH3ActiveConn( 2104 ConnectionEntry* ent, bool aNoHttp2, bool aNoHttp3) { 2105 if (aNoHttp2 && aNoHttp3) { 2106 return nullptr; 2107 } 2108 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2109 MOZ_ASSERT(ent); 2110 2111 bool isHttp3 = ent->IsHttp3() || ent->IsHttp3ProxyConnection(); 2112 // First look at ent. If protocol that ent provides is no forbidden, 2113 // i.e. ent use HTTP3 and !aNoHttp3 or en uses HTTP over TCP and !aNoHttp2. 2114 if ((!aNoHttp3 && isHttp3) || (!aNoHttp2 && !isHttp3)) { 2115 HttpConnectionBase* conn = ent->GetH2orH3ActiveConn(); 2116 if (conn) { 2117 return conn; 2118 } 2119 } 2120 2121 nsHttpConnectionInfo* ci = ent->mConnInfo; 2122 2123 // there was no active HTTP2/3 connection in the connection entry, but 2124 // there might be one in the hash table for coalescing 2125 HttpConnectionBase* existingConn = 2126 FindCoalescableConnection(ent, false, aNoHttp2, aNoHttp3); 2127 if (existingConn) { 2128 LOG( 2129 ("GetH2orH3ActiveConn() request for ent %p %s " 2130 "found an active connection %p in the coalescing hashtable\n", 2131 ent, ci->HashKey().get(), existingConn)); 2132 return existingConn; 2133 } 2134 2135 LOG( 2136 ("GetH2orH3ActiveConn() request for ent %p %s " 2137 "did not find an active connection\n", 2138 ent, ci->HashKey().get())); 2139 return nullptr; 2140 } 2141 2142 //----------------------------------------------------------------------------- 2143 2144 void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) { 2145 if (!OnSocketThread()) { 2146 (void)PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections); 2147 return; 2148 } 2149 2150 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2151 LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n")); 2152 for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) { 2153 RefPtr<ConnectionEntry> ent = iter.Data(); 2154 2155 // Close all active connections. 2156 ent->CloseActiveConnections(); 2157 2158 // Close all idle connections. 2159 ent->CloseIdleConnections(); 2160 2161 // Close tunneled connections 2162 ent->CloseExtendedCONNECTConnections(); 2163 2164 ent->ClosePendingConnections(); 2165 2166 // Close all pending transactions. 2167 ent->CancelAllTransactions(NS_ERROR_ABORT); 2168 2169 // Close all half open tcp connections. 2170 ent->CloseAllDnsAndConnectSockets(); 2171 2172 MOZ_ASSERT(!ent->mDoNotDestroy); 2173 iter.Remove(); 2174 } 2175 2176 mActiveTransactions[false].Clear(); 2177 mActiveTransactions[true].Clear(); 2178 } 2179 2180 void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase* param) { 2181 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2182 LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); 2183 2184 gHttpHandler->StopRequestTokenBucket(); 2185 AbortAndCloseAllConnections(0, nullptr); 2186 2187 // If all idle connections are removed we can stop pruning dead 2188 // connections. 2189 ConditionallyStopPruneDeadConnectionsTimer(); 2190 2191 if (mTimeoutTick) { 2192 mTimeoutTick->Cancel(); 2193 mTimeoutTick = nullptr; 2194 mTimeoutTickArmed = false; 2195 } 2196 if (mTimer) { 2197 mTimer->Cancel(); 2198 mTimer = nullptr; 2199 } 2200 if (mTrafficTimer) { 2201 mTrafficTimer->Cancel(); 2202 mTrafficTimer = nullptr; 2203 } 2204 DestroyThrottleTicker(); 2205 2206 mCoalescingHash.Clear(); 2207 2208 // signal shutdown complete 2209 nsCOMPtr<nsIRunnable> runnable = 2210 new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, 0, param); 2211 NS_DispatchToMainThread(runnable); 2212 } 2213 2214 void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, 2215 ARefBase* param) { 2216 MOZ_ASSERT(NS_IsMainThread()); 2217 LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n")); 2218 2219 BoolWrapper* shutdown = static_cast<BoolWrapper*>(param); 2220 shutdown->mBool = true; 2221 } 2222 2223 void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, 2224 ARefBase* param) { 2225 nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param); 2226 2227 LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans)); 2228 trans->SetPriority(priority); 2229 nsresult rv = ProcessNewTransaction(trans); 2230 if (NS_FAILED(rv)) trans->Close(rv); // for whatever its worth 2231 } 2232 2233 void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority, 2234 ARefBase* param) { 2235 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2236 2237 NewTransactionData* data = static_cast<NewTransactionData*>(param); 2238 LOG( 2239 ("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn " 2240 "[trans=%p, transWithStickyConn=%p, conn=%p]\n", 2241 data->mTrans.get(), data->mTransWithStickyConn.get(), 2242 data->mTransWithStickyConn->Connection())); 2243 2244 MOZ_ASSERT(data->mTransWithStickyConn && 2245 data->mTransWithStickyConn->Caps() & NS_HTTP_STICKY_CONNECTION); 2246 2247 data->mTrans->SetPriority(data->mPriority); 2248 2249 RefPtr<nsAHttpConnection> conn = data->mTransWithStickyConn->Connection(); 2250 if (conn && conn->IsPersistent()) { 2251 // This is so far a workaround to only reuse persistent 2252 // connection for authentication retry. See bug 459620 comment 4 2253 // for details. 2254 LOG((" Reuse connection [%p] for transaction [%p]", conn.get(), 2255 data->mTrans.get())); 2256 data->mTrans->SetConnection(conn); 2257 } 2258 2259 nsresult rv = ProcessNewTransaction(data->mTrans); 2260 if (NS_FAILED(rv)) { 2261 data->mTrans->Close(rv); // for whatever its worth 2262 } 2263 } 2264 2265 void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, 2266 ARefBase* param) { 2267 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2268 LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param)); 2269 2270 RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction*>(param); 2271 trans->SetPriority(priority); 2272 2273 if (!trans->ConnectionInfo()) { 2274 return; 2275 } 2276 ConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey()); 2277 2278 if (ent) { 2279 ent->ReschedTransaction(trans); 2280 } 2281 } 2282 2283 void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction( 2284 ClassOfService cos, ARefBase* param) { 2285 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2286 LOG( 2287 ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction " 2288 "[trans=%p]\n", 2289 param)); 2290 2291 nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param); 2292 2293 ClassOfService previous = trans->GetClassOfService(); 2294 trans->SetClassOfService(cos); 2295 2296 // incremental change alone will not trigger a reschedule 2297 if ((previous.Flags() ^ cos.Flags()) & 2298 (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) { 2299 (void)RescheduleTransaction(trans, trans->Priority()); 2300 } 2301 } 2302 2303 void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, 2304 ARefBase* param) { 2305 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2306 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param)); 2307 2308 nsresult closeCode = static_cast<nsresult>(reason); 2309 2310 // caller holds a ref to param/trans on stack 2311 nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param); 2312 2313 // 2314 // if the transaction owns a connection and the transaction is not done, 2315 // then ask the connection to close the transaction. otherwise, close the 2316 // transaction directly (removing it from the pending queue first). 2317 // 2318 RefPtr<nsAHttpConnection> conn(trans->Connection()); 2319 if (conn && !trans->IsDone()) { 2320 conn->CloseTransaction(trans, closeCode); 2321 } else { 2322 ConnectionEntry* ent = nullptr; 2323 if (trans->ConnectionInfo()) { 2324 ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey()); 2325 } 2326 if (ent && ent->RemoveTransFromPendingQ(trans)) { 2327 LOG( 2328 ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]" 2329 " removed from pending queue\n", 2330 trans)); 2331 } 2332 2333 trans->Close(closeCode); 2334 2335 // Cancel is a pretty strong signal that things might be hanging 2336 // so we want to cancel any null transactions related to this connection 2337 // entry. They are just optimizations, but they aren't hooked up to 2338 // anything that might get canceled from the rest of gecko, so best 2339 // to assume that's what was meant by the cancel we did receive if 2340 // it only applied to something in the queue. 2341 if (ent) { 2342 ent->CloseAllActiveConnsWithNullTransactcion(closeCode); 2343 } 2344 } 2345 } 2346 2347 void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase* param) { 2348 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2349 nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param); 2350 2351 if (!ci) { 2352 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n")); 2353 // Try and dispatch everything 2354 for (const auto& entry : mCT.Values()) { 2355 (void)ProcessPendingQForEntry(entry.get(), true); 2356 } 2357 return; 2358 } 2359 2360 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", 2361 ci->HashKey().get())); 2362 2363 // start by processing the queue identified by the given connection info. 2364 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 2365 if (!(ent && ProcessPendingQForEntry(ent, false))) { 2366 // if we reach here, it means that we couldn't dispatch a transaction 2367 // for the specified connection info. walk the connection table... 2368 for (const auto& entry : mCT.Values()) { 2369 if (ProcessPendingQForEntry(entry.get(), false)) { 2370 break; 2371 } 2372 } 2373 } 2374 } 2375 2376 nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci, 2377 nsresult code) { 2378 LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get())); 2379 2380 int32_t intReason = static_cast<int32_t>(code); 2381 return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, 2382 ci); 2383 } 2384 2385 void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, 2386 ARefBase* param) { 2387 nsresult reason = static_cast<nsresult>(code); 2388 nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param); 2389 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 2390 LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n", 2391 ci->HashKey().get(), ent)); 2392 if (ent) { 2393 ent->CancelAllTransactions(reason); 2394 } 2395 } 2396 2397 void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase*) { 2398 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2399 LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); 2400 2401 // Reset mTimeOfNextWakeUp so that we can find a new shortest value. 2402 mTimeOfNextWakeUp = UINT64_MAX; 2403 2404 // check canreuse() for all idle connections plus any active connections on 2405 // connection entries that are using spdy. 2406 if (mNumIdleConns || 2407 (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) { 2408 for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) { 2409 RefPtr<ConnectionEntry> ent = iter.Data(); 2410 2411 LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get())); 2412 2413 // Find out how long it will take for next idle connection to not 2414 // be reusable anymore. 2415 uint32_t timeToNextExpire = ent->PruneDeadConnections(); 2416 2417 // If time to next expire found is shorter than time to next 2418 // wake-up, we need to change the time for next wake-up. 2419 if (timeToNextExpire != UINT32_MAX) { 2420 uint32_t now = NowInSeconds(); 2421 uint64_t timeOfNextExpire = now + timeToNextExpire; 2422 // If pruning of dead connections is not already scheduled to 2423 // happen or time found for next connection to expire is is 2424 // before mTimeOfNextWakeUp, we need to schedule the pruning to 2425 // happen after timeToNextExpire. 2426 if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) { 2427 PruneDeadConnectionsAfter(timeToNextExpire); 2428 } 2429 } else { 2430 ConditionallyStopPruneDeadConnectionsTimer(); 2431 } 2432 2433 ent->RemoveEmptyPendingQ(); 2434 2435 // If this entry is empty, we have too many entries busy then 2436 // we can clean it up and restart 2437 if (mCT.Count() > 125 && ent->IdleConnectionsLength() == 0 && 2438 ent->ActiveConnsLength() == 0 && 2439 ent->DnsAndConnectSocketsLength() == 0 && 2440 ent->PendingQueueLength() == 0 && 2441 ent->UrgentStartQueueLength() == 0 && !ent->mDoNotDestroy && 2442 (!ent->mUsingSpdy || mCT.Count() > 300)) { 2443 LOG((" removing empty connection entry\n")); 2444 iter.Remove(); 2445 continue; 2446 } 2447 2448 // Otherwise use this opportunity to compact our arrays... 2449 ent->Compact(); 2450 } 2451 } 2452 } 2453 2454 void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase*) { 2455 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2456 LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n")); 2457 2458 // Prune connections without traffic 2459 for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) { 2460 // Close the connections with no registered traffic. 2461 ent->PruneNoTraffic(); 2462 } 2463 2464 mPruningNoTraffic = false; // not pruning anymore 2465 } 2466 2467 void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase*) { 2468 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2469 LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n")); 2470 2471 if (mPruningNoTraffic) { 2472 // Called in the time gap when the timeout to prune notraffic 2473 // connections has triggered but the pruning hasn't happened yet. 2474 return; 2475 } 2476 2477 mCoalescingHash.Clear(); 2478 2479 // Mark connections for traffic verification 2480 for (const auto& entry : mCT.Values()) { 2481 entry->ResetIPFamilyPreference(); 2482 entry->VerifyTraffic(); 2483 } 2484 2485 // If the timer is already there. we just re-init it 2486 if (!mTrafficTimer) { 2487 mTrafficTimer = NS_NewTimer(); 2488 } 2489 2490 // failure to create a timer is not a fatal error, but dead 2491 // connections will not be cleaned up as nicely 2492 if (mTrafficTimer) { 2493 // Give active connections time to get more traffic before killing 2494 // them off. Default: 5000 milliseconds 2495 mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(), 2496 nsITimer::TYPE_ONE_SHOT); 2497 } else { 2498 NS_WARNING("failed to create timer for VerifyTraffic!"); 2499 } 2500 // Calling ActivateTimeoutTick to ensure the next timeout tick is 1s. 2501 ActivateTimeoutTick(); 2502 } 2503 2504 void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, 2505 ARefBase* param) { 2506 LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n")); 2507 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2508 2509 mCoalescingHash.Clear(); 2510 2511 nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param); 2512 2513 for (const auto& entry : mCT.Values()) { 2514 entry->ClosePersistentConnections(); 2515 } 2516 2517 if (ci) ResetIPFamilyPreference(ci); 2518 } 2519 2520 void nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup(int32_t, 2521 ARefBase* param) { 2522 LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n")); 2523 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2524 2525 nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param); 2526 2527 if (!ci) { 2528 return; 2529 } 2530 2531 ConnectionEntry* entry = mCT.GetWeak(ci->HashKey()); 2532 if (entry) { 2533 entry->ClosePersistentConnections(); 2534 } 2535 2536 ResetIPFamilyPreference(ci); 2537 } 2538 2539 void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase* conn) { 2540 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2541 2542 // 2543 // 1) remove the connection from the active list 2544 // 2) if keep-alive, add connection to idle list 2545 // 3) post event to process the pending transaction queue 2546 // 2547 2548 MOZ_ASSERT(conn); 2549 ConnectionEntry* ent = conn->ConnectionInfo() 2550 ? mCT.GetWeak(conn->ConnectionInfo()->HashKey()) 2551 : nullptr; 2552 2553 if (!ent) { 2554 // this can happen if the connection is made outside of the 2555 // connection manager and is being "reclaimed" for use with 2556 // future transactions. HTTP/2 tunnels work like this. 2557 bool isWildcard = false; 2558 ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false, false, 2559 &isWildcard); 2560 LOG( 2561 ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p " 2562 "forced new hash entry %s\n", 2563 conn, conn->ConnectionInfo()->HashKey().get())); 2564 } 2565 2566 MOZ_ASSERT(ent); 2567 RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo); 2568 2569 LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent, 2570 conn)); 2571 2572 // If the connection is in the active list, remove that entry 2573 // and the reference held by the mActiveConns list. 2574 // This is never the final reference on conn as the event context 2575 // is also holding one that is released at the end of this function. 2576 2577 RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); 2578 if (!connTCP || connTCP->EverUsedSpdy()) { 2579 // Spdyand Http3 connections aren't reused in the traditional HTTP way in 2580 // the idleconns list, they are actively multplexed as active 2581 // conns. Even when they have 0 transactions on them they are 2582 // considered active connections. So when one is reclaimed it 2583 // is really complete and is meant to be shut down and not 2584 // reused. 2585 conn->DontReuse(); 2586 } 2587 2588 // a connection that still holds a reference to a transaction was 2589 // not closed naturally (i.e. it was reset or aborted) and is 2590 // therefore not something that should be reused. 2591 if (conn->Transaction()) { 2592 conn->DontReuse(); 2593 } 2594 2595 if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn)) || 2596 NS_SUCCEEDED(ent->RemovePendingConnection(conn))) { 2597 } else { 2598 LOG( 2599 ("HttpConnectionBase %p not found in its connection entry, try " 2600 "OwnerEntry", 2601 conn)); 2602 RefPtr<ConnectionEntry> entry = conn->OwnerEntry(); 2603 if (entry) { 2604 entry->RemoveActiveConnection(conn); 2605 } 2606 } 2607 2608 MOZ_ASSERT(conn->OwnerEntry() == nullptr); 2609 2610 if (connTCP && connTCP->CanReuse()) { 2611 LOG((" adding connection to idle list\n")); 2612 // Keep The idle connection list sorted with the connections that 2613 // have moved the largest data pipelines at the front because these 2614 // connections have the largest cwnds on the server. 2615 2616 // The linear search is ok here because the number of idleconns 2617 // in a single entry is generally limited to a small number (i.e. 6) 2618 2619 ent->InsertIntoIdleConnections(connTCP); 2620 } else { 2621 if (ent->IsInExtendedCONNECTConns(conn)) { 2622 ent->RemoveExtendedCONNECTConns(conn); 2623 } 2624 LOG((" connection cannot be reused; closing connection\n")); 2625 conn->SetCloseReason(ConnectionCloseReason::CANT_REUSED); 2626 conn->Close(NS_ERROR_ABORT); 2627 } 2628 2629 OnMsgProcessPendingQ(0, ci); 2630 } 2631 2632 void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase* param) { 2633 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2634 2635 nsresult rv = NS_OK; 2636 nsCompleteUpgradeData* data = static_cast<nsCompleteUpgradeData*>(param); 2637 MOZ_ASSERT(data->mTrans && data->mTrans->Caps() & NS_HTTP_STICKY_CONNECTION); 2638 2639 RefPtr<nsAHttpConnection> conn(data->mTrans->Connection()); 2640 LOG( 2641 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade " 2642 "conn=%p listener=%p wrapped=%d\n", 2643 conn.get(), data->mUpgradeListener.get(), data->mJsWrapped)); 2644 2645 if (!conn) { 2646 // Delay any error reporting to happen in transportAvailableFunc 2647 rv = NS_ERROR_UNEXPECTED; 2648 } else { 2649 MOZ_ASSERT(!data->mSocketTransport); 2650 rv = conn->TakeTransport(getter_AddRefs(data->mSocketTransport), 2651 getter_AddRefs(data->mSocketIn), 2652 getter_AddRefs(data->mSocketOut)); 2653 2654 if (NS_FAILED(rv)) { 2655 LOG((" conn->TakeTransport failed with %" PRIx32, 2656 static_cast<uint32_t>(rv))); 2657 } 2658 } 2659 2660 RefPtr<nsCompleteUpgradeData> upgradeData(data); 2661 2662 nsCOMPtr<nsIAsyncInputStream> socketIn; 2663 nsCOMPtr<nsIAsyncOutputStream> socketOut; 2664 2665 // If this is for JS, the input and output sockets need to be piped over the 2666 // socket thread. Otherwise, the JS may attempt to read and/or write the 2667 // sockets on the main thread, which could cause network I/O on the main 2668 // thread. This is particularly bad in the case of TLS connections, because 2669 // PSM and NSS rely on those connections only being used on the socket 2670 // thread. 2671 if (data->mJsWrapped) { 2672 nsCOMPtr<nsIAsyncInputStream> pipeIn; 2673 uint32_t segsize = 0; 2674 uint32_t segcount = 0; 2675 net_ResolveSegmentParams(segsize, segcount); 2676 if (NS_SUCCEEDED(rv)) { 2677 NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(socketOut), true, true, 2678 segsize, segcount); 2679 rv = NS_AsyncCopy(pipeIn, data->mSocketOut, gSocketTransportService, 2680 NS_ASYNCCOPY_VIA_READSEGMENTS, segsize); 2681 } 2682 2683 nsCOMPtr<nsIAsyncOutputStream> pipeOut; 2684 if (NS_SUCCEEDED(rv)) { 2685 NS_NewPipe2(getter_AddRefs(socketIn), getter_AddRefs(pipeOut), true, true, 2686 segsize, segcount); 2687 rv = NS_AsyncCopy(data->mSocketIn, pipeOut, gSocketTransportService, 2688 NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize); 2689 } 2690 } else { 2691 socketIn = upgradeData->mSocketIn; 2692 socketOut = upgradeData->mSocketOut; 2693 } 2694 2695 auto transportAvailableFunc = [upgradeData{std::move(upgradeData)}, socketIn, 2696 socketOut, aRv(rv)]() { 2697 // Handle any potential previous errors first 2698 // and call OnUpgradeFailed if necessary. 2699 nsresult rv = aRv; 2700 2701 if (NS_FAILED(rv)) { 2702 rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv); 2703 if (NS_FAILED(rv)) { 2704 LOG( 2705 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed." 2706 " listener=%p\n", 2707 upgradeData->mUpgradeListener.get())); 2708 } 2709 return; 2710 } 2711 2712 rv = upgradeData->mUpgradeListener->OnTransportAvailable( 2713 upgradeData->mSocketTransport, socketIn, socketOut); 2714 if (NS_FAILED(rv)) { 2715 LOG( 2716 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable " 2717 "failed. listener=%p\n", 2718 upgradeData->mUpgradeListener.get())); 2719 } 2720 }; 2721 2722 if (data->mJsWrapped) { 2723 LOG( 2724 ("nsHttpConnectionMgr::OnMsgCompleteUpgrade " 2725 "conn=%p listener=%p wrapped=%d pass to main thread\n", 2726 conn.get(), data->mUpgradeListener.get(), data->mJsWrapped)); 2727 NS_DispatchToMainThread( 2728 NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade", 2729 transportAvailableFunc)); 2730 } else { 2731 transportAvailableFunc(); 2732 } 2733 } 2734 2735 void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase*) { 2736 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2737 uint32_t param = static_cast<uint32_t>(inParam); 2738 uint16_t name = ((param) & 0xFFFF0000) >> 16; 2739 uint16_t value = param & 0x0000FFFF; 2740 2741 switch (name) { 2742 case MAX_CONNECTIONS: 2743 mMaxConns = value; 2744 break; 2745 case MAX_URGENT_START_Q: 2746 mMaxUrgentExcessiveConns = value; 2747 break; 2748 case MAX_PERSISTENT_CONNECTIONS_PER_HOST: 2749 mMaxPersistConnsPerHost = value; 2750 break; 2751 case MAX_PERSISTENT_CONNECTIONS_PER_PROXY: 2752 mMaxPersistConnsPerProxy = value; 2753 break; 2754 case MAX_REQUEST_DELAY: 2755 mMaxRequestDelay = value; 2756 break; 2757 case THROTTLING_ENABLED: 2758 SetThrottlingEnabled(!!value); 2759 break; 2760 case THROTTLING_SUSPEND_FOR: 2761 mThrottleSuspendFor = value; 2762 break; 2763 case THROTTLING_RESUME_FOR: 2764 mThrottleResumeFor = value; 2765 break; 2766 case THROTTLING_HOLD_TIME: 2767 mThrottleHoldTime = value; 2768 break; 2769 case THROTTLING_MAX_TIME: 2770 mThrottleMaxTime = TimeDuration::FromMilliseconds(value); 2771 break; 2772 case PROXY_BE_CONSERVATIVE: 2773 mBeConservativeForProxy = !!value; 2774 break; 2775 default: 2776 MOZ_ASSERT_UNREACHABLE("unexpected parameter name"); 2777 } 2778 } 2779 2780 // Read Timeout Tick handlers 2781 2782 void nsHttpConnectionMgr::ActivateTimeoutTick() { 2783 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2784 LOG( 2785 ("nsHttpConnectionMgr::ActivateTimeoutTick() " 2786 "this=%p mTimeoutTick=%p\n", 2787 this, mTimeoutTick.get())); 2788 2789 // The timer tick should be enabled if it is not already pending. 2790 // Upon running the tick will rearm itself if there are active 2791 // connections available. 2792 2793 if (mTimeoutTick && mTimeoutTickArmed) { 2794 // make sure we get one iteration on a quick tick 2795 if (mTimeoutTickNext > 1) { 2796 mTimeoutTickNext = 1; 2797 mTimeoutTick->SetDelay(1000); 2798 } 2799 return; 2800 } 2801 2802 if (!mTimeoutTick) { 2803 mTimeoutTick = NS_NewTimer(); 2804 if (!mTimeoutTick) { 2805 NS_WARNING("failed to create timer for http timeout management"); 2806 return; 2807 } 2808 ReentrantMonitorAutoEnter mon(mReentrantMonitor); 2809 if (!mSocketThreadTarget) { 2810 NS_WARNING("cannot activate timout if not initialized or shutdown"); 2811 return; 2812 } 2813 mTimeoutTick->SetTarget(mSocketThreadTarget); 2814 } 2815 2816 if (mIsShuttingDown) { // Atomic 2817 // don't set a timer to generate an event if we're shutting down 2818 // (and mSocketThreadTarget might be null or garbage if we're shutting down) 2819 return; 2820 } 2821 MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed"); 2822 mTimeoutTickArmed = true; 2823 mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK); 2824 } 2825 2826 class UINT64Wrapper : public ARefBase { 2827 public: 2828 explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {} 2829 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override) 2830 2831 uint64_t GetValue() { return mUint64; } 2832 2833 private: 2834 uint64_t mUint64; 2835 virtual ~UINT64Wrapper() = default; 2836 }; 2837 2838 nsresult nsHttpConnectionMgr::UpdateCurrentBrowserId(uint64_t aId) { 2839 RefPtr<UINT64Wrapper> idWrapper = new UINT64Wrapper(aId); 2840 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId, 0, 2841 idWrapper); 2842 } 2843 2844 void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) { 2845 LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable)); 2846 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2847 2848 mThrottleEnabled = aEnable; 2849 2850 if (mThrottleEnabled) { 2851 EnsureThrottleTickerIfNeeded(); 2852 } else { 2853 DestroyThrottleTicker(); 2854 ResumeReadOf(mActiveTransactions[false]); 2855 ResumeReadOf(mActiveTransactions[true]); 2856 } 2857 } 2858 2859 bool nsHttpConnectionMgr::InThrottlingTimeWindow() { 2860 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2861 2862 if (mThrottlingWindowEndsAt.IsNull()) { 2863 return true; 2864 } 2865 return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt; 2866 } 2867 2868 void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker) { 2869 LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow")); 2870 2871 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2872 2873 mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime; 2874 2875 if (!mThrottleTicker && MOZ_LIKELY(aEnsureTicker) && 2876 MOZ_LIKELY(mThrottleEnabled)) { 2877 EnsureThrottleTickerIfNeeded(); 2878 } 2879 } 2880 2881 void nsHttpConnectionMgr::LogActiveTransactions(char operation) { 2882 if (!LOG_ENABLED()) { 2883 return; 2884 } 2885 2886 nsTArray<RefPtr<nsHttpTransaction>>* trs = nullptr; 2887 uint32_t au, at, bu = 0, bt = 0; 2888 2889 trs = mActiveTransactions[false].Get(mCurrentBrowserId); 2890 au = trs ? trs->Length() : 0; 2891 trs = mActiveTransactions[true].Get(mCurrentBrowserId); 2892 at = trs ? trs->Length() : 0; 2893 2894 for (const auto& data : mActiveTransactions[false].Values()) { 2895 bu += data->Length(); 2896 } 2897 bu -= au; 2898 for (const auto& data : mActiveTransactions[true].Values()) { 2899 bt += data->Length(); 2900 } 2901 bt -= at; 2902 2903 // Shows counts of: 2904 // - unthrottled transaction for the active tab 2905 // - throttled transaction for the active tab 2906 // - unthrottled transaction for background tabs 2907 // - throttled transaction for background tabs 2908 LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt)); 2909 } 2910 2911 void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction* aTrans) { 2912 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2913 2914 uint64_t tabId = aTrans->BrowserId(); 2915 bool throttled = aTrans->EligibleForThrottling(); 2916 2917 nsTArray<RefPtr<nsHttpTransaction>>* transactions = 2918 mActiveTransactions[throttled].GetOrInsertNew(tabId); 2919 2920 MOZ_ASSERT(!transactions->Contains(aTrans)); 2921 2922 transactions->AppendElement(aTrans); 2923 2924 LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64 2925 "(%d) thr=%d", 2926 aTrans, tabId, tabId == mCurrentBrowserId, throttled)); 2927 LogActiveTransactions('+'); 2928 2929 if (tabId == mCurrentBrowserId) { 2930 mActiveTabTransactionsExist = true; 2931 if (!throttled) { 2932 mActiveTabUnthrottledTransactionsExist = true; 2933 } 2934 } 2935 2936 // Shift the throttling window to the future (actually, makes sure 2937 // that throttling will engage when there is anything to throttle.) 2938 // The |false| argument means we don't need this call to ensure 2939 // the ticker, since we do it just below. Calling 2940 // EnsureThrottleTickerIfNeeded directly does a bit more than call 2941 // from inside of TouchThrottlingTimeWindow. 2942 TouchThrottlingTimeWindow(false); 2943 2944 if (!mThrottleEnabled) { 2945 return; 2946 } 2947 2948 EnsureThrottleTickerIfNeeded(); 2949 } 2950 2951 void nsHttpConnectionMgr::RemoveActiveTransaction( 2952 nsHttpTransaction* aTrans, Maybe<bool> const& aOverride) { 2953 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2954 2955 uint64_t tabId = aTrans->BrowserId(); 2956 bool forActiveTab = tabId == mCurrentBrowserId; 2957 bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling()); 2958 2959 nsTArray<RefPtr<nsHttpTransaction>>* transactions = 2960 mActiveTransactions[throttled].Get(tabId); 2961 2962 if (!transactions || !transactions->RemoveElement(aTrans)) { 2963 // Was not tracked as active, probably just ignore. 2964 return; 2965 } 2966 2967 LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64 2968 "(%d) thr=%d", 2969 aTrans, tabId, forActiveTab, throttled)); 2970 2971 if (!transactions->IsEmpty()) { 2972 // There are still transactions of the type, hence nothing in the throttling 2973 // conditions has changed and we don't need to update "Exists" caches nor we 2974 // need to wake any now throttled transactions. 2975 LogActiveTransactions('-'); 2976 return; 2977 } 2978 2979 // To optimize the following logic, always remove the entry when the array is 2980 // empty. 2981 mActiveTransactions[throttled].Remove(tabId); 2982 LogActiveTransactions('-'); 2983 2984 if (forActiveTab) { 2985 // Update caches of the active tab transaction existence, since it's now 2986 // affected 2987 if (!throttled) { 2988 mActiveTabUnthrottledTransactionsExist = false; 2989 } 2990 if (mActiveTabTransactionsExist) { 2991 mActiveTabTransactionsExist = 2992 mActiveTransactions[!throttled].Contains(tabId); 2993 } 2994 } 2995 2996 if (!mThrottleEnabled) { 2997 return; 2998 } 2999 3000 bool unthrottledExist = !mActiveTransactions[false].IsEmpty(); 3001 bool throttledExist = !mActiveTransactions[true].IsEmpty(); 3002 3003 if (!unthrottledExist && !throttledExist) { 3004 // Nothing active globally, just get rid of the timer completely and we are 3005 // done. 3006 MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist); 3007 MOZ_ASSERT(!mActiveTabTransactionsExist); 3008 3009 DestroyThrottleTicker(); 3010 return; 3011 } 3012 3013 if (!mThrottlingInhibitsReading) { 3014 // There is then nothing to wake up. Affected transactions will not be 3015 // put to sleep automatically on next tick. 3016 LOG((" reading not currently inhibited")); 3017 return; 3018 } 3019 3020 if (mActiveTabUnthrottledTransactionsExist) { 3021 // There are still unthrottled transactions for the active tab, hence the 3022 // state is unaffected and we don't need to do anything (nothing to wake). 3023 LOG((" there are unthrottled for the active tab")); 3024 return; 3025 } 3026 3027 if (mActiveTabTransactionsExist) { 3028 // There are only trottled transactions for the active tab. 3029 // If the last transaction we just removed was a non-throttled for the 3030 // active tab we can wake the throttled transactions for the active tab. 3031 if (forActiveTab && !throttled) { 3032 LOG((" resuming throttled for active tab")); 3033 ResumeReadOf(mActiveTransactions[true].Get(mCurrentBrowserId)); 3034 } 3035 return; 3036 } 3037 3038 if (!unthrottledExist) { 3039 // There are no unthrottled transactions for any tab. Resume all throttled, 3040 // all are only for background tabs. 3041 LOG((" delay resuming throttled for background tabs")); 3042 DelayedResumeBackgroundThrottledTransactions(); 3043 return; 3044 } 3045 3046 if (forActiveTab) { 3047 // Removing the last transaction for the active tab frees up the unthrottled 3048 // background tabs transactions. 3049 LOG((" delay resuming unthrottled for background tabs")); 3050 DelayedResumeBackgroundThrottledTransactions(); 3051 return; 3052 } 3053 3054 LOG((" not resuming anything")); 3055 } 3056 3057 void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction* aTrans) { 3058 LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans)); 3059 3060 // First remove then add. In case of a download that is the only active 3061 // transaction and has just been marked as download (goes unthrottled to 3062 // throttled), adding first would cause it to be throttled for first few 3063 // milliseconds - becuause it would appear as if there were both throttled 3064 // and unthrottled transactions at the time. 3065 3066 Maybe<bool> reversed; 3067 reversed.emplace(!aTrans->EligibleForThrottling()); 3068 RemoveActiveTransaction(aTrans, reversed); 3069 3070 AddActiveTransaction(aTrans); 3071 3072 LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans)); 3073 } 3074 3075 bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction* aTrans) { 3076 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3077 3078 LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans)); 3079 3080 if (!mThrottlingInhibitsReading || !mThrottleEnabled) { 3081 return false; 3082 } 3083 3084 uint64_t tabId = aTrans->BrowserId(); 3085 bool forActiveTab = tabId == mCurrentBrowserId; 3086 bool throttled = aTrans->EligibleForThrottling(); 3087 3088 bool stop = [=]() { 3089 if (mActiveTabTransactionsExist) { 3090 if (!tabId) { 3091 // Chrome initiated and unidentified transactions just respect 3092 // their throttle flag, when something for the active tab is happening. 3093 // This also includes downloads. 3094 LOG((" active tab loads, trans is tab-less, throttled=%d", throttled)); 3095 return throttled; 3096 } 3097 if (!forActiveTab) { 3098 // This is a background tab request, we want them to always throttle 3099 // when there are transactions running for the ative tab. 3100 LOG((" active tab loads, trans not of the active tab")); 3101 return true; 3102 } 3103 3104 if (mActiveTabUnthrottledTransactionsExist) { 3105 // Unthrottled transactions for the active tab take precedence 3106 LOG((" active tab loads unthrottled, trans throttled=%d", throttled)); 3107 return throttled; 3108 } 3109 3110 LOG((" trans for active tab, don't throttle")); 3111 return false; 3112 } 3113 3114 MOZ_ASSERT(!forActiveTab); 3115 3116 if (!mActiveTransactions[false].IsEmpty()) { 3117 // This means there are unthrottled active transactions for background 3118 // tabs. If we are here, there can't be any transactions for the active 3119 // tab. (If there is no transaction for a tab id, there is no entry for it 3120 // in the hashtable.) 3121 LOG((" backround tab(s) load unthrottled, trans throttled=%d", 3122 throttled)); 3123 return throttled; 3124 } 3125 3126 // There are only unthrottled transactions for background tabs: don't 3127 // throttle. 3128 LOG((" backround tab(s) load throttled, don't throttle")); 3129 return false; 3130 }(); 3131 3132 if (forActiveTab && !stop) { 3133 // This is an active-tab transaction and is allowed to read. Hence, 3134 // prolong the throttle time window to make sure all 'lower-decks' 3135 // transactions will actually throttle. 3136 TouchThrottlingTimeWindow(); 3137 return false; 3138 } 3139 3140 // Only stop reading when in the configured throttle max-time (aka time 3141 // window). This window is prolonged (restarted) by a call to 3142 // TouchThrottlingTimeWindow called on new transaction activation or on 3143 // receive of response bytes of an active tab transaction. 3144 bool inWindow = InThrottlingTimeWindow(); 3145 3146 LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d", stop, inWindow, 3147 !!mDelayedResumeReadTimer)); 3148 3149 if (!forActiveTab) { 3150 // If the delayed background resume timer exists, background transactions 3151 // are scheduled to be woken after a delay, hence leave them throttled. 3152 inWindow = inWindow || mDelayedResumeReadTimer; 3153 } 3154 3155 return stop && inWindow; 3156 } 3157 3158 bool nsHttpConnectionMgr::IsConnEntryUnderPressure( 3159 nsHttpConnectionInfo* connInfo) { 3160 ConnectionEntry* ent = mCT.GetWeak(connInfo->HashKey()); 3161 if (!ent) { 3162 // No entry, no pressure. 3163 return false; 3164 } 3165 3166 return ent->PendingQueueLengthForWindow(mCurrentBrowserId) > 0; 3167 } 3168 3169 bool nsHttpConnectionMgr::IsThrottleTickerNeeded() { 3170 LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded")); 3171 3172 if (mActiveTabUnthrottledTransactionsExist && 3173 mActiveTransactions[false].Count() > 1) { 3174 LOG((" there are unthrottled transactions for both active and bck")); 3175 return true; 3176 } 3177 3178 if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) { 3179 LOG((" there are throttled transactions for both active and bck")); 3180 return true; 3181 } 3182 3183 if (!mActiveTransactions[true].IsEmpty() && 3184 !mActiveTransactions[false].IsEmpty()) { 3185 LOG((" there are both throttled and unthrottled transactions")); 3186 return true; 3187 } 3188 3189 LOG((" nothing to throttle")); 3190 return false; 3191 } 3192 3193 void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() { 3194 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3195 3196 LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded")); 3197 if (!IsThrottleTickerNeeded()) { 3198 return; 3199 } 3200 3201 // There is a new demand to throttle, hence unschedule delayed resume 3202 // of background throttled transastions. 3203 CancelDelayedResumeBackgroundThrottledTransactions(); 3204 3205 if (mThrottleTicker) { 3206 return; 3207 } 3208 3209 mThrottleTicker = NS_NewTimer(); 3210 if (mThrottleTicker) { 3211 MOZ_ASSERT(!mThrottlingInhibitsReading); 3212 3213 mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT); 3214 mThrottlingInhibitsReading = true; 3215 } 3216 3217 LogActiveTransactions('^'); 3218 } 3219 3220 // Can be called with or without the monitor held 3221 void nsHttpConnectionMgr::DestroyThrottleTicker() { 3222 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3223 3224 // Nothing to throttle, hence no need for this timer anymore. 3225 CancelDelayedResumeBackgroundThrottledTransactions(); 3226 3227 MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded()); 3228 3229 if (!mThrottleTicker) { 3230 return; 3231 } 3232 3233 LOG(("nsHttpConnectionMgr::DestroyThrottleTicker")); 3234 mThrottleTicker->Cancel(); 3235 mThrottleTicker = nullptr; 3236 3237 mThrottlingInhibitsReading = false; 3238 3239 LogActiveTransactions('v'); 3240 } 3241 3242 void nsHttpConnectionMgr::ThrottlerTick() { 3243 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3244 3245 mThrottlingInhibitsReading = !mThrottlingInhibitsReading; 3246 3247 LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", 3248 mThrottlingInhibitsReading)); 3249 3250 // If there are only background transactions to be woken after a delay, keep 3251 // the ticker so that we woke them only for the resume-for interval and then 3252 // throttle them again until the background-resume delay passes. 3253 if (!mThrottlingInhibitsReading && !mDelayedResumeReadTimer && 3254 (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) { 3255 LOG((" last tick")); 3256 mThrottleTicker = nullptr; 3257 } 3258 3259 if (mThrottlingInhibitsReading) { 3260 if (mThrottleTicker) { 3261 mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT); 3262 } 3263 } else { 3264 if (mThrottleTicker) { 3265 mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT); 3266 } 3267 3268 ResumeReadOf(mActiveTransactions[false], true); 3269 ResumeReadOf(mActiveTransactions[true]); 3270 } 3271 } 3272 3273 void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() { 3274 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3275 3276 if (mDelayedResumeReadTimer) { 3277 return; 3278 } 3279 3280 LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions")); 3281 NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this, 3282 mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT); 3283 } 3284 3285 void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() { 3286 if (!mDelayedResumeReadTimer) { 3287 return; 3288 } 3289 3290 LOG( 3291 ("nsHttpConnectionMgr::" 3292 "CancelDelayedResumeBackgroundThrottledTransactions")); 3293 mDelayedResumeReadTimer->Cancel(); 3294 mDelayedResumeReadTimer = nullptr; 3295 } 3296 3297 void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() { 3298 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3299 3300 LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions")); 3301 mDelayedResumeReadTimer = nullptr; 3302 3303 if (!IsThrottleTickerNeeded()) { 3304 DestroyThrottleTicker(); 3305 } 3306 3307 if (!mActiveTransactions[false].IsEmpty()) { 3308 ResumeReadOf(mActiveTransactions[false], true); 3309 } else { 3310 ResumeReadOf(mActiveTransactions[true], true); 3311 } 3312 } 3313 3314 void nsHttpConnectionMgr::ResumeReadOf( 3315 nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>& 3316 hashtable, 3317 bool excludeForActiveTab) { 3318 for (const auto& entry : hashtable) { 3319 if (excludeForActiveTab && entry.GetKey() == mCurrentBrowserId) { 3320 // These have never been throttled (never stopped reading) 3321 continue; 3322 } 3323 ResumeReadOf(entry.GetWeak()); 3324 } 3325 } 3326 3327 void nsHttpConnectionMgr::ResumeReadOf( 3328 nsTArray<RefPtr<nsHttpTransaction>>* transactions) { 3329 MOZ_ASSERT(transactions); 3330 3331 for (const auto& trans : *transactions) { 3332 trans->ResumeReading(); 3333 } 3334 } 3335 3336 void nsHttpConnectionMgr::NotifyConnectionOfBrowserIdChange( 3337 uint64_t previousId) { 3338 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3339 3340 nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr; 3341 nsTArray<RefPtr<nsAHttpConnection>> connections; 3342 3343 auto addConnectionHelper = 3344 [&connections](nsTArray<RefPtr<nsHttpTransaction>>* trans) { 3345 if (!trans) { 3346 return; 3347 } 3348 3349 for (const auto& t : *trans) { 3350 RefPtr<nsAHttpConnection> conn = t->Connection(); 3351 if (conn && !connections.Contains(conn)) { 3352 connections.AppendElement(conn); 3353 } 3354 } 3355 }; 3356 3357 // Get unthrottled transactions with the previous and current window id. 3358 transactions = mActiveTransactions[false].Get(previousId); 3359 addConnectionHelper(transactions); 3360 transactions = mActiveTransactions[false].Get(mCurrentBrowserId); 3361 addConnectionHelper(transactions); 3362 3363 // Get throttled transactions with the previous and current window id. 3364 transactions = mActiveTransactions[true].Get(previousId); 3365 addConnectionHelper(transactions); 3366 transactions = mActiveTransactions[true].Get(mCurrentBrowserId); 3367 addConnectionHelper(transactions); 3368 3369 for (const auto& conn : connections) { 3370 conn->CurrentBrowserIdChanged(mCurrentBrowserId); 3371 } 3372 } 3373 3374 void nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId(int32_t aLoading, 3375 ARefBase* param) { 3376 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3377 3378 uint64_t id = static_cast<UINT64Wrapper*>(param)->GetValue(); 3379 3380 if (mCurrentBrowserId == id) { 3381 // duplicate notification 3382 return; 3383 } 3384 3385 bool activeTabWasLoading = mActiveTabTransactionsExist; 3386 3387 uint64_t previousId = mCurrentBrowserId; 3388 mCurrentBrowserId = id; 3389 3390 if (StaticPrefs::network_http_active_tab_priority()) { 3391 NotifyConnectionOfBrowserIdChange(previousId); 3392 } 3393 3394 LOG( 3395 ("nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId" 3396 " id=%" PRIx64 "\n", 3397 mCurrentBrowserId)); 3398 3399 nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr; 3400 3401 // Update the "Exists" caches and resume any transactions that now deserve it, 3402 // changing the active tab changes the conditions for throttling. 3403 transactions = mActiveTransactions[false].Get(mCurrentBrowserId); 3404 mActiveTabUnthrottledTransactionsExist = !!transactions; 3405 3406 if (!mActiveTabUnthrottledTransactionsExist) { 3407 transactions = mActiveTransactions[true].Get(mCurrentBrowserId); 3408 } 3409 mActiveTabTransactionsExist = !!transactions; 3410 3411 if (transactions) { 3412 // This means there are some transactions for this newly activated tab, 3413 // resume them but anything else. 3414 LOG((" resuming newly activated tab transactions")); 3415 ResumeReadOf(transactions); 3416 return; 3417 } 3418 3419 if (!activeTabWasLoading) { 3420 // There were no transactions for the previously active tab, hence 3421 // all remaning transactions, if there were, were all unthrottled, 3422 // no need to wake them. 3423 return; 3424 } 3425 3426 if (!mActiveTransactions[false].IsEmpty()) { 3427 LOG((" resuming unthrottled background transactions")); 3428 ResumeReadOf(mActiveTransactions[false]); 3429 return; 3430 } 3431 3432 if (!mActiveTransactions[true].IsEmpty()) { 3433 LOG((" resuming throttled background transactions")); 3434 ResumeReadOf(mActiveTransactions[true]); 3435 return; 3436 } 3437 3438 DestroyThrottleTicker(); 3439 } 3440 3441 void nsHttpConnectionMgr::TimeoutTick() { 3442 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3443 MOZ_ASSERT(mTimeoutTick, "no readtimeout tick"); 3444 3445 LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns)); 3446 // The next tick will be between 1 second and 1 hr 3447 // Set it to the max value here, and the TimeoutTick()s can 3448 // reduce it to their local needs. 3449 mTimeoutTickNext = 3600; // 1hr 3450 3451 for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) { 3452 uint32_t timeoutTickNext = ent->TimeoutTick(); 3453 mTimeoutTickNext = std::min(mTimeoutTickNext, timeoutTickNext); 3454 } 3455 3456 if (mTimeoutTick) { 3457 mTimeoutTickNext = std::max(mTimeoutTickNext, 1U); 3458 mTimeoutTick->SetDelay(mTimeoutTickNext * 1000); 3459 } 3460 } 3461 3462 // GetOrCreateConnectionEntry finds a ent for a particular CI for use in 3463 // dispatching a transaction according to these rules 3464 // 1] use an ent that matches the ci that can be dispatched immediately 3465 // 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately 3466 // 3] otherwise create an ent that matches ci and make new conn on it 3467 3468 ConnectionEntry* nsHttpConnectionMgr::GetOrCreateConnectionEntry( 3469 nsHttpConnectionInfo* specificCI, bool prohibitWildCard, bool aNoHttp2, 3470 bool aNoHttp3, bool* aIsWildcard, bool* aAvailableForDispatchNow) { 3471 if (aAvailableForDispatchNow) { 3472 *aAvailableForDispatchNow = false; 3473 } 3474 *aIsWildcard = false; 3475 3476 // step 1 3477 LOG(("GetOrCreateConnectionEntry step 1")); 3478 ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey()); 3479 if (specificEnt && specificEnt->AvailableForDispatchNow()) { 3480 if (aAvailableForDispatchNow) { 3481 *aAvailableForDispatchNow = true; 3482 } 3483 return specificEnt; 3484 } 3485 3486 // step 1 repeated for an inverted anonymous flag; we return an entry 3487 // only when it has an h2 established connection that is not authenticated 3488 // with a client certificate. 3489 RefPtr<nsHttpConnectionInfo> anonInvertedCI(specificCI->Clone()); 3490 anonInvertedCI->SetAnonymous(!specificCI->GetAnonymous()); 3491 ConnectionEntry* invertedEnt = mCT.GetWeak(anonInvertedCI->HashKey()); 3492 if (invertedEnt) { 3493 HttpConnectionBase* h2orh3conn = 3494 GetH2orH3ActiveConn(invertedEnt, aNoHttp2, aNoHttp3); 3495 if (h2orh3conn && h2orh3conn->IsExperienced() && 3496 h2orh3conn->NoClientCertAuth()) { 3497 MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3()); 3498 LOG( 3499 ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous " 3500 "connections, ent=%p", 3501 invertedEnt)); 3502 if (aAvailableForDispatchNow) { 3503 *aAvailableForDispatchNow = true; 3504 } 3505 return invertedEnt; 3506 } 3507 } 3508 3509 if (!specificCI->UsingHttpsProxy()) { 3510 prohibitWildCard = true; 3511 } 3512 3513 // step 2 3514 LOG(("GetOrCreateConnectionEntry step 2 prohibitWildCard=%d, aNoHttp3=%d", 3515 prohibitWildCard, aNoHttp3)); 3516 if (!prohibitWildCard) { 3517 RefPtr<nsHttpConnectionInfo> wildCardProxyCI; 3518 DebugOnly<nsresult> rv = 3519 specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI)); 3520 MOZ_ASSERT(NS_SUCCEEDED(rv)); 3521 ConnectionEntry* wildCardEnt = mCT.GetWeak(wildCardProxyCI->HashKey()); 3522 if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) { 3523 if (aAvailableForDispatchNow) { 3524 *aAvailableForDispatchNow = true; 3525 } 3526 *aIsWildcard = true; 3527 return wildCardEnt; 3528 } 3529 } 3530 3531 // step 3 3532 LOG(("GetOrCreateConnectionEntry step 3")); 3533 if (!specificEnt) { 3534 RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone()); 3535 specificEnt = new ConnectionEntry(clone); 3536 mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt}); 3537 } 3538 return specificEnt; 3539 } 3540 3541 void nsHttpConnectionMgr::DoSpeculativeConnection( 3542 SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) { 3543 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3544 MOZ_ASSERT(aTrans); 3545 3546 bool isWildcard = false; 3547 ConnectionEntry* ent = GetOrCreateConnectionEntry( 3548 aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY, 3549 aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard); 3550 if (!aFetchHTTPSRR && 3551 nsHttpHandler::EchConfigEnabled(aTrans->ConnectionInfo()->IsHttp3())) { 3552 // This happens when this is called from 3553 // SpeculativeTransaction::OnHTTPSRRAvailable. We have to update this 3554 // entry's echConfig so that the newly created connection can use the latest 3555 // echConfig. 3556 ent->MaybeUpdateEchConfig(aTrans->ConnectionInfo()); 3557 } 3558 DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR); 3559 } 3560 3561 void nsHttpConnectionMgr::DoSpeculativeConnectionInternal( 3562 ConnectionEntry* aEnt, SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) { 3563 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3564 MOZ_ASSERT(aTrans); 3565 MOZ_ASSERT(aEnt); 3566 if (!gHttpHandler->Active()) { 3567 // Do nothing if we are shutting down. 3568 return; 3569 } 3570 3571 ProxyDNSStrategy strategy = GetProxyDNSStrategyHelper( 3572 aEnt->mConnInfo->ProxyType(), aEnt->mConnInfo->ProxyFlag()); 3573 // Speculative connections can be triggered by non-Necko consumers, 3574 // so add an extra check to ensure HTTPS RR isn't fetched when a proxy is 3575 // used. 3576 if (aFetchHTTPSRR && strategy == ProxyDNSStrategy::ORIGIN && 3577 NS_SUCCEEDED(aTrans->FetchHTTPSRR())) { 3578 // nsHttpConnectionMgr::DoSpeculativeConnection will be called again 3579 // when HTTPS RR is available. 3580 return; 3581 } 3582 3583 uint32_t parallelSpeculativeConnectLimit = 3584 aTrans->ParallelSpeculativeConnectLimit() 3585 ? *aTrans->ParallelSpeculativeConnectLimit() 3586 : gHttpHandler->ParallelSpeculativeConnectLimit(); 3587 bool ignoreIdle = aTrans->IgnoreIdle() ? *aTrans->IgnoreIdle() : false; 3588 bool allow1918 = aTrans->Allow1918() ? *aTrans->Allow1918() : false; 3589 3590 bool keepAlive = aTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE; 3591 if (mNumDnsAndConnectSockets < parallelSpeculativeConnectLimit && 3592 ((ignoreIdle && 3593 (aEnt->IdleConnectionsLength() < parallelSpeculativeConnectLimit)) || 3594 !aEnt->IdleConnectionsLength()) && 3595 !(keepAlive && aEnt->RestrictConnections()) && 3596 !AtActiveConnectionLimit(aEnt, aTrans->Caps())) { 3597 nsresult rv = aEnt->CreateDnsAndConnectSocket(aTrans, aTrans->Caps(), true, 3598 false, allow1918, nullptr); 3599 if (NS_FAILED(rv)) { 3600 LOG( 3601 ("DoSpeculativeConnectionInternal Transport socket creation " 3602 "failure: %" PRIx32 "\n", 3603 static_cast<uint32_t>(rv))); 3604 } 3605 } else { 3606 LOG( 3607 ("DoSpeculativeConnectionInternal Transport ci=%s " 3608 "not created due to existing connection count:%d", 3609 aEnt->mConnInfo->HashKey().get(), parallelSpeculativeConnectLimit)); 3610 } 3611 } 3612 3613 void nsHttpConnectionMgr::DoFallbackConnection(SpeculativeTransaction* aTrans, 3614 bool aFetchHTTPSRR) { 3615 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3616 MOZ_ASSERT(aTrans); 3617 3618 LOG(("nsHttpConnectionMgr::DoFallbackConnection")); 3619 3620 bool availableForDispatchNow = false; 3621 bool aIsWildcard = false; 3622 ConnectionEntry* ent = GetOrCreateConnectionEntry( 3623 aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY, 3624 aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &aIsWildcard, 3625 &availableForDispatchNow); 3626 3627 if (availableForDispatchNow) { 3628 LOG( 3629 ("nsHttpConnectionMgr::DoFallbackConnection fallback connection is " 3630 "ready for dispatching ent=%p", 3631 ent)); 3632 aTrans->InvokeCallback(); 3633 return; 3634 } 3635 3636 DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR); 3637 } 3638 3639 void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase* param) { 3640 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3641 3642 SpeculativeConnectArgs* args = static_cast<SpeculativeConnectArgs*>(param); 3643 3644 LOG( 3645 ("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s, " 3646 "mFetchHTTPSRR=%d]\n", 3647 args->mTrans->ConnectionInfo()->HashKey().get(), args->mFetchHTTPSRR)); 3648 DoSpeculativeConnection(args->mTrans, args->mFetchHTTPSRR); 3649 } 3650 3651 bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo* proxy) { 3652 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3653 if (mBeConservativeForProxy) { 3654 // The pref says to be conservative for proxies. 3655 return true; 3656 } 3657 3658 if (!proxy) { 3659 // There is no proxy, so be conservative by default. 3660 return true; 3661 } 3662 3663 // Be conservative only if there is no proxy host set either. 3664 // This logic was copied from nsSSLIOLayerAddToSocket. 3665 nsAutoCString proxyHost; 3666 proxy->GetHost(proxyHost); 3667 return proxyHost.IsEmpty(); 3668 } 3669 3670 // register a connection to receive CanJoinConnection() for particular 3671 // origin keys 3672 void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn, 3673 const nsACString& host, 3674 int32_t port) { 3675 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3676 nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr; 3677 if (!ci || !conn->CanDirectlyActivate()) { 3678 return; 3679 } 3680 3681 nsAutoCString newKey; 3682 nsHttpConnectionInfo::BuildOriginFrameHashKey(newKey, ci, host, port); 3683 mCoalescingHash.GetOrInsertNew(newKey, 1)->AppendElement( 3684 do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn))); 3685 3686 LOG( 3687 ("nsHttpConnectionMgr::RegisterOriginCoalescingKey " 3688 "Established New Coalescing Key %s to %p %s\n", 3689 newKey.get(), conn, ci->HashKey().get())); 3690 } 3691 3692 bool nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams>* aArg) { 3693 for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) { 3694 if (ent->mConnInfo->GetPrivate()) { 3695 continue; 3696 } 3697 aArg->AppendElement(ent->GetConnectionData()); 3698 } 3699 3700 return true; 3701 } 3702 3703 bool nsHttpConnectionMgr::GetHttp3ConnectionStatsData( 3704 nsTArray<Http3ConnectionStatsParams>* aArg) { 3705 for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) { 3706 if (ent->mConnInfo->GetPrivate()) { 3707 continue; 3708 } 3709 aArg->AppendElement(ent->GetHttp3ConnectionStatsData()); 3710 } 3711 3712 return true; 3713 } 3714 3715 void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) { 3716 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3717 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 3718 if (ent) { 3719 ent->ResetIPFamilyPreference(); 3720 } 3721 } 3722 3723 void nsHttpConnectionMgr::ExcludeHttp2(const nsHttpConnectionInfo* ci) { 3724 LOG(("nsHttpConnectionMgr::ExcludeHttp2 excluding ci %s", 3725 ci->HashKey().BeginReading())); 3726 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 3727 if (!ent) { 3728 LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!")); 3729 return; 3730 } 3731 3732 ent->DisallowHttp2(); 3733 } 3734 3735 void nsHttpConnectionMgr::ExcludeHttp3(const nsHttpConnectionInfo* ci) { 3736 LOG(("nsHttpConnectionMgr::ExcludeHttp3 exclude ci %s", 3737 ci->HashKey().BeginReading())); 3738 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 3739 if (!ent) { 3740 LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!")); 3741 return; 3742 } 3743 3744 ent->DontReuseHttp3Conn(); 3745 } 3746 3747 void nsHttpConnectionMgr::MoveToWildCardConnEntry( 3748 nsHttpConnectionInfo* specificCI, nsHttpConnectionInfo* wildCardCI, 3749 HttpConnectionBase* proxyConn) { 3750 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3751 MOZ_ASSERT(specificCI->UsingHttpsProxy()); 3752 3753 LOG( 3754 ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to " 3755 "change CI from %s to %s\n", 3756 proxyConn, specificCI->HashKey().get(), wildCardCI->HashKey().get())); 3757 3758 ConnectionEntry* ent = mCT.GetWeak(specificCI->HashKey()); 3759 LOG( 3760 ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy " 3761 "%d, h3=%d)\n", 3762 proxyConn, ent, ent ? ent->mUsingSpdy : 0, 3763 ent ? ent->IsHttp3ProxyConnection() : 0)); 3764 3765 if (!ent || (!ent->mUsingSpdy && !ent->IsHttp3ProxyConnection())) { 3766 return; 3767 } 3768 3769 bool isWildcard = false; 3770 ConnectionEntry* wcEnt = 3771 GetOrCreateConnectionEntry(wildCardCI, true, false, false, &isWildcard); 3772 if (wcEnt == ent) { 3773 // nothing to do! 3774 LOG(("nothing to do ")); 3775 return; 3776 } 3777 if (ent->mUsingSpdy) { 3778 wcEnt->mUsingSpdy = true; 3779 } else { 3780 MOZ_ASSERT(wcEnt->IsHttp3ProxyConnection()); 3781 } 3782 3783 LOG( 3784 ("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p " 3785 "idle=%zu active=%zu half=%zu pending=%zu\n", 3786 ent, ent->IdleConnectionsLength(), ent->ActiveConnsLength(), 3787 ent->DnsAndConnectSocketsLength(), ent->PendingQueueLength())); 3788 3789 LOG( 3790 ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p " 3791 "idle=%zu active=%zu half=%zu pending=%zu\n", 3792 wcEnt, wcEnt->IdleConnectionsLength(), wcEnt->ActiveConnsLength(), 3793 wcEnt->DnsAndConnectSocketsLength(), wcEnt->PendingQueueLength())); 3794 3795 ent->MoveConnection(proxyConn, wcEnt); 3796 // Ensure other wildcard connections are closed gracefully. 3797 wcEnt->MakeAllDontReuseExcept(proxyConn); 3798 } 3799 3800 bool nsHttpConnectionMgr::RemoveTransFromConnEntry(nsHttpTransaction* aTrans, 3801 const nsACString& aHashKey) { 3802 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3803 3804 LOG(("nsHttpConnectionMgr::RemoveTransFromConnEntry: trans=%p ci=%s", aTrans, 3805 PromiseFlatCString(aHashKey).get())); 3806 3807 if (aHashKey.IsEmpty()) { 3808 return false; 3809 } 3810 3811 // Step 1: Get the transaction's connection entry. 3812 ConnectionEntry* entry = mCT.GetWeak(aHashKey); 3813 if (!entry) { 3814 return false; 3815 } 3816 3817 // Step 2: Try to find the undispatched transaction. 3818 return entry->RemoveTransFromPendingQ(aTrans); 3819 } 3820 3821 void nsHttpConnectionMgr::IncreaseNumDnsAndConnectSockets() { 3822 mNumDnsAndConnectSockets++; 3823 } 3824 3825 void nsHttpConnectionMgr::DecreaseNumDnsAndConnectSockets() { 3826 MOZ_ASSERT(mNumDnsAndConnectSockets); 3827 if (mNumDnsAndConnectSockets) { // just in case 3828 mNumDnsAndConnectSockets--; 3829 } 3830 } 3831 3832 already_AddRefed<PendingTransactionInfo> 3833 nsHttpConnectionMgr::FindTransactionHelper(bool removeWhenFound, 3834 ConnectionEntry* aEnt, 3835 nsAHttpTransaction* aTrans) { 3836 nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ = 3837 aEnt->GetTransactionPendingQHelper(aTrans); 3838 3839 int32_t index = 3840 pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1; 3841 3842 RefPtr<PendingTransactionInfo> info; 3843 if (index != -1) { 3844 info = (*pendingQ)[index]; 3845 if (removeWhenFound) { 3846 pendingQ->RemoveElementAt(index); 3847 } 3848 } 3849 return info.forget(); 3850 } 3851 3852 already_AddRefed<ConnectionEntry> nsHttpConnectionMgr::FindConnectionEntry( 3853 const nsHttpConnectionInfo* ci) { 3854 return mCT.Get(ci->HashKey()); 3855 } 3856 3857 nsHttpConnectionMgr* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; } 3858 3859 HttpConnectionMgrParent* nsHttpConnectionMgr::AsHttpConnectionMgrParent() { 3860 return nullptr; 3861 } 3862 3863 void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive) { 3864 mNumIdleConns++; 3865 3866 // If the added connection was first idle connection or has shortest 3867 // time to live among the watched connections, pruning dead 3868 // connections needs to be done when it can't be reused anymore. 3869 if (!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) { 3870 PruneDeadConnectionsAfter(timeToLive); 3871 } 3872 } 3873 3874 void nsHttpConnectionMgr::DecrementNumIdleConns() { 3875 MOZ_ASSERT(mNumIdleConns); 3876 mNumIdleConns--; 3877 ConditionallyStopPruneDeadConnectionsTimer(); 3878 } 3879 3880 // A structure used to marshall objects necessary for ServerCertificateHashaes 3881 class nsStoreServerCertHashesData : public ARefBase { 3882 public: 3883 nsStoreServerCertHashesData( 3884 nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3, 3885 nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes) 3886 : mConnInfo(aConnInfo), 3887 mNoSpdy(aNoSpdy), 3888 mNoHttp3(aNoHttp3), 3889 mServerCertHashes(std::move(aServerCertHashes)) {} 3890 3891 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStoreServerCertHashesData, override) 3892 3893 RefPtr<nsHttpConnectionInfo> mConnInfo; 3894 bool mNoSpdy; 3895 bool mNoHttp3; 3896 nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes; 3897 3898 private: 3899 virtual ~nsStoreServerCertHashesData() = default; 3900 }; 3901 3902 // The connection manager needs to know the hashes used for a WebTransport 3903 // connection authenticated with serverCertHashes 3904 nsresult nsHttpConnectionMgr::StoreServerCertHashes( 3905 nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3, 3906 nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes) { 3907 RefPtr<nsHttpConnectionInfo> ci = aConnInfo->Clone(); 3908 RefPtr<nsStoreServerCertHashesData> data = new nsStoreServerCertHashesData( 3909 ci, aNoSpdy, aNoHttp3, std::move(aServerCertHashes)); 3910 return PostEvent(&nsHttpConnectionMgr::OnMsgStoreServerCertHashes, 0, data); 3911 } 3912 3913 void nsHttpConnectionMgr::OnMsgStoreServerCertHashes(int32_t, ARefBase* param) { 3914 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3915 3916 nsStoreServerCertHashesData* data = 3917 static_cast<nsStoreServerCertHashesData*>(param); 3918 3919 bool isWildcard; 3920 ConnectionEntry* connEnt = GetOrCreateConnectionEntry( 3921 data->mConnInfo, true, data->mNoSpdy, data->mNoHttp3, &isWildcard); 3922 MOZ_ASSERT(!isWildcard, "No webtransport with wildcard"); 3923 connEnt->SetServerCertHashes(std::move(data->mServerCertHashes)); 3924 } 3925 3926 const nsTArray<RefPtr<nsIWebTransportHash>>* 3927 nsHttpConnectionMgr::GetServerCertHashes(nsHttpConnectionInfo* aConnInfo) { 3928 ConnectionEntry* connEnt = mCT.GetWeak(aConnInfo->HashKey()); 3929 if (!connEnt) { 3930 MOZ_ASSERT(0); 3931 return nullptr; 3932 } 3933 return &connEnt->GetServerCertHashes(); 3934 } 3935 3936 void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction* aTrans) { 3937 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 3938 // We only do this check on socket thread. When this function is called on 3939 // main thread, the transaction is newly created, so we can skip this check. 3940 if (!OnSocketThread()) { 3941 return; 3942 } 3943 3944 nsAutoCString hashKey; 3945 aTrans->GetHashKeyOfConnectionEntry(hashKey); 3946 if (hashKey.IsEmpty()) { 3947 return; 3948 } 3949 3950 bool foundInPendingQ = RemoveTransFromConnEntry(aTrans, hashKey); 3951 MOZ_DIAGNOSTIC_ASSERT(!foundInPendingQ); 3952 #endif 3953 } 3954 3955 bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3( 3956 nsHttpConnectionInfo* ci, nsresult aError) { 3957 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 3958 if (!ent) { 3959 return false; 3960 } 3961 3962 return ent->AllowToRetryDifferentIPFamilyForHttp3(aError); 3963 } 3964 3965 void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3( 3966 nsHttpConnectionInfo* ci, uint16_t aIPFamily) { 3967 ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); 3968 if (!ent) { 3969 return; 3970 } 3971 3972 ent->SetRetryDifferentIPFamilyForHttp3(aIPFamily); 3973 } 3974 3975 } // namespace mozilla::net