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 "NullHttpTransaction.h"
19 #include "mozilla/ChaosMode.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/net/DNS.h"
24 #include "mozilla/net/DashboardTypes.h"
25 #include "nsCOMPtr.h"
26 #include "nsHttpConnectionMgr.h"
27 #include "nsHttpHandler.h"
28 #include "nsIClassOfService.h"
29 #include "nsIDNSRecord.h"
30 #include "nsIHttpChannelInternal.h"
31 #include "nsIRequestContext.h"
32 #include "nsISocketTransport.h"
33 #include "nsISocketTransportService.h"
34 #include "nsITransport.h"
35 #include "nsIXPConnect.h"
36 #include "nsInterfaceRequestorAgg.h"
37 #include "nsNetCID.h"
38 #include "nsNetUtil.h"
39 #include "nsQueryObject.h"
40 #include "HttpConnectionUDP.h"
41 #include "TCPFastOpenLayer.h"
42 
43 namespace mozilla {
44 namespace net {
45 
46 //-----------------------------------------------------------------------------
47 
NS_IMPL_ISUPPORTS(nsHttpConnectionMgr,nsIObserver)48 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
49 
50 // This function decides the transaction's order in the pending queue.
51 // Given two transactions t1 and t2, returning true means that t2 is
52 // more important than t1 and thus should be dispatched first.
53 static bool TransactionComparator(nsHttpTransaction* t1,
54                                   nsHttpTransaction* t2) {
55   bool t1Blocking =
56       t1->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
57   bool t2Blocking =
58       t2->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
59 
60   if (t1Blocking > t2Blocking) {
61     return false;
62   }
63 
64   if (t2Blocking > t1Blocking) {
65     return true;
66   }
67 
68   return t1->Priority() >= t2->Priority();
69 }
70 
InsertTransactionSorted(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>> & pendingQ,nsHttpConnectionMgr::PendingTransactionInfo * pendingTransInfo,bool aInsertAsFirstForTheSamePriority)71 void nsHttpConnectionMgr::InsertTransactionSorted(
72     nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>>& pendingQ,
73     nsHttpConnectionMgr::PendingTransactionInfo* pendingTransInfo,
74     bool aInsertAsFirstForTheSamePriority /*= false*/) {
75   // insert the transaction into the front of the queue based on following
76   // rules:
77   // 1. The transaction has NS_HTTP_LOAD_AS_BLOCKING or NS_HTTP_LOAD_UNBLOCKED.
78   // 2. The transaction's priority is higher.
79   //
80   // search in reverse order under the assumption that many of the
81   // existing transactions will have the same priority (usually 0).
82 
83   nsHttpTransaction* trans = pendingTransInfo->mTransaction;
84 
85   for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
86     nsHttpTransaction* t = pendingQ[i]->mTransaction;
87     if (TransactionComparator(trans, t)) {
88       if (ChaosMode::isActive(ChaosFeature::NetworkScheduling) ||
89           aInsertAsFirstForTheSamePriority) {
90         int32_t samePriorityCount;
91         for (samePriorityCount = 0; i - samePriorityCount >= 0;
92              ++samePriorityCount) {
93           if (pendingQ[i - samePriorityCount]->mTransaction->Priority() !=
94               trans->Priority()) {
95             break;
96           }
97         }
98         if (aInsertAsFirstForTheSamePriority) {
99           i -= samePriorityCount;
100         } else {
101           // skip over 0...all of the elements with the same priority.
102           i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
103         }
104       }
105       pendingQ.InsertElementAt(i + 1, pendingTransInfo);
106       return;
107     }
108   }
109   pendingQ.InsertElementAt(0, pendingTransInfo);
110 }
111 
112 //-----------------------------------------------------------------------------
113 
nsHttpConnectionMgr()114 nsHttpConnectionMgr::nsHttpConnectionMgr()
115     : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor"),
116       mMaxUrgentExcessiveConns(0),
117       mMaxConns(0),
118       mMaxPersistConnsPerHost(0),
119       mMaxPersistConnsPerProxy(0),
120       mMaxRequestDelay(0),
121       mThrottleEnabled(false),
122       mThrottleVersion(2),
123       mThrottleSuspendFor(0),
124       mThrottleResumeFor(0),
125       mThrottleReadLimit(0),
126       mThrottleReadInterval(0),
127       mThrottleHoldTime(0),
128       mThrottleMaxTime(0),
129       mBeConservativeForProxy(true),
130       mIsShuttingDown(false),
131       mNumActiveConns(0),
132       mNumIdleConns(0),
133       mNumSpdyHttp3ActiveConns(0),
134       mNumHalfOpenConns(0),
135       mTimeOfNextWakeUp(UINT64_MAX),
136       mPruningNoTraffic(false),
137       mTimeoutTickArmed(false),
138       mTimeoutTickNext(1),
139       mCurrentTopLevelOuterContentWindowId(0),
140       mThrottlingInhibitsReading(false),
141       mActiveTabTransactionsExist(false),
142       mActiveTabUnthrottledTransactionsExist(false) {
143   LOG(("Creating nsHttpConnectionMgr @%p\n", this));
144 }
145 
~nsHttpConnectionMgr()146 nsHttpConnectionMgr::~nsHttpConnectionMgr() {
147   LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
148   MOZ_ASSERT(mCoalescingHash.Count() == 0);
149   if (mTimeoutTick) mTimeoutTick->Cancel();
150 }
151 
EnsureSocketThreadTarget()152 nsresult nsHttpConnectionMgr::EnsureSocketThreadTarget() {
153   nsCOMPtr<nsIEventTarget> sts;
154   nsCOMPtr<nsIIOService> ioService = services::GetIOService();
155   if (ioService) {
156     nsCOMPtr<nsISocketTransportService> realSTS =
157         services::GetSocketTransportService();
158     sts = do_QueryInterface(realSTS);
159   }
160 
161   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
162 
163   // do nothing if already initialized or if we've shut down
164   if (mSocketThreadTarget || mIsShuttingDown) return NS_OK;
165 
166   mSocketThreadTarget = sts;
167 
168   return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
169 }
170 
Init(uint16_t maxUrgentExcessiveConns,uint16_t maxConns,uint16_t maxPersistConnsPerHost,uint16_t maxPersistConnsPerProxy,uint16_t maxRequestDelay,bool throttleEnabled,uint32_t throttleVersion,uint32_t throttleSuspendFor,uint32_t throttleResumeFor,uint32_t throttleReadLimit,uint32_t throttleReadInterval,uint32_t throttleHoldTime,uint32_t throttleMaxTime,bool beConservativeForProxy)171 nsresult nsHttpConnectionMgr::Init(
172     uint16_t maxUrgentExcessiveConns, uint16_t maxConns,
173     uint16_t maxPersistConnsPerHost, uint16_t maxPersistConnsPerProxy,
174     uint16_t maxRequestDelay, bool throttleEnabled, uint32_t throttleVersion,
175     uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
176     uint32_t throttleReadLimit, uint32_t throttleReadInterval,
177     uint32_t throttleHoldTime, uint32_t throttleMaxTime,
178     bool beConservativeForProxy) {
179   LOG(("nsHttpConnectionMgr::Init\n"));
180 
181   {
182     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
183 
184     mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
185     mMaxConns = maxConns;
186     mMaxPersistConnsPerHost = maxPersistConnsPerHost;
187     mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
188     mMaxRequestDelay = maxRequestDelay;
189 
190     mThrottleEnabled = throttleEnabled;
191     mThrottleVersion = throttleVersion;
192     mThrottleSuspendFor = throttleSuspendFor;
193     mThrottleResumeFor = throttleResumeFor;
194     mThrottleReadLimit = throttleReadLimit;
195     mThrottleReadInterval = throttleReadInterval;
196     mThrottleHoldTime = throttleHoldTime;
197     mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
198 
199     mBeConservativeForProxy = beConservativeForProxy;
200 
201     mIsShuttingDown = false;
202   }
203 
204   return EnsureSocketThreadTarget();
205 }
206 
207 class BoolWrapper : public ARefBase {
208  public:
BoolWrapper()209   BoolWrapper() : mBool(false) {}
210   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
211 
212  public:  // intentional!
213   bool mBool;
214 
215  private:
216   virtual ~BoolWrapper() = default;
217 };
218 
Shutdown()219 nsresult nsHttpConnectionMgr::Shutdown() {
220   LOG(("nsHttpConnectionMgr::Shutdown\n"));
221 
222   RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
223   {
224     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
225 
226     // do nothing if already shutdown
227     if (!mSocketThreadTarget) return NS_OK;
228 
229     nsresult rv =
230         PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, 0, shutdownWrapper);
231 
232     // release our reference to the STS to prevent further events
233     // from being posted.  this is how we indicate that we are
234     // shutting down.
235     mIsShuttingDown = true;
236     mSocketThreadTarget = nullptr;
237 
238     if (NS_FAILED(rv)) {
239       NS_WARNING("unable to post SHUTDOWN message");
240       return rv;
241     }
242   }
243 
244   // wait for shutdown event to complete
245   SpinEventLoopUntil([&, shutdownWrapper]() { return shutdownWrapper->mBool; });
246 
247   return NS_OK;
248 }
249 
250 class ConnEvent : public Runnable {
251  public:
ConnEvent(nsHttpConnectionMgr * mgr,nsConnEventHandler handler,int32_t iparam,ARefBase * vparam)252   ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
253             int32_t iparam, ARefBase* vparam)
254       : Runnable("net::ConnEvent"),
255         mMgr(mgr),
256         mHandler(handler),
257         mIParam(iparam),
258         mVParam(vparam) {}
259 
Run()260   NS_IMETHOD Run() override {
261     (mMgr->*mHandler)(mIParam, mVParam);
262     return NS_OK;
263   }
264 
265  private:
266   virtual ~ConnEvent() = default;
267 
268   RefPtr<nsHttpConnectionMgr> mMgr;
269   nsConnEventHandler mHandler;
270   int32_t mIParam;
271   RefPtr<ARefBase> mVParam;
272 };
273 
PostEvent(nsConnEventHandler handler,int32_t iparam,ARefBase * vparam)274 nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
275                                         int32_t iparam, ARefBase* vparam) {
276   Unused << EnsureSocketThreadTarget();
277 
278   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
279 
280   nsresult rv;
281   if (!mSocketThreadTarget) {
282     NS_WARNING("cannot post event if not initialized");
283     rv = NS_ERROR_NOT_INITIALIZED;
284   } else {
285     nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
286     rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
287   }
288   return rv;
289 }
290 
PruneDeadConnectionsAfter(uint32_t timeInSeconds)291 void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) {
292   LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
293 
294   if (!mTimer) mTimer = NS_NewTimer();
295 
296   // failure to create a timer is not a fatal error, but idle connections
297   // will not be cleaned up until we try to use them.
298   if (mTimer) {
299     mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
300     mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
301   } else {
302     NS_WARNING("failed to create: timer for pruning the dead connections!");
303   }
304 }
305 
ConditionallyStopPruneDeadConnectionsTimer()306 void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() {
307   // Leave the timer in place if there are connections that potentially
308   // need management
309   if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
310     return;
311 
312   LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
313 
314   // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
315   mTimeOfNextWakeUp = UINT64_MAX;
316   if (mTimer) {
317     mTimer->Cancel();
318     mTimer = nullptr;
319   }
320 }
321 
ConditionallyStopTimeoutTick()322 void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
323   LOG(
324       ("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
325        "armed=%d active=%d\n",
326        mTimeoutTickArmed, mNumActiveConns));
327 
328   if (!mTimeoutTickArmed) return;
329 
330   if (mNumActiveConns) return;
331 
332   LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
333 
334   mTimeoutTick->Cancel();
335   mTimeoutTickArmed = false;
336 }
337 
338 //-----------------------------------------------------------------------------
339 // nsHttpConnectionMgr::nsIObserver
340 //-----------------------------------------------------------------------------
341 
342 NS_IMETHODIMP
Observe(nsISupports * subject,const char * topic,const char16_t * data)343 nsHttpConnectionMgr::Observe(nsISupports* subject, const char* topic,
344                              const char16_t* data) {
345   LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
346 
347   if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
348     nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
349     if (timer == mTimer) {
350       Unused << PruneDeadConnections();
351     } else if (timer == mTimeoutTick) {
352       TimeoutTick();
353     } else if (timer == mTrafficTimer) {
354       Unused << PruneNoTraffic();
355     } else if (timer == mThrottleTicker) {
356       ThrottlerTick();
357     } else if (timer == mDelayedResumeReadTimer) {
358       ResumeBackgroundThrottledTransactions();
359     } else {
360       MOZ_ASSERT(false, "unexpected timer-callback");
361       LOG(("Unexpected timer object\n"));
362       return NS_ERROR_UNEXPECTED;
363     }
364   }
365 
366   return NS_OK;
367 }
368 
369 //-----------------------------------------------------------------------------
370 
AddTransaction(HttpTransactionShell * trans,int32_t priority)371 nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
372                                              int32_t priority) {
373   LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
374   return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
375                    trans->AsHttpTransaction());
376 }
377 
378 class NewTransactionData : public ARefBase {
379  public:
NewTransactionData(nsHttpTransaction * trans,int32_t priority,nsHttpTransaction * transWithStickyConn)380   NewTransactionData(nsHttpTransaction* trans, int32_t priority,
381                      nsHttpTransaction* transWithStickyConn)
382       : mTrans(trans),
383         mPriority(priority),
384         mTransWithStickyConn(transWithStickyConn) {}
385 
386   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
387 
388   RefPtr<nsHttpTransaction> mTrans;
389   int32_t mPriority;
390   RefPtr<nsHttpTransaction> mTransWithStickyConn;
391 
392  private:
393   virtual ~NewTransactionData() = default;
394 };
395 
AddTransactionWithStickyConn(HttpTransactionShell * trans,int32_t priority,HttpTransactionShell * transWithStickyConn)396 nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
397     HttpTransactionShell* trans, int32_t priority,
398     HttpTransactionShell* transWithStickyConn) {
399   LOG(
400       ("nsHttpConnectionMgr::AddTransactionWithStickyConn "
401        "[trans=%p %d transWithStickyConn=%p]\n",
402        trans, priority, transWithStickyConn));
403   RefPtr<NewTransactionData> data =
404       new NewTransactionData(trans->AsHttpTransaction(), priority,
405                              transWithStickyConn->AsHttpTransaction());
406   return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
407                    data);
408 }
409 
RescheduleTransaction(HttpTransactionShell * trans,int32_t priority)410 nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
411                                                     int32_t priority) {
412   LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
413        priority));
414   return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
415                    trans->AsHttpTransaction());
416 }
417 
UpdateClassOfServiceOnTransaction(HttpTransactionShell * trans,uint32_t classOfService)418 void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
419     HttpTransactionShell* trans, uint32_t classOfService) {
420   LOG(
421       ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
422        "classOfService=%" PRIu32 "]\n",
423        trans, static_cast<uint32_t>(classOfService)));
424   Unused << PostEvent(
425       &nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
426       static_cast<int32_t>(classOfService), trans->AsHttpTransaction());
427 }
428 
CancelTransaction(HttpTransactionShell * trans,nsresult reason)429 nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans,
430                                                 nsresult reason) {
431   LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
432        trans, static_cast<uint32_t>(reason)));
433   return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
434                    static_cast<int32_t>(reason), trans->AsHttpTransaction());
435 }
436 
PruneDeadConnections()437 nsresult nsHttpConnectionMgr::PruneDeadConnections() {
438   return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
439 }
440 
441 //
442 // Called after a timeout. Check for active connections that have had no
443 // traffic since they were "marked" and nuke them.
PruneNoTraffic()444 nsresult nsHttpConnectionMgr::PruneNoTraffic() {
445   LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
446   mPruningNoTraffic = true;
447   return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
448 }
449 
VerifyTraffic()450 nsresult nsHttpConnectionMgr::VerifyTraffic() {
451   LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
452   return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
453 }
454 
DoShiftReloadConnectionCleanup(nsHttpConnectionInfo * aCI)455 nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(
456     nsHttpConnectionInfo* aCI) {
457   RefPtr<nsHttpConnectionInfo> ci;
458   if (aCI) {
459     ci = aCI->Clone();
460   }
461   return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
462                    ci);
463 }
464 
465 class SpeculativeConnectArgs : public ARefBase {
466  public:
SpeculativeConnectArgs()467   SpeculativeConnectArgs()
468       : mParallelSpeculativeConnectLimit(0),
469         mIgnoreIdle(false),
470         mIsFromPredictor(false),
471         mAllow1918(false) {
472     mOverridesOK = false;
473   }
474   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
475 
476  public:  // intentional!
477   RefPtr<NullHttpTransaction> mTrans;
478 
479   bool mOverridesOK;
480   uint32_t mParallelSpeculativeConnectLimit;
481   bool mIgnoreIdle;
482   bool mIsFromPredictor;
483   bool mAllow1918;
484 
485  private:
486   virtual ~SpeculativeConnectArgs() = default;
487   NS_DECL_OWNINGTHREAD
488 };
489 
SpeculativeConnect(nsHttpConnectionInfo * ci,nsIInterfaceRequestor * callbacks,uint32_t caps,NullHttpTransaction * nullTransaction)490 nsresult nsHttpConnectionMgr::SpeculativeConnect(
491     nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
492     NullHttpTransaction* nullTransaction) {
493   if (!IsNeckoChild() && NS_IsMainThread()) {
494     // HACK: make sure PSM gets initialized on the main thread.
495     net_EnsurePSMInit();
496   }
497 
498   LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
499        ci->HashKey().get()));
500 
501   nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
502       do_GetInterface(callbacks);
503 
504   bool allow1918 = overrider ? overrider->GetAllow1918() : false;
505 
506   // Hosts that are Local IP Literals should not be speculatively
507   // connected - Bug 853423.
508   if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
509     LOG(
510         ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
511          "address [%s]",
512          ci->Origin()));
513     return NS_OK;
514   }
515 
516   RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
517 
518   // Wrap up the callbacks and the target to ensure they're released on the
519   // target thread properly.
520   nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
521   NS_NewInterfaceRequestorAggregation(callbacks, nullptr,
522                                       getter_AddRefs(wrappedCallbacks));
523 
524   caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
525   caps |= NS_HTTP_ERROR_SOFTLY;
526   args->mTrans = nullTransaction
527                      ? nullTransaction
528                      : new NullHttpTransaction(ci, wrappedCallbacks, caps);
529 
530   if (overrider) {
531     args->mOverridesOK = true;
532     args->mParallelSpeculativeConnectLimit =
533         overrider->GetParallelSpeculativeConnectLimit();
534     args->mIgnoreIdle = overrider->GetIgnoreIdle();
535     args->mIsFromPredictor = overrider->GetIsFromPredictor();
536     args->mAllow1918 = overrider->GetAllow1918();
537   }
538 
539   return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
540 }
541 
GetSocketThreadTarget(nsIEventTarget ** target)542 nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) {
543   Unused << EnsureSocketThreadTarget();
544 
545   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
546   nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
547   temp.forget(target);
548   return NS_OK;
549 }
550 
ReclaimConnection(HttpConnectionBase * conn)551 nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) {
552   LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
553 
554   Unused << EnsureSocketThreadTarget();
555 
556   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
557 
558   if (!mSocketThreadTarget) {
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 mSocketThreadTarget->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:
nsCompleteUpgradeData(nsHttpTransaction * aTrans,nsIHttpUpgradeListener * aListener,bool aJsWrapped)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:
~nsCompleteUpgradeData()592   virtual ~nsCompleteUpgradeData() {
593     NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
594                            mUpgradeListener.forget());
595   }
596 };
597 
CompleteUpgrade(HttpTransactionShell * aTrans,nsIHttpUpgradeListener * aUpgradeListener)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 
UpdateParam(nsParamName name,uint16_t value)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 
ProcessPendingQ(nsHttpConnectionInfo * aCI)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 
ProcessPendingQ()625 nsresult nsHttpConnectionMgr::ProcessPendingQ() {
626   LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
627   return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
628 }
629 
OnMsgUpdateRequestTokenBucket(int32_t,ARefBase * param)630 void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
631                                                         ARefBase* param) {
632   EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param);
633   gHttpHandler->SetRequestTokenBucket(tokenBucket);
634 }
635 
UpdateRequestTokenBucket(EventTokenBucket * aBucket)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 
ClearConnectionHistory()644 nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
645   return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
646                    nullptr);
647 }
648 
OnMsgClearConnectionHistory(int32_t,ARefBase * param)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<nsConnectionEntry> ent = iter.Data();
657     if (ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 &&
658         ent->mHalfOpens.Length() == 0 && ent->mUrgentStartQ.Length() == 0 &&
659         ent->PendingQLength() == 0 &&
660         ent->mHalfOpenFastOpenBackups.Length() == 0 && !ent->mDoNotDestroy) {
661       iter.Remove();
662     }
663   }
664 }
665 
CloseIdleConnection(nsHttpConnection * conn)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   nsConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
675 
676   RefPtr<nsHttpConnection> deleteProtector(conn);
677   if (!ent || !ent->mIdleConns.RemoveElement(conn)) return NS_ERROR_UNEXPECTED;
678 
679   conn->Close(NS_ERROR_ABORT);
680   mNumIdleConns--;
681   ConditionallyStopPruneDeadConnectionsTimer();
682   return NS_OK;
683 }
684 
RemoveIdleConnection(nsHttpConnection * conn)685 nsresult nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection* conn) {
686   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
687 
688   LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn));
689 
690   if (!conn->ConnectionInfo()) {
691     return NS_ERROR_UNEXPECTED;
692   }
693 
694   nsConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
695 
696   if (!ent || !ent->mIdleConns.RemoveElement(conn)) {
697     return NS_ERROR_UNEXPECTED;
698   }
699 
700   mNumIdleConns--;
701   ConditionallyStopPruneDeadConnectionsTimer();
702   return NS_OK;
703 }
704 
FindCoalescableConnectionByHashKey(nsConnectionEntry * ent,const nsCString & key,bool justKidding,bool aNoHttp3)705 HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
706     nsConnectionEntry* ent, const nsCString& key, bool justKidding,
707     bool aNoHttp3) {
708   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
709   MOZ_ASSERT(ent->mConnInfo);
710   nsHttpConnectionInfo* ci = ent->mConnInfo;
711 
712   nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key);
713   if (!listOfWeakConns) {
714     return nullptr;
715   }
716 
717   uint32_t listLen = listOfWeakConns->Length();
718   for (uint32_t j = 0; j < listLen;) {
719     RefPtr<HttpConnectionBase> potentialMatch =
720         do_QueryReferent(listOfWeakConns->ElementAt(j));
721     if (!potentialMatch) {
722       // This is a connection that needs to be removed from the list
723       LOG(
724           ("FindCoalescableConnectionByHashKey() found old conn %p that has "
725            "null weak ptr - removing\n",
726            listOfWeakConns->ElementAt(j).get()));
727       if (j != listLen - 1) {
728         listOfWeakConns->Elements()[j] =
729             listOfWeakConns->Elements()[listLen - 1];
730       }
731       listOfWeakConns->RemoveElementAt(listLen - 1);
732       MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
733       listLen--;
734       continue;  // without adjusting iterator
735     }
736 
737     if (aNoHttp3 && potentialMatch->UsingHttp3()) {
738       j++;
739       continue;
740     }
741     bool couldJoin;
742     if (justKidding) {
743       couldJoin =
744           potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
745     } else {
746       couldJoin =
747           potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
748     }
749     if (couldJoin) {
750       LOG(
751           ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
752            "newCI=%s matchedCI=%s join ok\n",
753            potentialMatch.get(), key.get(), ci->HashKey().get(),
754            potentialMatch->ConnectionInfo()->HashKey().get()));
755       return potentialMatch.get();
756     }
757     LOG(
758         ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
759          "newCI=%s matchedCI=%s join failed\n",
760          potentialMatch.get(), key.get(), ci->HashKey().get(),
761          potentialMatch->ConnectionInfo()->HashKey().get()));
762 
763     ++j;  // bypassed by continue when weakptr fails
764   }
765 
766   if (!listLen) {  // shrunk to 0 while iterating
767     LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
768     mCoalescingHash.Remove(key);
769   }
770   return nullptr;
771 }
772 
BuildOriginFrameHashKey(nsACString & newKey,nsHttpConnectionInfo * ci,const nsACString & host,int32_t port)773 static void BuildOriginFrameHashKey(nsACString& newKey,
774                                     nsHttpConnectionInfo* ci,
775                                     const nsACString& host, int32_t port) {
776   newKey.Assign(host);
777   if (ci->GetAnonymous()) {
778     newKey.AppendLiteral("~A:");
779   } else {
780     newKey.AppendLiteral("~.:");
781   }
782   newKey.AppendInt(port);
783   newKey.AppendLiteral("/[");
784   nsAutoCString suffix;
785   ci->GetOriginAttributes().CreateSuffix(suffix);
786   newKey.Append(suffix);
787   newKey.AppendLiteral("]viaORIGIN.FRAME");
788 }
789 
FindCoalescableConnection(nsConnectionEntry * ent,bool justKidding,bool aNoHttp3)790 HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection(
791     nsConnectionEntry* ent, bool justKidding, bool aNoHttp3) {
792   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
793   MOZ_ASSERT(ent->mConnInfo);
794   nsHttpConnectionInfo* ci = ent->mConnInfo;
795   LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
796   // First try and look it up by origin frame
797   nsCString newKey;
798   BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
799   HttpConnectionBase* conn =
800       FindCoalescableConnectionByHashKey(ent, newKey, justKidding, aNoHttp3);
801   if (conn) {
802     LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
803          ci->HashKey().get(), conn, newKey.get()));
804     return conn;
805   }
806 
807   // now check for DNS based keys
808   // deleted conns (null weak pointers) are removed from list
809   uint32_t keyLen = ent->mCoalescingKeys.Length();
810   for (uint32_t i = 0; i < keyLen; ++i) {
811     conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i],
812                                               justKidding, aNoHttp3);
813     if (conn) {
814       LOG(("FindCoalescableConnection(%s) match conn %p on dns key %s\n",
815            ci->HashKey().get(), conn, ent->mCoalescingKeys[i].get()));
816       return conn;
817     }
818   }
819 
820   LOG(("FindCoalescableConnection(%s) no matching conn\n",
821        ci->HashKey().get()));
822   return nullptr;
823 }
824 
UpdateCoalescingForNewConn(HttpConnectionBase * newConn,nsConnectionEntry * ent)825 void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
826     HttpConnectionBase* newConn, nsConnectionEntry* ent) {
827   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
828   MOZ_ASSERT(newConn);
829   MOZ_ASSERT(newConn->ConnectionInfo());
830   MOZ_ASSERT(ent);
831   MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
832 
833   HttpConnectionBase* existingConn =
834       FindCoalescableConnection(ent, true, false);
835   if (existingConn) {
836     LOG(
837         ("UpdateCoalescingForNewConn() found existing active conn that could "
838          "have served newConn "
839          "graceful close of newConn=%p to migrate to existingConn %p\n",
840          newConn, existingConn));
841     newConn->DontReuse();
842     return;
843   }
844 
845   // This connection might go into the mCoalescingHash for new transactions to
846   // be coalesced onto if it can accept new transactions
847   if (!newConn->CanDirectlyActivate()) {
848     return;
849   }
850 
851   uint32_t keyLen = ent->mCoalescingKeys.Length();
852   for (uint32_t i = 0; i < keyLen; ++i) {
853     LOG((
854         "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
855         newConn, newConn->ConnectionInfo()->HashKey().get(),
856         ent->mCoalescingKeys[i].get()));
857     nsTArray<nsWeakPtr>* listOfWeakConns =
858         mCoalescingHash.Get(ent->mCoalescingKeys[i]);
859     if (!listOfWeakConns) {
860       LOG(("UpdateCoalescingForNewConn() need new list element\n"));
861       listOfWeakConns = new nsTArray<nsWeakPtr>(1);
862       mCoalescingHash.Put(ent->mCoalescingKeys[i], listOfWeakConns);
863     }
864     listOfWeakConns->AppendElement(
865         do_GetWeakReference(static_cast<nsISupportsWeakReference*>(newConn)));
866   }
867 
868   // Cancel any other pending connections - their associated transactions
869   // are in the pending queue and will be dispatched onto this new connection
870   for (int32_t index = ent->mHalfOpens.Length() - 1; index >= 0; --index) {
871     RefPtr<nsHalfOpenSocket> half = ent->mHalfOpens[index];
872     LOG(("UpdateCoalescingForNewConn() forcing halfopen abandon %p\n",
873          half.get()));
874     ent->mHalfOpens[index]->Abandon();
875   }
876 
877   if (ent->mActiveConns.Length() > 1) {
878     // this is a new connection that can be coalesced onto. hooray!
879     // if there are other connection to this entry (e.g.
880     // some could still be handshaking, shutting down, etc..) then close
881     // them down after any transactions that are on them are complete.
882     // This probably happened due to the parallel connection algorithm
883     // that is used only before the host is known to speak h2.
884     for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
885       HttpConnectionBase* otherConn = ent->mActiveConns[index];
886       if (otherConn != newConn) {
887         LOG(
888             ("UpdateCoalescingForNewConn() shutting down old connection (%p) "
889              "because new "
890              "spdy connection (%p) takes precedence\n",
891              otherConn, newConn));
892         otherConn->DontReuse();
893       }
894     }
895   }
896 
897   for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
898        --index) {
899     LOG(
900         ("UpdateCoalescingForNewConn() shutting down connection in fast "
901          "open state (%p) because new spdy connection (%p) takes "
902          "precedence\n",
903          ent->mHalfOpenFastOpenBackups[index].get(), newConn));
904     RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
905     half->CancelFastOpenConnection();
906   }
907 }
908 
909 // This function lets a connection, after completing the NPN phase,
910 // report whether or not it is using spdy through the usingSpdy
911 // argument. It would not be necessary if NPN were driven out of
912 // the connection manager. The connection entry associated with the
913 // connection is then updated to indicate whether or not we want to use
914 // spdy with that host and update the coalescing hash
915 // entries used for de-sharding hostsnames.
ReportSpdyConnection(nsHttpConnection * conn,bool usingSpdy)916 void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn,
917                                                bool usingSpdy) {
918   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
919   if (!conn->ConnectionInfo()) {
920     return;
921   }
922   nsConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
923   if (!ent || !usingSpdy) {
924     return;
925   }
926 
927   ent->mUsingSpdy = true;
928   mNumSpdyHttp3ActiveConns++;
929 
930   // adjust timeout timer
931   uint32_t ttl = conn->TimeToLive();
932   uint64_t timeOfExpire = NowInSeconds() + ttl;
933   if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
934     PruneDeadConnectionsAfter(ttl);
935   }
936 
937   UpdateCoalescingForNewConn(conn, ent);
938 
939   nsresult rv = ProcessPendingQ(ent->mConnInfo);
940   if (NS_FAILED(rv)) {
941     LOG(
942         ("ReportSpdyConnection conn=%p ent=%p "
943          "failed to process pending queue (%08x)\n",
944          conn, ent, static_cast<uint32_t>(rv)));
945   }
946   rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
947   if (NS_FAILED(rv)) {
948     LOG(
949         ("ReportSpdyConnection conn=%p ent=%p "
950          "failed to post event (%08x)\n",
951          conn, ent, static_cast<uint32_t>(rv)));
952   }
953 }
954 
955 //-----------------------------------------------------------------------------
DispatchPendingQ(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>> & pendingQ,nsConnectionEntry * ent,bool considerAll)956 bool nsHttpConnectionMgr::DispatchPendingQ(
957     nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>>& pendingQ,
958     nsConnectionEntry* ent, bool considerAll) {
959   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
960 
961   PendingTransactionInfo* pendingTransInfo = nullptr;
962   nsresult rv;
963   bool dispatchedSuccessfully = false;
964 
965   // if !considerAll iterate the pending list until one is dispatched
966   // successfully. Keep iterating afterwards only until a transaction fails to
967   // dispatch. if considerAll == true then try and dispatch all items.
968   for (uint32_t i = 0; i < pendingQ.Length();) {
969     pendingTransInfo = pendingQ[i];
970     LOG((
971         "nsHttpConnectionMgr::DispatchPendingQ "
972         "[trans=%p, halfOpen=%p, activeConn=%p]\n",
973         pendingTransInfo->mTransaction.get(), pendingTransInfo->mHalfOpen.get(),
974         pendingTransInfo->mActiveConn.get()));
975 
976     // When this transaction has already established a half-open
977     // connection, we want to prevent any duplicate half-open
978     // connections from being established and bound to this
979     // transaction. Allow only use of an idle persistent connection
980     // (if found) for transactions referred by a half-open connection.
981     bool alreadyHalfOpenOrWaitingForTLS = false;
982     if (pendingTransInfo->mHalfOpen) {
983       MOZ_ASSERT(!pendingTransInfo->mActiveConn);
984       RefPtr<nsHalfOpenSocket> halfOpen =
985           do_QueryReferent(pendingTransInfo->mHalfOpen);
986       LOG(
987           ("nsHttpConnectionMgr::DispatchPendingQ "
988            "[trans=%p, halfOpen=%p]\n",
989            pendingTransInfo->mTransaction.get(), halfOpen.get()));
990       if (halfOpen) {
991         alreadyHalfOpenOrWaitingForTLS = true;
992       } else {
993         // If we have not found the halfOpen socket, remove the pointer.
994         pendingTransInfo->mHalfOpen = nullptr;
995       }
996     } else if (pendingTransInfo->mActiveConn) {
997       MOZ_ASSERT(!pendingTransInfo->mHalfOpen);
998       RefPtr<HttpConnectionBase> activeConn =
999           do_QueryReferent(pendingTransInfo->mActiveConn);
1000       LOG(
1001           ("nsHttpConnectionMgr::DispatchPendingQ "
1002            "[trans=%p, activeConn=%p]\n",
1003            pendingTransInfo->mTransaction.get(), activeConn.get()));
1004       // Check if this transaction claimed a connection that is still
1005       // performing tls handshake with a NullHttpTransaction or it is between
1006       // finishing tls and reclaiming (When nullTrans finishes tls handshake,
1007       // httpConnection does not have a transaction any more and a
1008       // ReclaimConnection is dispatched). But if an error occurred the
1009       // connection will be closed, it will exist but CanReused will be
1010       // false.
1011       if (activeConn &&
1012           ((activeConn->Transaction() &&
1013             activeConn->Transaction()->IsNullTransaction()) ||
1014            (!activeConn->Transaction() && activeConn->CanReuse()))) {
1015         alreadyHalfOpenOrWaitingForTLS = true;
1016       } else {
1017         // If we have not found the connection, remove the pointer.
1018         pendingTransInfo->mActiveConn = nullptr;
1019       }
1020     }
1021 
1022     rv = TryDispatchTransaction(
1023         ent,
1024         alreadyHalfOpenOrWaitingForTLS ||
1025             !!pendingTransInfo->mTransaction->TunnelProvider(),
1026         pendingTransInfo);
1027     if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
1028       if (NS_SUCCEEDED(rv)) {
1029         LOG(("  dispatching pending transaction...\n"));
1030       } else {
1031         LOG(
1032             ("  removing pending transaction based on "
1033              "TryDispatchTransaction returning hard error %" PRIx32 "\n",
1034              static_cast<uint32_t>(rv)));
1035       }
1036       ReleaseClaimedSockets(ent, pendingTransInfo);
1037       if (pendingQ.RemoveElement(pendingTransInfo)) {
1038         // pendingTransInfo is now potentially destroyed
1039         dispatchedSuccessfully = true;
1040         continue;  // dont ++i as we just made the array shorter
1041       }
1042 
1043       LOG(("  transaction not found in pending queue\n"));
1044     }
1045 
1046     if (dispatchedSuccessfully && !considerAll) break;
1047 
1048     ++i;
1049   }
1050   return dispatchedSuccessfully;
1051 }
1052 
TotalActiveConnections(nsConnectionEntry * ent) const1053 uint32_t nsHttpConnectionMgr::TotalActiveConnections(
1054     nsConnectionEntry* ent) const {
1055   // Add in the in-progress tcp connections, we will assume they are
1056   // keepalive enabled.
1057   // Exclude half-open's that has already created a usable connection.
1058   // This prevents the limit being stuck on ipv6 connections that
1059   // eventually time out after typical 21 seconds of no ACK+SYN reply.
1060   return ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
1061 }
1062 
MaxPersistConnections(nsConnectionEntry * ent) const1063 uint32_t nsHttpConnectionMgr::MaxPersistConnections(
1064     nsConnectionEntry* ent) const {
1065   if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
1066     return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
1067   }
1068 
1069   return static_cast<uint32_t>(mMaxPersistConnsPerHost);
1070 }
1071 
PreparePendingQForDispatching(nsConnectionEntry * ent,nsTArray<RefPtr<PendingTransactionInfo>> & pendingQ,bool considerAll)1072 void nsHttpConnectionMgr::PreparePendingQForDispatching(
1073     nsConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
1074     bool considerAll) {
1075   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1076 
1077   pendingQ.Clear();
1078 
1079   uint32_t totalCount = TotalActiveConnections(ent);
1080   uint32_t maxPersistConns = MaxPersistConnections(ent);
1081   uint32_t availableConnections =
1082       maxPersistConns > totalCount ? maxPersistConns - totalCount : 0;
1083 
1084   // No need to try dispatching if we reach the active connection limit.
1085   if (!availableConnections) {
1086     return;
1087   }
1088 
1089   // Only have to get transactions from the queue whose window id is 0.
1090   if (!gHttpHandler->ActiveTabPriority()) {
1091     ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
1092     return;
1093   }
1094 
1095   uint32_t maxFocusedWindowConnections =
1096       availableConnections * gHttpHandler->FocusedWindowTransactionRatio();
1097   MOZ_ASSERT(maxFocusedWindowConnections < availableConnections);
1098 
1099   if (!maxFocusedWindowConnections) {
1100     maxFocusedWindowConnections = 1;
1101   }
1102 
1103   // Only need to dispatch transactions for either focused or
1104   // non-focused window because considerAll is false.
1105   if (!considerAll) {
1106     ent->AppendPendingQForFocusedWindow(mCurrentTopLevelOuterContentWindowId,
1107                                         pendingQ, maxFocusedWindowConnections);
1108 
1109     if (pendingQ.IsEmpty()) {
1110       ent->AppendPendingQForNonFocusedWindows(
1111           mCurrentTopLevelOuterContentWindowId, pendingQ, availableConnections);
1112     }
1113     return;
1114   }
1115 
1116   uint32_t maxNonFocusedWindowConnections =
1117       availableConnections - maxFocusedWindowConnections;
1118   nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
1119 
1120   ent->AppendPendingQForFocusedWindow(mCurrentTopLevelOuterContentWindowId,
1121                                       pendingQ, maxFocusedWindowConnections);
1122 
1123   if (maxNonFocusedWindowConnections) {
1124     ent->AppendPendingQForNonFocusedWindows(
1125         mCurrentTopLevelOuterContentWindowId, remainingPendingQ,
1126         maxNonFocusedWindowConnections);
1127   }
1128 
1129   // If the slots for either focused or non-focused window are not filled up
1130   // to the availability, try to use the remaining available connections
1131   // for the other slot (with preference for the focused window).
1132   if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
1133     ent->AppendPendingQForFocusedWindow(
1134         mCurrentTopLevelOuterContentWindowId, pendingQ,
1135         maxNonFocusedWindowConnections - remainingPendingQ.Length());
1136   } else if (pendingQ.Length() < maxFocusedWindowConnections) {
1137     ent->AppendPendingQForNonFocusedWindows(
1138         mCurrentTopLevelOuterContentWindowId, remainingPendingQ,
1139         maxFocusedWindowConnections - pendingQ.Length());
1140   }
1141 
1142   MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <=
1143              availableConnections);
1144 
1145   LOG(
1146       ("nsHttpConnectionMgr::PreparePendingQForDispatching "
1147        "focused window pendingQ.Length()=%zu"
1148        ", remainingPendingQ.Length()=%zu\n",
1149        pendingQ.Length(), remainingPendingQ.Length()));
1150 
1151   // Append elements in |remainingPendingQ| to |pendingQ|. The order in
1152   // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
1153   pendingQ.AppendElements(std::move(remainingPendingQ));
1154 }
1155 
ProcessPendingQForEntry(nsConnectionEntry * ent,bool considerAll)1156 bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry* ent,
1157                                                   bool considerAll) {
1158   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1159 
1160   LOG(
1161       ("nsHttpConnectionMgr::ProcessPendingQForEntry "
1162        "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
1163        " queued=%zu]\n",
1164        ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
1165        ent->mIdleConns.Length(), ent->mUrgentStartQ.Length(),
1166        ent->PendingQLength()));
1167 
1168   if (LOG_ENABLED()) {
1169     LOG(("urgent queue ["));
1170     for (const auto& info : ent->mUrgentStartQ) {
1171       LOG(("  %p", info->mTransaction.get()));
1172     }
1173     for (auto it = ent->mPendingTransactionTable.Iter(); !it.Done();
1174          it.Next()) {
1175       LOG(("] window id = %" PRIx64 " queue [", it.Key()));
1176       for (const auto& info : *it.UserData()) {
1177         LOG(("  %p", info->mTransaction.get()));
1178       }
1179     }
1180     if (!ent->mConnInfo->IsHttp3()) {
1181       LOG(("] active urgent conns ["));
1182       for (HttpConnectionBase* conn : ent->mActiveConns) {
1183         RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
1184         MOZ_ASSERT(connTCP);
1185         if (connTCP->IsUrgentStartPreferred()) {
1186           LOG(("  %p", conn));
1187         }
1188       }
1189       LOG(("] active regular conns ["));
1190       for (HttpConnectionBase* conn : ent->mActiveConns) {
1191         RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
1192         MOZ_ASSERT(connTCP);
1193         if (!connTCP->IsUrgentStartPreferred()) {
1194           LOG(("  %p", conn));
1195         }
1196       }
1197 
1198       LOG(("] idle urgent conns ["));
1199       for (nsHttpConnection* conn : ent->mIdleConns) {
1200         if (conn->IsUrgentStartPreferred()) {
1201           LOG(("  %p", conn));
1202         }
1203       }
1204       LOG(("] idle regular conns ["));
1205       for (nsHttpConnection* conn : ent->mIdleConns) {
1206         if (!conn->IsUrgentStartPreferred()) {
1207           LOG(("  %p", conn));
1208         }
1209       }
1210     } else {
1211       for (HttpConnectionBase* conn : ent->mActiveConns) {
1212         LOG(("  %p", conn));
1213       }
1214       MOZ_ASSERT(ent->mIdleConns.Length() == 0);
1215     }
1216     LOG(("]"));
1217   }
1218 
1219   if (!ent->mUrgentStartQ.Length() && !ent->PendingQLength()) {
1220     return false;
1221   }
1222   ProcessSpdyPendingQ(ent);
1223 
1224   bool dispatchedSuccessfully = false;
1225 
1226   if (!ent->mUrgentStartQ.IsEmpty()) {
1227     dispatchedSuccessfully =
1228         DispatchPendingQ(ent->mUrgentStartQ, ent, considerAll);
1229   }
1230 
1231   if (dispatchedSuccessfully && !considerAll) {
1232     return dispatchedSuccessfully;
1233   }
1234 
1235   nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
1236   PreparePendingQForDispatching(ent, pendingQ, considerAll);
1237 
1238   // The only case that |pendingQ| is empty is when there is no
1239   // connection available for dispatching.
1240   if (pendingQ.IsEmpty()) {
1241     return dispatchedSuccessfully;
1242   }
1243 
1244   dispatchedSuccessfully |= DispatchPendingQ(pendingQ, ent, considerAll);
1245 
1246   // Put the leftovers into connection entry, in the same order as they
1247   // were before to keep the natural ordering.
1248   for (const auto& transactionInfo : Reversed(pendingQ)) {
1249     ent->InsertTransaction(transactionInfo, true);
1250   }
1251 
1252   // Only remove empty pendingQ when considerAll is true.
1253   if (considerAll) {
1254     ent->RemoveEmptyPendingQ();
1255   }
1256 
1257   return dispatchedSuccessfully;
1258 }
1259 
ProcessPendingQForEntry(nsHttpConnectionInfo * ci)1260 bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) {
1261   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1262 
1263   nsConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
1264   if (ent) return ProcessPendingQForEntry(ent, false);
1265   return false;
1266 }
1267 
1268 // we're at the active connection limit if any one of the following conditions
1269 // is true:
1270 //  (1) at max-connections
1271 //  (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
1272 //  (3) keep-alive disabled and at max-connections-per-server
AtActiveConnectionLimit(nsConnectionEntry * ent,uint32_t caps)1273 bool nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry* ent,
1274                                                   uint32_t caps) {
1275   nsHttpConnectionInfo* ci = ent->mConnInfo;
1276   uint32_t totalCount = TotalActiveConnections(ent);
1277 
1278   if (ci->IsHttp3()) {
1279     return totalCount > 0;
1280   }
1281 
1282   uint32_t maxPersistConns = MaxPersistConnections(ent);
1283 
1284   LOG(
1285       ("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
1286        "totalCount=%u, maxPersistConns=%u]\n",
1287        ci->HashKey().get(), caps, totalCount, maxPersistConns));
1288 
1289   if (caps & NS_HTTP_URGENT_START) {
1290     if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
1291       LOG((
1292           "The number of total connections are greater than or equal to sum of "
1293           "max urgent-start queue length and the number of max persistent "
1294           "connections.\n"));
1295       return true;
1296     }
1297     return false;
1298   }
1299 
1300   // update maxconns if potentially limited by the max socket count
1301   // this requires a dynamic reduction in the max socket count to a point
1302   // lower than the max-connections pref.
1303   uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
1304   if (mMaxConns > maxSocketCount) {
1305     mMaxConns = maxSocketCount;
1306     LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
1307          mMaxConns));
1308   }
1309 
1310   // If there are more active connections than the global limit, then we're
1311   // done. Purging idle connections won't get us below it.
1312   if (mNumActiveConns >= mMaxConns) {
1313     LOG(("  num active conns == max conns\n"));
1314     return true;
1315   }
1316 
1317   bool result = (totalCount >= maxPersistConns);
1318   LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
1319   return result;
1320 }
1321 
ClosePersistentConnections(nsConnectionEntry * ent)1322 void nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry* ent) {
1323   LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
1324        ent->mConnInfo->HashKey().get()));
1325   while (ent->mIdleConns.Length()) {
1326     RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
1327     ent->mIdleConns.RemoveElementAt(0);
1328     mNumIdleConns--;
1329     conn->Close(NS_ERROR_ABORT);
1330   }
1331 
1332   int32_t activeCount = ent->mActiveConns.Length();
1333   for (int32_t i = 0; i < activeCount; i++) ent->mActiveConns[i]->DontReuse();
1334   for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
1335        --index) {
1336     RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
1337     half->CancelFastOpenConnection();
1338   }
1339 }
1340 
RestrictConnections(nsConnectionEntry * ent)1341 bool nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry* ent) {
1342   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1343 
1344   if (ent->AvailableForDispatchNow()) {
1345     // this might be a h2/spdy connection in this connection entry that
1346     // is able to be immediately muxxed, or it might be one that
1347     // was found in the same state through a coalescing hash
1348     LOG(
1349         ("nsHttpConnectionMgr::RestrictConnections %p %s restricted due to "
1350          "active >=h2\n",
1351          ent, ent->mConnInfo->HashKey().get()));
1352     return true;
1353   }
1354 
1355   // If this host is trying to negotiate a SPDY session right now,
1356   // don't create any new ssl connections until the result of the
1357   // negotiation is known.
1358 
1359   bool doRestrict = ent->mConnInfo->FirstHopSSL() &&
1360                     gHttpHandler->IsSpdyEnabled() && ent->mUsingSpdy &&
1361                     (ent->mHalfOpens.Length() || ent->mActiveConns.Length());
1362 
1363   // If there are no restrictions, we are done
1364   if (!doRestrict) return false;
1365 
1366   // If the restriction is based on a tcp handshake in progress
1367   // let that connect and then see if it was SPDY or not
1368   if (ent->UnconnectedHalfOpens()) {
1369     return true;
1370   }
1371 
1372   // There is a concern that a host is using a mix of HTTP/1 and SPDY.
1373   // In that case we don't want to restrict connections just because
1374   // there is a single active HTTP/1 session in use.
1375   if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
1376     bool confirmedRestrict = false;
1377     for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
1378       HttpConnectionBase* conn = ent->mActiveConns[index];
1379       RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
1380       if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) {
1381         confirmedRestrict = true;
1382         break;
1383       }
1384     }
1385     doRestrict = confirmedRestrict;
1386     if (!confirmedRestrict) {
1387       LOG(
1388           ("nsHttpConnectionMgr spdy connection restriction to "
1389            "%s bypassed.\n",
1390            ent->mConnInfo->Origin()));
1391     }
1392   }
1393   return doRestrict;
1394 }
1395 
1396 // returns NS_OK if a connection was started
1397 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
1398 //        ephemeral limits
1399 // returns other NS_ERROR on hard failure conditions
MakeNewConnection(nsConnectionEntry * ent,PendingTransactionInfo * pendingTransInfo)1400 nsresult nsHttpConnectionMgr::MakeNewConnection(
1401     nsConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
1402   nsHttpTransaction* trans = pendingTransInfo->mTransaction;
1403 
1404   LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent,
1405        trans));
1406   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1407 
1408   uint32_t halfOpenLength = ent->mHalfOpens.Length();
1409   for (uint32_t i = 0; i < halfOpenLength; i++) {
1410     auto halfOpen = ent->mHalfOpens[i];
1411     if (halfOpen->AcceptsTransaction(trans) && halfOpen->Claim()) {
1412       // We've found a speculative connection or a connection that
1413       // is free to be used in the half open list.
1414       // A free to be used connection is a connection that was
1415       // open for a concrete transaction, but that trunsaction
1416       // ended up using another connection.
1417       LOG(
1418           ("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
1419            "Found a speculative or a free-to-use half open connection\n",
1420            ent->mConnInfo->HashKey().get()));
1421       pendingTransInfo->mHalfOpen = do_GetWeakReference(
1422           static_cast<nsISupportsWeakReference*>(ent->mHalfOpens[i]));
1423       // return OK because we have essentially opened a new connection
1424       // by converting a speculative half-open to general use
1425       return NS_OK;
1426     }
1427   }
1428 
1429   // consider null transactions that are being used to drive the ssl handshake
1430   // if the transaction creating this connection can re-use persistent
1431   // connections
1432   if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
1433     uint32_t activeLength = ent->mActiveConns.Length();
1434     for (uint32_t i = 0; i < activeLength; i++) {
1435       nsAHttpTransaction* activeTrans = ent->mActiveConns[i]->Transaction();
1436       NullHttpTransaction* nullTrans =
1437           activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
1438       if (nullTrans && nullTrans->Claim()) {
1439         LOG(
1440             ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
1441              "Claiming a null transaction for later use\n",
1442              ent->mConnInfo->HashKey().get()));
1443         pendingTransInfo->mActiveConn = do_GetWeakReference(
1444             static_cast<nsISupportsWeakReference*>(ent->mActiveConns[i]));
1445         return NS_OK;
1446       }
1447     }
1448   }
1449 
1450   // If this host is trying to negotiate a SPDY session right now,
1451   // don't create any new connections until the result of the
1452   // negotiation is known.
1453   if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
1454       (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && RestrictConnections(ent)) {
1455     LOG(
1456         ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
1457          "Not Available Due to RestrictConnections()\n",
1458          ent->mConnInfo->HashKey().get()));
1459     return NS_ERROR_NOT_AVAILABLE;
1460   }
1461 
1462   // We need to make a new connection. If that is going to exceed the
1463   // global connection limit then try and free up some room by closing
1464   // an idle connection to another host. We know it won't select "ent"
1465   // because we have already determined there are no idle connections
1466   // to our destination
1467 
1468   if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
1469     // If the global number of connections is preventing the opening of new
1470     // connections to a host without idle connections, then close them
1471     // regardless of their TTL.
1472     auto iter = mCT.Iter();
1473     while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) {
1474       RefPtr<nsConnectionEntry> entry = iter.Data();
1475       if (!entry->mIdleConns.Length()) {
1476         iter.Next();
1477         continue;
1478       }
1479       RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
1480       entry->mIdleConns.RemoveElementAt(0);
1481       conn->Close(NS_ERROR_ABORT);
1482       mNumIdleConns--;
1483       ConditionallyStopPruneDeadConnectionsTimer();
1484     }
1485   }
1486 
1487   if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns &&
1488       gHttpHandler->IsSpdyEnabled()) {
1489     // If the global number of connections is preventing the opening of new
1490     // connections to a host without idle connections, then close any spdy
1491     // ASAP.
1492     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
1493       RefPtr<nsConnectionEntry> entry = iter.Data();
1494       if (!entry->mUsingSpdy) {
1495         continue;
1496       }
1497 
1498       for (uint32_t index = 0; index < entry->mActiveConns.Length(); ++index) {
1499         HttpConnectionBase* conn = entry->mActiveConns[index];
1500         if (conn->UsingSpdy() && conn->CanReuse()) {
1501           conn->DontReuse();
1502           // Stop on <= (particularly =) because this dontreuse
1503           // causes async close.
1504           if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
1505             goto outerLoopEnd;
1506           }
1507         }
1508       }
1509     }
1510   outerLoopEnd:;
1511   }
1512 
1513   if (AtActiveConnectionLimit(ent, trans->Caps()))
1514     return NS_ERROR_NOT_AVAILABLE;
1515 
1516   nsresult rv =
1517       CreateTransport(ent, trans, trans->Caps(), false, false,
1518                       trans->ClassOfService() & nsIClassOfService::UrgentStart,
1519                       true, pendingTransInfo);
1520   if (NS_FAILED(rv)) {
1521     /* hard failure */
1522     LOG(
1523         ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
1524          "CreateTransport() hard failure.\n",
1525          ent->mConnInfo->HashKey().get(), trans));
1526     trans->Close(rv);
1527     if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE;
1528     return rv;
1529   }
1530 
1531   return NS_OK;
1532 }
1533 
1534 // returns OK if a connection is found for the transaction
1535 //   and the transaction is started.
1536 // returns ERROR_NOT_AVAILABLE if no connection can be found and it
1537 //   should be queued until circumstances change
1538 // returns other ERROR when transaction has a hard failure and should
1539 //   not remain in the pending queue
TryDispatchTransaction(nsConnectionEntry * ent,bool onlyReusedConnection,PendingTransactionInfo * pendingTransInfo)1540 nsresult nsHttpConnectionMgr::TryDispatchTransaction(
1541     nsConnectionEntry* ent, bool onlyReusedConnection,
1542     PendingTransactionInfo* pendingTransInfo) {
1543   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1544 
1545   nsHttpTransaction* trans = pendingTransInfo->mTransaction;
1546 
1547   LOG(
1548       ("nsHttpConnectionMgr::TryDispatchTransaction without conn "
1549        "[trans=%p halfOpen=%p conn=%p ci=%p ci=%s caps=%x tunnelprovider=%p "
1550        "onlyreused=%d active=%zu idle=%zu]\n",
1551        trans, pendingTransInfo->mHalfOpen.get(),
1552        pendingTransInfo->mActiveConn.get(), ent->mConnInfo.get(),
1553        ent->mConnInfo->HashKey().get(), uint32_t(trans->Caps()),
1554        trans->TunnelProvider(), onlyReusedConnection,
1555        ent->mActiveConns.Length(), ent->mIdleConns.Length()));
1556 
1557   uint32_t caps = trans->Caps();
1558 
1559   // 0 - If this should use spdy then dispatch it post haste.
1560   // 1 - If there is connection pressure then see if we can pipeline this on
1561   //     a connection of a matching type instead of using a new conn
1562   // 2 - If there is an idle connection, use it!
1563   // 3 - if class == reval or script and there is an open conn of that type
1564   //     then pipeline onto shortest pipeline of that class if limits allow
1565   // 4 - If we aren't up against our connection limit,
1566   //     then open a new one
1567   // 5 - Try a pipeline if we haven't already - this will be unusual because
1568   //     it implies a low connection pressure situation where
1569   //     MakeNewConnection() failed.. that is possible, but unlikely, due to
1570   //     global limits
1571   // 6 - no connection is available - queue it
1572 
1573   RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection;
1574 
1575   // step 0
1576   // look for existing spdy connection - that's always best because it is
1577   // essentially pipelining without head of line blocking
1578 
1579   if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
1580     RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
1581         ent,
1582         (!gHttpHandler->IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
1583     if (conn) {
1584       if (trans->IsWebsocketUpgrade() && !conn->CanAcceptWebsocket()) {
1585         // This is a websocket transaction and we already have a h2 connection
1586         // that do not support websockets, we should disable h2 for this
1587         // transaction.
1588         trans->DisableSpdy();
1589         caps &= NS_HTTP_DISALLOW_SPDY;
1590       } else {
1591         if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
1592             (caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) ||
1593             !conn->IsExperienced()) {
1594           LOG(("   dispatch to spdy: [conn=%p]\n", conn.get()));
1595           trans->RemoveDispatchedAsBlocking(); /* just in case */
1596           nsresult rv = DispatchTransaction(ent, trans, conn);
1597           NS_ENSURE_SUCCESS(rv, rv);
1598           return NS_OK;
1599         }
1600         unusedSpdyPersistentConnection = conn;
1601       }
1602     }
1603   }
1604 
1605   // If this is not a blocking transaction and the request context for it is
1606   // currently processing one or more blocking transactions then we
1607   // need to just leave it in the queue until those are complete unless it is
1608   // explicitly marked as unblocked.
1609   if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
1610     if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
1611       nsIRequestContext* requestContext = trans->RequestContext();
1612       if (requestContext) {
1613         uint32_t blockers = 0;
1614         if (NS_SUCCEEDED(
1615                 requestContext->GetBlockingTransactionCount(&blockers)) &&
1616             blockers) {
1617           // need to wait for blockers to clear
1618           LOG(("   blocked by request context: [rc=%p trans=%p blockers=%d]\n",
1619                requestContext, trans, blockers));
1620           return NS_ERROR_NOT_AVAILABLE;
1621         }
1622       }
1623     }
1624   } else {
1625     // Mark the transaction and its load group as blocking right now to prevent
1626     // other transactions from being reordered in the queue due to slow syns.
1627     trans->DispatchedAsBlocking();
1628   }
1629 
1630   // step 1
1631   // If connection pressure, then we want to favor pipelining of any kind
1632   // h1 pipelining has been removed
1633 
1634   // Subject most transactions at high parallelism to rate pacing.
1635   // It will only be actually submitted to the
1636   // token bucket once, and if possible it is granted admission synchronously.
1637   // It is important to leave a transaction in the pending queue when blocked by
1638   // pacing so it can be found on cancel if necessary.
1639   // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
1640   // limited.
1641   if (gHttpHandler->UseRequestTokenBucket()) {
1642     // submit even whitelisted transactions to the token bucket though they will
1643     // not be slowed by it
1644     bool runNow = trans->TryToRunPacedRequest();
1645     if (!runNow) {
1646       if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <=
1647           gHttpHandler->RequestTokenBucketMinParallelism()) {
1648         runNow = true;  // white list it
1649       } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
1650         runNow = true;  // white list it
1651       }
1652     }
1653     if (!runNow) {
1654       LOG(("   blocked due to rate pacing trans=%p\n", trans));
1655       return NS_ERROR_NOT_AVAILABLE;
1656     }
1657   }
1658 
1659   // step 2
1660   // consider an idle persistent connection
1661   bool idleConnsAllUrgent = false;
1662   if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
1663     nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, true,
1664                                                    &idleConnsAllUrgent);
1665     if (NS_SUCCEEDED(rv)) {
1666       LOG(("   dispatched step 2 (idle) trans=%p\n", trans));
1667       return NS_OK;
1668     }
1669   }
1670 
1671   // step 3
1672   // consider pipelining scripts and revalidations
1673   // h1 pipelining has been removed
1674 
1675   // step 4
1676   if (!onlyReusedConnection) {
1677     nsresult rv = MakeNewConnection(ent, pendingTransInfo);
1678     if (NS_SUCCEEDED(rv)) {
1679       // this function returns NOT_AVAILABLE for asynchronous connects
1680       LOG(("   dispatched step 4 (async new conn) trans=%p\n", trans));
1681       return NS_ERROR_NOT_AVAILABLE;
1682     }
1683 
1684     if (rv != NS_ERROR_NOT_AVAILABLE) {
1685       // not available return codes should try next step as they are
1686       // not hard errors. Other codes should stop now
1687       LOG(("   failed step 4 (%" PRIx32 ") trans=%p\n",
1688            static_cast<uint32_t>(rv), trans));
1689       return rv;
1690     }
1691 
1692     // repeat step 2 when there are only idle connections and all are urgent,
1693     // don't respect urgency so that non-urgent transaction will be allowed
1694     // to dispatch on an urgent-start-only marked connection to avoid
1695     // dispatch deadlocks
1696     if (!(trans->ClassOfService() & nsIClassOfService::UrgentStart) &&
1697         idleConnsAllUrgent &&
1698         ent->mActiveConns.Length() < MaxPersistConnections(ent)) {
1699       rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false);
1700       if (NS_SUCCEEDED(rv)) {
1701         LOG(("   dispatched step 2a (idle, reuse urgent) trans=%p\n", trans));
1702         return NS_OK;
1703       }
1704     }
1705   } else if (trans->TunnelProvider() &&
1706              trans->TunnelProvider()->MaybeReTunnel(trans)) {
1707     LOG(("   sort of dispatched step 4a tunnel requeue trans=%p\n", trans));
1708     // the tunnel provider took responsibility for making a new tunnel
1709     return NS_OK;
1710   }
1711 
1712   // step 5
1713   // previously pipelined anything here if allowed but h1 pipelining has been
1714   // removed
1715 
1716   // step 6
1717   if (unusedSpdyPersistentConnection) {
1718     // to avoid deadlocks, we need to throw away this perfectly valid SPDY
1719     // connection to make room for a new one that can service a no KEEPALIVE
1720     // request
1721     unusedSpdyPersistentConnection->DontReuse();
1722   }
1723 
1724   LOG(("   not dispatched (queued) trans=%p\n", trans));
1725   return NS_ERROR_NOT_AVAILABLE; /* queue it */
1726 }
1727 
TryDispatchTransactionOnIdleConn(nsConnectionEntry * ent,PendingTransactionInfo * pendingTransInfo,bool respectUrgency,bool * allUrgent)1728 nsresult nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn(
1729     nsConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
1730     bool respectUrgency, bool* allUrgent) {
1731   bool onlyUrgent = !!ent->mIdleConns.Length();
1732 
1733   nsHttpTransaction* trans = pendingTransInfo->mTransaction;
1734   bool urgentTrans = trans->ClassOfService() & nsIClassOfService::UrgentStart;
1735 
1736   LOG(
1737       ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
1738        "trans=%p, urgent=%d",
1739        ent, trans, urgentTrans));
1740 
1741   RefPtr<nsHttpConnection> conn;
1742   size_t index = 0;
1743   while (!conn && (ent->mIdleConns.Length() > index)) {
1744     conn = ent->mIdleConns[index];
1745 
1746     // non-urgent transactions can only be dispatched on non-urgent
1747     // started or used connections.
1748     if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
1749       LOG(("  skipping urgent: [conn=%p]", conn.get()));
1750       conn = nullptr;
1751       ++index;
1752       continue;
1753     }
1754 
1755     onlyUrgent = false;
1756 
1757     ent->mIdleConns.RemoveElementAt(index);
1758     mNumIdleConns--;
1759 
1760     // we check if the connection can be reused before even checking if
1761     // it is a "matching" connection.
1762     if (!conn->CanReuse()) {
1763       LOG(("   dropping stale connection: [conn=%p]\n", conn.get()));
1764       conn->Close(NS_ERROR_ABORT);
1765       conn = nullptr;
1766     } else {
1767       LOG(("   reusing connection: [conn=%p]\n", conn.get()));
1768       conn->EndIdleMonitoring();
1769     }
1770 
1771     // If there are no idle connections left at all, we need to make
1772     // sure that we are not pruning dead connections anymore.
1773     ConditionallyStopPruneDeadConnectionsTimer();
1774   }
1775 
1776   if (allUrgent) {
1777     *allUrgent = onlyUrgent;
1778   }
1779 
1780   if (conn) {
1781     // This will update the class of the connection to be the class of
1782     // the transaction dispatched on it.
1783     AddActiveConn(conn, ent);
1784     nsresult rv = DispatchTransaction(ent, trans, conn);
1785     NS_ENSURE_SUCCESS(rv, rv);
1786 
1787     return NS_OK;
1788   }
1789 
1790   return NS_ERROR_NOT_AVAILABLE;
1791 }
1792 
DispatchTransaction(nsConnectionEntry * ent,nsHttpTransaction * trans,HttpConnectionBase * conn)1793 nsresult nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry* ent,
1794                                                   nsHttpTransaction* trans,
1795                                                   HttpConnectionBase* conn) {
1796   uint32_t caps = trans->Caps();
1797   int32_t priority = trans->Priority();
1798   nsresult rv;
1799 
1800   LOG(
1801       ("nsHttpConnectionMgr::DispatchTransaction "
1802        "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
1803        "isHttp3=%d]\n",
1804        ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority,
1805        conn->UsingSpdy(), conn->UsingHttp3()));
1806 
1807   // It is possible for a rate-paced transaction to be dispatched independent
1808   // of the token bucket when the amount of parallelization has changed or
1809   // when a muxed connection (e.g. h2) becomes available.
1810   trans->CancelPacing(NS_OK);
1811 
1812   if (conn->UsingSpdy() || conn->UsingHttp3()) {
1813     LOG(
1814         ("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
1815          "Connection host = %s\n",
1816          trans->ConnectionInfo()->Origin(), conn->ConnectionInfo()->Origin()));
1817     rv = conn->Activate(trans, caps, priority);
1818     MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
1819     if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
1820       if (conn->UsingSpdy()) {
1821         AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
1822                             trans->GetPendingTime(), TimeStamp::Now());
1823       } else {
1824         AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3,
1825                             trans->GetPendingTime(), TimeStamp::Now());
1826       }
1827       trans->SetPendingTime(false);
1828     }
1829     return rv;
1830   }
1831 
1832   MOZ_ASSERT(conn && !conn->Transaction(),
1833              "DispatchTranaction() on non spdy active connection");
1834 
1835   rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
1836 
1837   if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
1838     AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
1839                         trans->GetPendingTime(), TimeStamp::Now());
1840     trans->SetPendingTime(false);
1841   }
1842   return rv;
1843 }
1844 
1845 //-----------------------------------------------------------------------------
1846 // ConnectionHandle
1847 //
1848 // thin wrapper around a real connection, used to keep track of references
1849 // to the connection to determine when the connection may be reused.  the
1850 // transaction owns a reference to this handle.  this extra
1851 // layer of indirection greatly simplifies consumer code, avoiding the
1852 // need for consumer code to know when to give the connection back to the
1853 // connection manager.
1854 //
1855 class ConnectionHandle : public nsAHttpConnection {
1856  public:
1857   NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSAHTTPCONNECTION(mConn)1858   NS_DECL_NSAHTTPCONNECTION(mConn)
1859 
1860   explicit ConnectionHandle(HttpConnectionBase* conn) : mConn(conn) {}
Reset()1861   void Reset() { mConn = nullptr; }
1862 
1863  private:
1864   virtual ~ConnectionHandle();
1865   RefPtr<HttpConnectionBase> mConn;
1866 };
1867 
MakeConnectionHandle(HttpConnectionBase * aWrapped)1868 nsAHttpConnection* nsHttpConnectionMgr::MakeConnectionHandle(
1869     HttpConnectionBase* aWrapped) {
1870   return new ConnectionHandle(aWrapped);
1871 }
1872 
~ConnectionHandle()1873 ConnectionHandle::~ConnectionHandle() {
1874   if (mConn) {
1875     nsresult rv = gHttpHandler->ReclaimConnection(mConn);
1876     if (NS_FAILED(rv)) {
1877       LOG(
1878           ("ConnectionHandle::~ConnectionHandle\n"
1879            "    failed to reclaim connection\n"));
1880     }
1881   }
1882 }
1883 
NS_IMPL_ISUPPORTS0(ConnectionHandle)1884 NS_IMPL_ISUPPORTS0(ConnectionHandle)
1885 
1886 // Use this method for dispatching nsAHttpTransction's. It can only safely be
1887 // used upon first use of a connection when NPN has not negotiated SPDY vs
1888 // HTTP/1 yet as multiplexing onto an existing SPDY session requires a
1889 // concrete nsHttpTransaction
1890 nsresult nsHttpConnectionMgr::DispatchAbstractTransaction(
1891     nsConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps,
1892     HttpConnectionBase* conn, int32_t priority) {
1893   MOZ_ASSERT(ent);
1894 
1895   nsresult rv;
1896   MOZ_ASSERT(!conn->UsingSpdy(),
1897              "Spdy Must Not Use DispatchAbstractTransaction");
1898   LOG(
1899       ("nsHttpConnectionMgr::DispatchAbstractTransaction "
1900        "[ci=%s trans=%p caps=%x conn=%p]\n",
1901        ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
1902 
1903   RefPtr<nsAHttpTransaction> transaction(aTrans);
1904   RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
1905 
1906   // give the transaction the indirect reference to the connection.
1907   transaction->SetConnection(handle);
1908 
1909   rv = conn->Activate(transaction, caps, priority);
1910   if (NS_FAILED(rv)) {
1911     LOG(("  conn->Activate failed [rv=%" PRIx32 "]\n",
1912          static_cast<uint32_t>(rv)));
1913     ent->mActiveConns.RemoveElement(conn);
1914     DecrementActiveConnCount(conn);
1915     ConditionallyStopTimeoutTick();
1916 
1917     // sever back references to connection, and do so without triggering
1918     // a call to ReclaimConnection ;-)
1919     transaction->SetConnection(nullptr);
1920     handle->Reset();  // destroy the connection
1921   }
1922 
1923   return rv;
1924 }
1925 
ReportProxyTelemetry(nsConnectionEntry * ent)1926 void nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry* ent) {
1927   enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
1928 
1929   if (!ent->mConnInfo->UsingProxy())
1930     Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
1931   else if (ent->mConnInfo->UsingHttpsProxy())
1932     Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
1933   else if (ent->mConnInfo->UsingHttpProxy())
1934     Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
1935   else
1936     Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
1937 }
1938 
ProcessNewTransaction(nsHttpTransaction * trans)1939 nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
1940   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1941 
1942   // since "adds" and "cancels" are processed asynchronously and because
1943   // various events might trigger an "add" directly on the socket thread,
1944   // we must take care to avoid dispatching a transaction that has already
1945   // been canceled (see bug 190001).
1946   if (NS_FAILED(trans->Status())) {
1947     LOG(("  transaction was canceled... dropping event!\n"));
1948     return NS_OK;
1949   }
1950 
1951   trans->SetPendingTime();
1952 
1953   RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
1954       trans->GetPushedStream();
1955   if (pushedStreamWrapper) {
1956     Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
1957     if (pushedStream) {
1958       LOG(("  ProcessNewTransaction %p tied to h2 session push %p\n", trans,
1959            pushedStream->Session()));
1960       return pushedStream->Session()->AddStream(trans, trans->Priority(), false,
1961                                                 false, nullptr)
1962                  ? NS_OK
1963                  : NS_ERROR_UNEXPECTED;
1964     }
1965   }
1966 
1967   nsresult rv = NS_OK;
1968   nsHttpConnectionInfo* ci = trans->ConnectionInfo();
1969   MOZ_ASSERT(ci);
1970 
1971   nsConnectionEntry* ent = GetOrCreateConnectionEntry(
1972       ci, !!trans->TunnelProvider(), trans->Caps() & NS_HTTP_DISALLOW_HTTP3);
1973   MOZ_ASSERT(ent);
1974 
1975   ReportProxyTelemetry(ent);
1976 
1977   // Check if the transaction already has a sticky reference to a connection.
1978   // If so, then we can just use it directly by transferring its reference
1979   // to the new connection variable instead of searching for a new one
1980 
1981   nsAHttpConnection* wrappedConnection = trans->Connection();
1982   RefPtr<HttpConnectionBase> conn;
1983   RefPtr<PendingTransactionInfo> pendingTransInfo;
1984   if (wrappedConnection) conn = wrappedConnection->TakeHttpConnection();
1985 
1986   if (conn) {
1987     MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
1988     LOG(
1989         ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
1990          "sticky connection=%p\n",
1991          trans, conn.get()));
1992 
1993     if (static_cast<int32_t>(ent->mActiveConns.IndexOf(conn)) == -1) {
1994       LOG(
1995           ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
1996            "sticky connection=%p needs to go on the active list\n",
1997            trans, conn.get()));
1998 
1999       // make sure it isn't on the idle list - we expect this to be an
2000       // unknown fresh connection
2001       MOZ_ASSERT(static_cast<int32_t>(ent->mIdleConns.IndexOf(conn)) == -1);
2002       MOZ_ASSERT(!conn->IsExperienced());
2003 
2004       AddActiveConn(conn, ent);  // make it active
2005     }
2006 
2007     trans->SetConnection(nullptr);
2008     rv = DispatchTransaction(ent, trans, conn);
2009   } else {
2010     if (!ent->AllowSpdy()) {
2011       trans->DisableSpdy();
2012     }
2013     pendingTransInfo = new PendingTransactionInfo(trans);
2014     rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(),
2015                                 pendingTransInfo);
2016   }
2017 
2018   if (NS_SUCCEEDED(rv)) {
2019     LOG(("  ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
2020     return rv;
2021   }
2022 
2023   if (rv == NS_ERROR_NOT_AVAILABLE) {
2024     if (!pendingTransInfo) {
2025       pendingTransInfo = new PendingTransactionInfo(trans);
2026     }
2027     if (trans->Caps() & NS_HTTP_URGENT_START) {
2028       LOG(
2029           ("  adding transaction to pending queue "
2030            "[trans=%p urgent-start-count=%zu]\n",
2031            trans, ent->mUrgentStartQ.Length() + 1));
2032       // put this transaction on the urgent-start queue...
2033       InsertTransactionSorted(ent->mUrgentStartQ, pendingTransInfo);
2034     } else {
2035       LOG(
2036           ("  adding transaction to pending queue "
2037            "[trans=%p pending-count=%zu]\n",
2038            trans, ent->PendingQLength() + 1));
2039       // put this transaction on the pending queue...
2040       ent->InsertTransaction(pendingTransInfo);
2041     }
2042     return NS_OK;
2043   }
2044 
2045   LOG(("  ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans,
2046        static_cast<uint32_t>(rv)));
2047   return rv;
2048 }
2049 
AddActiveConn(HttpConnectionBase * conn,nsConnectionEntry * ent)2050 void nsHttpConnectionMgr::AddActiveConn(HttpConnectionBase* conn,
2051                                         nsConnectionEntry* ent) {
2052   ent->mActiveConns.AppendElement(conn);
2053   mNumActiveConns++;
2054   ActivateTimeoutTick();
2055 }
2056 
DecrementActiveConnCount(HttpConnectionBase * conn)2057 void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) {
2058   mNumActiveConns--;
2059 
2060   RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
2061   if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--;
2062 }
2063 
StartedConnect()2064 void nsHttpConnectionMgr::StartedConnect() {
2065   mNumActiveConns++;
2066   ActivateTimeoutTick();  // likely disabled by RecvdConnect()
2067 }
2068 
RecvdConnect()2069 void nsHttpConnectionMgr::RecvdConnect() {
2070   mNumActiveConns--;
2071   ConditionallyStopTimeoutTick();
2072 }
2073 
ReleaseClaimedSockets(nsConnectionEntry * ent,PendingTransactionInfo * pendingTransInfo)2074 void nsHttpConnectionMgr::ReleaseClaimedSockets(
2075     nsConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
2076   if (pendingTransInfo->mHalfOpen) {
2077     RefPtr<nsHalfOpenSocket> halfOpen =
2078         do_QueryReferent(pendingTransInfo->mHalfOpen);
2079     LOG(
2080         ("nsHttpConnectionMgr::ReleaseClaimedSockets "
2081          "[trans=%p halfOpen=%p]",
2082          pendingTransInfo->mTransaction.get(), halfOpen.get()));
2083     if (halfOpen) {
2084       halfOpen->Unclaim();
2085     }
2086     pendingTransInfo->mHalfOpen = nullptr;
2087   } else if (pendingTransInfo->mActiveConn) {
2088     RefPtr<HttpConnectionBase> activeConn =
2089         do_QueryReferent(pendingTransInfo->mActiveConn);
2090     if (activeConn && activeConn->Transaction() &&
2091         activeConn->Transaction()->IsNullTransaction()) {
2092       NullHttpTransaction* nullTrans =
2093           activeConn->Transaction()->QueryNullTransaction();
2094       nullTrans->Unclaim();
2095       LOG(("nsHttpConnectionMgr::ReleaseClaimedSockets - mark %p unclaimed.",
2096            activeConn.get()));
2097     }
2098   }
2099 }
2100 
CreateTransport(nsConnectionEntry * ent,nsAHttpTransaction * trans,uint32_t caps,bool speculative,bool isFromPredictor,bool urgentStart,bool allow1918,PendingTransactionInfo * pendingTransInfo)2101 nsresult nsHttpConnectionMgr::CreateTransport(
2102     nsConnectionEntry* ent, nsAHttpTransaction* trans, uint32_t caps,
2103     bool speculative, bool isFromPredictor, bool urgentStart, bool allow1918,
2104     PendingTransactionInfo* pendingTransInfo) {
2105   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2106   MOZ_ASSERT((speculative && !pendingTransInfo) ||
2107              (!speculative && pendingTransInfo));
2108 
2109   RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(
2110       ent, trans, caps, speculative, isFromPredictor, urgentStart);
2111 
2112   if (speculative) {
2113     sock->SetAllow1918(allow1918);
2114   }
2115   // The socket stream holds the reference to the half open
2116   // socket - so if the stream fails to init the half open
2117   // will go away.
2118   nsresult rv = sock->SetupPrimaryStreams();
2119   NS_ENSURE_SUCCESS(rv, rv);
2120 
2121   if (pendingTransInfo) {
2122     pendingTransInfo->mHalfOpen =
2123         do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
2124     DebugOnly<bool> claimed = sock->Claim();
2125     MOZ_ASSERT(claimed);
2126   }
2127 
2128   ent->mHalfOpens.AppendElement(sock);
2129   mNumHalfOpenConns++;
2130   return NS_OK;
2131 }
2132 
DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> & pendingQ,nsConnectionEntry * ent,HttpConnectionBase * conn)2133 void nsHttpConnectionMgr::DispatchSpdyPendingQ(
2134     nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, nsConnectionEntry* ent,
2135     HttpConnectionBase* conn) {
2136   if (pendingQ.Length() == 0) {
2137     return;
2138   }
2139 
2140   nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
2141   uint32_t index;
2142   // Dispatch all the transactions we can
2143   for (index = 0; index < pendingQ.Length() && conn->CanDirectlyActivate();
2144        ++index) {
2145     PendingTransactionInfo* pendingTransInfo = pendingQ[index];
2146 
2147     if (!(pendingTransInfo->mTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
2148         pendingTransInfo->mTransaction->Caps() & NS_HTTP_DISALLOW_SPDY) {
2149       leftovers.AppendElement(pendingTransInfo);
2150       continue;
2151     }
2152 
2153     nsresult rv =
2154         DispatchTransaction(ent, pendingTransInfo->mTransaction, conn);
2155     if (NS_FAILED(rv)) {
2156       // this cannot happen, but if due to some bug it does then
2157       // close the transaction
2158       MOZ_ASSERT(false, "Dispatch SPDY Transaction");
2159       LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
2160            pendingTransInfo->mTransaction.get()));
2161       pendingTransInfo->mTransaction->Close(rv);
2162     }
2163     ReleaseClaimedSockets(ent, pendingTransInfo);
2164   }
2165 
2166   // Slurp up the rest of the pending queue into our leftovers bucket (we
2167   // might have some left if conn->CanDirectlyActivate returned false)
2168   for (; index < pendingQ.Length(); ++index) {
2169     PendingTransactionInfo* pendingTransInfo = pendingQ[index];
2170     leftovers.AppendElement(pendingTransInfo);
2171   }
2172 
2173   // Put the leftovers back in the pending queue and get rid of the
2174   // transactions we dispatched
2175   leftovers.SwapElements(pendingQ);
2176   leftovers.Clear();
2177 }
2178 
2179 // This function tries to dispatch the pending h2 or h3 transactions on
2180 // the connection entry sent in as an argument. It will do so on the
2181 // active h2 or h3 connection either in that same entry or from the
2182 // coalescing hash table
ProcessSpdyPendingQ(nsConnectionEntry * ent)2183 void nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry* ent) {
2184   HttpConnectionBase* conn = GetH2orH3ActiveConn(ent, false);
2185   if (!conn || !conn->CanDirectlyActivate()) {
2186     return;
2187   }
2188 
2189   DispatchSpdyPendingQ(ent->mUrgentStartQ, ent, conn);
2190   if (!conn->CanDirectlyActivate()) {
2191     return;
2192   }
2193 
2194   nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
2195   // XXX Get all transactions for SPDY currently.
2196   ent->AppendPendingQForNonFocusedWindows(0, pendingQ);
2197   DispatchSpdyPendingQ(pendingQ, ent, conn);
2198 
2199   // Put the leftovers back in the pending queue.
2200   for (const auto& transactionInfo : pendingQ) {
2201     ent->InsertTransaction(transactionInfo);
2202   }
2203 }
2204 
OnMsgProcessAllSpdyPendingQ(int32_t,ARefBase *)2205 void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*) {
2206   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2207   LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
2208   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2209     ProcessSpdyPendingQ(iter.Data().get());
2210   }
2211 }
2212 
2213 // Given a connection entry, return an active h2 or h3 connection
2214 // that can be directly activated or null.
GetH2orH3ActiveConn(nsConnectionEntry * ent,bool aNoHttp3)2215 HttpConnectionBase* nsHttpConnectionMgr::GetH2orH3ActiveConn(
2216     nsConnectionEntry* ent, bool aNoHttp3) {
2217   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2218   MOZ_ASSERT(ent);
2219 
2220   HttpConnectionBase* experienced = nullptr;
2221   HttpConnectionBase* noExperience = nullptr;
2222   uint32_t activeLen = ent->mActiveConns.Length();
2223   nsHttpConnectionInfo* ci = ent->mConnInfo;
2224   uint32_t index;
2225 
2226   // activeLen should generally be 1.. this is a setup race being resolved
2227   // take a conn who can activate and is experienced
2228   for (index = 0; index < activeLen; ++index) {
2229     HttpConnectionBase* tmp = ent->mActiveConns[index];
2230     if (tmp->CanDirectlyActivate()) {
2231       if (tmp->IsExperienced()) {
2232         experienced = tmp;
2233         break;
2234       }
2235       noExperience = tmp;  // keep looking for a better option
2236     }
2237   }
2238 
2239   // if that worked, cleanup anything else and exit
2240   if (experienced) {
2241     for (index = 0; index < activeLen; ++index) {
2242       HttpConnectionBase* tmp = ent->mActiveConns[index];
2243       // in the case where there is a functional h2 session, drop the others
2244       if (tmp != experienced) {
2245         tmp->DontReuse();
2246       }
2247     }
2248     for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
2249          --index) {
2250       LOG(
2251           ("GetH2orH3ActiveConn() shutting down connection in fast "
2252            "open state (%p) because we have an experienced spdy "
2253            "connection (%p).\n",
2254            ent->mHalfOpenFastOpenBackups[index].get(), experienced));
2255       RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
2256       half->CancelFastOpenConnection();
2257     }
2258 
2259     LOG(
2260         ("GetH2orH3ActiveConn() request for ent %p %s "
2261          "found an active experienced connection %p in native connection "
2262          "entry\n",
2263          ent, ci->HashKey().get(), experienced));
2264     return experienced;
2265   }
2266 
2267   if (noExperience) {
2268     LOG(
2269         ("GetH2orH3ActiveConn() request for ent %p %s "
2270          "found an active but inexperienced connection %p in native connection "
2271          "entry\n",
2272          ent, ci->HashKey().get(), noExperience));
2273     return noExperience;
2274   }
2275 
2276   // there was no active spdy connection in the connection entry, but
2277   // there might be one in the hash table for coalescing
2278   HttpConnectionBase* existingConn =
2279       FindCoalescableConnection(ent, false, aNoHttp3);
2280   if (existingConn) {
2281     LOG(
2282         ("GetH2orH3ActiveConn() request for ent %p %s "
2283          "found an active connection %p in the coalescing hashtable\n",
2284          ent, ci->HashKey().get(), existingConn));
2285     return existingConn;
2286   }
2287 
2288   LOG(
2289       ("GetH2orH3ActiveConn() request for ent %p %s "
2290        "did not find an active connection\n",
2291        ent, ci->HashKey().get()));
2292   return nullptr;
2293 }
2294 
2295 //-----------------------------------------------------------------------------
2296 
AbortAndCloseAllConnections(int32_t,ARefBase *)2297 void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) {
2298   if (!OnSocketThread()) {
2299     Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
2300     return;
2301   }
2302 
2303   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2304   LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
2305   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2306     RefPtr<nsConnectionEntry> ent = iter.Data();
2307 
2308     // Close all active connections.
2309     while (ent->mActiveConns.Length()) {
2310       RefPtr<HttpConnectionBase> conn(ent->mActiveConns[0]);
2311       ent->mActiveConns.RemoveElementAt(0);
2312       DecrementActiveConnCount(conn);
2313       // Since HttpConnectionBase::Close doesn't break the bond with
2314       // the connection's transaction, we must explicitely tell it
2315       // to close its transaction and not just self.
2316       conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
2317     }
2318 
2319     // Close all idle connections.
2320     while (ent->mIdleConns.Length()) {
2321       RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
2322 
2323       ent->mIdleConns.RemoveElementAt(0);
2324       mNumIdleConns--;
2325 
2326       conn->Close(NS_ERROR_ABORT);
2327     }
2328 
2329     // If all idle connections are removed we can stop pruning dead
2330     // connections.
2331     ConditionallyStopPruneDeadConnectionsTimer();
2332 
2333     // Close all urgentStart transactions.
2334     while (ent->mUrgentStartQ.Length()) {
2335       PendingTransactionInfo* pendingTransInfo = ent->mUrgentStartQ[0];
2336       pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
2337       ent->mUrgentStartQ.RemoveElementAt(0);
2338     }
2339 
2340     // Close all pending transactions.
2341     for (auto it = ent->mPendingTransactionTable.Iter(); !it.Done();
2342          it.Next()) {
2343       while (it.UserData()->Length()) {
2344         PendingTransactionInfo* pendingTransInfo = (*it.UserData())[0];
2345         pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
2346         it.UserData()->RemoveElementAt(0);
2347       }
2348     }
2349     ent->mPendingTransactionTable.Clear();
2350 
2351     // Close all half open tcp connections.
2352     for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
2353       ent->mHalfOpens[i]->Abandon();
2354     }
2355 
2356     MOZ_ASSERT(ent->mHalfOpenFastOpenBackups.Length() == 0 &&
2357                !ent->mDoNotDestroy);
2358     iter.Remove();
2359   }
2360 
2361   mActiveTransactions[false].Clear();
2362   mActiveTransactions[true].Clear();
2363 }
2364 
OnMsgShutdown(int32_t,ARefBase * param)2365 void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase* param) {
2366   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2367   LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
2368 
2369   gHttpHandler->StopRequestTokenBucket();
2370   AbortAndCloseAllConnections(0, nullptr);
2371 
2372   // If all idle connections are removed we can stop pruning dead
2373   // connections.
2374   ConditionallyStopPruneDeadConnectionsTimer();
2375 
2376   if (mTimeoutTick) {
2377     mTimeoutTick->Cancel();
2378     mTimeoutTick = nullptr;
2379     mTimeoutTickArmed = false;
2380   }
2381   if (mTimer) {
2382     mTimer->Cancel();
2383     mTimer = nullptr;
2384   }
2385   if (mTrafficTimer) {
2386     mTrafficTimer->Cancel();
2387     mTrafficTimer = nullptr;
2388   }
2389   DestroyThrottleTicker();
2390 
2391   mCoalescingHash.Clear();
2392 
2393   // signal shutdown complete
2394   nsCOMPtr<nsIRunnable> runnable =
2395       new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, 0, param);
2396   NS_DispatchToMainThread(runnable);
2397 }
2398 
OnMsgShutdownConfirm(int32_t priority,ARefBase * param)2399 void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority,
2400                                                ARefBase* param) {
2401   MOZ_ASSERT(NS_IsMainThread());
2402   LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
2403 
2404   BoolWrapper* shutdown = static_cast<BoolWrapper*>(param);
2405   shutdown->mBool = true;
2406 }
2407 
OnMsgNewTransaction(int32_t priority,ARefBase * param)2408 void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority,
2409                                               ARefBase* param) {
2410   nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
2411 
2412   LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans));
2413   trans->SetPriority(priority);
2414   nsresult rv = ProcessNewTransaction(trans);
2415   if (NS_FAILED(rv)) trans->Close(rv);  // for whatever its worth
2416 }
2417 
OnMsgNewTransactionWithStickyConn(int32_t priority,ARefBase * param)2418 void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority,
2419                                                             ARefBase* param) {
2420   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2421 
2422   NewTransactionData* data = static_cast<NewTransactionData*>(param);
2423   LOG(
2424       ("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn "
2425        "[trans=%p, transWithStickyConn=%p, conn=%p]\n",
2426        data->mTrans.get(), data->mTransWithStickyConn.get(),
2427        data->mTransWithStickyConn->Connection()));
2428 
2429   MOZ_ASSERT(data->mTransWithStickyConn &&
2430              data->mTransWithStickyConn->Caps() & NS_HTTP_STICKY_CONNECTION);
2431 
2432   data->mTrans->SetPriority(data->mPriority);
2433 
2434   RefPtr<nsAHttpConnection> conn = data->mTransWithStickyConn->Connection();
2435   if (conn && conn->IsPersistent()) {
2436     // This is so far a workaround to only reuse persistent
2437     // connection for authentication retry. See bug 459620 comment 4
2438     // for details.
2439     LOG((" Reuse connection [%p] for transaction [%p]", conn.get(),
2440          data->mTrans.get()));
2441     data->mTrans->SetConnection(conn);
2442   }
2443 
2444   nsresult rv = ProcessNewTransaction(data->mTrans);
2445   if (NS_FAILED(rv)) {
2446     data->mTrans->Close(rv);  // for whatever its worth
2447   }
2448 }
2449 
TabIdForQueuing(nsAHttpTransaction * transaction)2450 static uint64_t TabIdForQueuing(nsAHttpTransaction* transaction) {
2451   return gHttpHandler->ActiveTabPriority()
2452              ? transaction->TopLevelOuterContentWindowId()
2453              : 0;
2454 }
2455 
2456 nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>>*
GetTransactionPendingQHelper(nsConnectionEntry * ent,nsAHttpTransaction * trans)2457 nsHttpConnectionMgr::GetTransactionPendingQHelper(nsConnectionEntry* ent,
2458                                                   nsAHttpTransaction* trans) {
2459   nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ = nullptr;
2460   int32_t caps = trans->Caps();
2461   if (caps & NS_HTTP_URGENT_START) {
2462     pendingQ = &(ent->mUrgentStartQ);
2463   } else {
2464     pendingQ = ent->mPendingTransactionTable.Get(TabIdForQueuing(trans));
2465   }
2466   return pendingQ;
2467 }
2468 
OnMsgReschedTransaction(int32_t priority,ARefBase * param)2469 void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority,
2470                                                   ARefBase* param) {
2471   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2472   LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
2473 
2474   RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction*>(param);
2475   trans->SetPriority(priority);
2476 
2477   if (!trans->ConnectionInfo()) {
2478     return;
2479   }
2480   nsConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
2481 
2482   if (ent) {
2483     nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
2484         GetTransactionPendingQHelper(ent, trans);
2485 
2486     int32_t index =
2487         pendingQ ? pendingQ->IndexOf(trans, 0, PendingComparator()) : -1;
2488     if (index >= 0) {
2489       RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
2490       pendingQ->RemoveElementAt(index);
2491       InsertTransactionSorted(*pendingQ, pendingTransInfo);
2492     }
2493   }
2494 }
2495 
OnMsgUpdateClassOfServiceOnTransaction(int32_t arg,ARefBase * param)2496 void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
2497     int32_t arg, ARefBase* param) {
2498   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2499   LOG(
2500       ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
2501        "[trans=%p]\n",
2502        param));
2503 
2504   uint32_t cos = static_cast<uint32_t>(arg);
2505   nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
2506 
2507   uint32_t previous = trans->ClassOfService();
2508   trans->SetClassOfService(cos);
2509 
2510   if ((previous ^ cos) & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
2511     Unused << RescheduleTransaction(trans, trans->Priority());
2512   }
2513 }
2514 
OnMsgCancelTransaction(int32_t reason,ARefBase * param)2515 void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason,
2516                                                  ARefBase* param) {
2517   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2518   LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
2519 
2520   nsresult closeCode = static_cast<nsresult>(reason);
2521 
2522   // caller holds a ref to param/trans on stack
2523   nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
2524 
2525   //
2526   // if the transaction owns a connection and the transaction is not done,
2527   // then ask the connection to close the transaction.  otherwise, close the
2528   // transaction directly (removing it from the pending queue first).
2529   //
2530   RefPtr<nsAHttpConnection> conn(trans->Connection());
2531   if (conn && !trans->IsDone()) {
2532     conn->CloseTransaction(trans, closeCode);
2533   } else {
2534     nsConnectionEntry* ent = nullptr;
2535     if (trans->ConnectionInfo()) {
2536       ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
2537     }
2538     if (ent) {
2539       int32_t transIndex;
2540       // We will abandon all half-open sockets belonging to the given
2541       // transaction.
2542       nsTArray<RefPtr<PendingTransactionInfo>>* infoArray =
2543           GetTransactionPendingQHelper(ent, trans);
2544 
2545       RefPtr<PendingTransactionInfo> pendingTransInfo;
2546       transIndex =
2547           infoArray ? infoArray->IndexOf(trans, 0, PendingComparator()) : -1;
2548       if (transIndex >= 0) {
2549         LOG(
2550             ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
2551              " found in urgentStart queue\n",
2552              trans));
2553         pendingTransInfo = (*infoArray)[transIndex];
2554         // We do not need to ReleaseClaimedSockets while we are
2555         // going to close them all any way!
2556         infoArray->RemoveElementAt(transIndex);
2557       }
2558 
2559       // Abandon all half-open sockets belonging to the given transaction.
2560       if (pendingTransInfo) {
2561         RefPtr<nsHalfOpenSocket> half =
2562             do_QueryReferent(pendingTransInfo->mHalfOpen);
2563         if (half) {
2564           half->Abandon();
2565         }
2566         pendingTransInfo->mHalfOpen = nullptr;
2567       }
2568     }
2569 
2570     trans->Close(closeCode);
2571 
2572     // Cancel is a pretty strong signal that things might be hanging
2573     // so we want to cancel any null transactions related to this connection
2574     // entry. They are just optimizations, but they aren't hooked up to
2575     // anything that might get canceled from the rest of gecko, so best
2576     // to assume that's what was meant by the cancel we did receive if
2577     // it only applied to something in the queue.
2578     for (uint32_t index = 0; ent && (index < ent->mActiveConns.Length());
2579          ++index) {
2580       HttpConnectionBase* activeConn = ent->mActiveConns[index];
2581       nsAHttpTransaction* liveTransaction = activeConn->Transaction();
2582       if (liveTransaction && liveTransaction->IsNullTransaction()) {
2583         LOG(
2584             ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
2585              "also canceling Null Transaction %p on conn %p\n",
2586              trans, liveTransaction, activeConn));
2587         activeConn->CloseTransaction(liveTransaction, closeCode);
2588       }
2589     }
2590   }
2591 }
2592 
OnMsgProcessPendingQ(int32_t,ARefBase * param)2593 void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase* param) {
2594   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2595   nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
2596 
2597   if (!ci) {
2598     LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
2599     // Try and dispatch everything
2600     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2601       Unused << ProcessPendingQForEntry(iter.Data().get(), true);
2602     }
2603     return;
2604   }
2605 
2606   LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
2607        ci->HashKey().get()));
2608 
2609   // start by processing the queue identified by the given connection info.
2610   nsConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
2611   if (!(ent && ProcessPendingQForEntry(ent, false))) {
2612     // if we reach here, it means that we couldn't dispatch a transaction
2613     // for the specified connection info.  walk the connection table...
2614     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2615       if (ProcessPendingQForEntry(iter.Data().get(), false)) {
2616         break;
2617       }
2618     }
2619   }
2620 }
2621 
CancelTransactions(nsHttpConnectionInfo * ci,nsresult code)2622 nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci,
2623                                                  nsresult code) {
2624   LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get()));
2625 
2626   int32_t intReason = static_cast<int32_t>(code);
2627   return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason,
2628                    ci);
2629 }
2630 
CancelTransactionsHelper(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>> & pendingQ,const nsHttpConnectionInfo * ci,const nsHttpConnectionMgr::nsConnectionEntry * ent,nsresult reason)2631 void nsHttpConnectionMgr::CancelTransactionsHelper(
2632     nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>>& pendingQ,
2633     const nsHttpConnectionInfo* ci,
2634     const nsHttpConnectionMgr::nsConnectionEntry* ent, nsresult reason) {
2635   for (const auto& pendingTransInfo : pendingQ) {
2636     LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
2637          ci->HashKey().get(), ent, pendingTransInfo->mTransaction.get()));
2638     pendingTransInfo->mTransaction->Close(reason);
2639   }
2640   pendingQ.Clear();
2641 }
2642 
OnMsgCancelTransactions(int32_t code,ARefBase * param)2643 void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code,
2644                                                   ARefBase* param) {
2645   nsresult reason = static_cast<nsresult>(code);
2646   nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
2647   nsConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
2648   LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
2649        ci->HashKey().get(), ent));
2650   if (!ent) {
2651     return;
2652   }
2653 
2654   CancelTransactionsHelper(ent->mUrgentStartQ, ci, ent, reason);
2655 
2656   for (auto it = ent->mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
2657     CancelTransactionsHelper(*it.UserData(), ci, ent, reason);
2658   }
2659   ent->mPendingTransactionTable.Clear();
2660 }
2661 
OnMsgPruneDeadConnections(int32_t,ARefBase *)2662 void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase*) {
2663   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2664   LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
2665 
2666   // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
2667   mTimeOfNextWakeUp = UINT64_MAX;
2668 
2669   // check canreuse() for all idle connections plus any active connections on
2670   // connection entries that are using spdy.
2671   if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) {
2672     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2673       RefPtr<nsConnectionEntry> ent = iter.Data();
2674 
2675       LOG(("  pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
2676 
2677       // Find out how long it will take for next idle connection to not
2678       // be reusable anymore.
2679       uint32_t timeToNextExpire = UINT32_MAX;
2680       int32_t count = ent->mIdleConns.Length();
2681       if (count > 0) {
2682         for (int32_t i = count - 1; i >= 0; --i) {
2683           RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
2684           if (!conn->CanReuse()) {
2685             ent->mIdleConns.RemoveElementAt(i);
2686             conn->Close(NS_ERROR_ABORT);
2687             mNumIdleConns--;
2688           } else {
2689             timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
2690           }
2691         }
2692       }
2693 
2694       if (ent->mUsingSpdy) {
2695         for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
2696           RefPtr<nsHttpConnection> connTCP =
2697               do_QueryObject(ent->mActiveConns[i]);
2698           // Http3 has its own timers, it is not using this one.
2699           if (connTCP && connTCP->UsingSpdy()) {
2700             if (!connTCP->CanReuse()) {
2701               // Marking it don't-reuse will create an active
2702               // tear down if the spdy session is idle.
2703               connTCP->DontReuse();
2704             } else {
2705               timeToNextExpire =
2706                   std::min(timeToNextExpire, connTCP->TimeToLive());
2707             }
2708           }
2709         }
2710       }
2711 
2712       // If time to next expire found is shorter than time to next
2713       // wake-up, we need to change the time for next wake-up.
2714       if (timeToNextExpire != UINT32_MAX) {
2715         uint32_t now = NowInSeconds();
2716         uint64_t timeOfNextExpire = now + timeToNextExpire;
2717         // If pruning of dead connections is not already scheduled to
2718         // happen or time found for next connection to expire is is
2719         // before mTimeOfNextWakeUp, we need to schedule the pruning to
2720         // happen after timeToNextExpire.
2721         if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
2722           PruneDeadConnectionsAfter(timeToNextExpire);
2723         }
2724       } else {
2725         ConditionallyStopPruneDeadConnectionsTimer();
2726       }
2727 
2728       ent->RemoveEmptyPendingQ();
2729 
2730       // If this entry is empty, we have too many entries busy then
2731       // we can clean it up and restart
2732       if (mCT.Count() > 125 && ent->mIdleConns.Length() == 0 &&
2733           ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 &&
2734           ent->PendingQLength() == 0 && ent->mUrgentStartQ.Length() == 0 &&
2735           ent->mHalfOpenFastOpenBackups.Length() == 0 && !ent->mDoNotDestroy &&
2736           (!ent->mUsingSpdy || mCT.Count() > 300)) {
2737         LOG(("    removing empty connection entry\n"));
2738         iter.Remove();
2739         continue;
2740       }
2741 
2742       // Otherwise use this opportunity to compact our arrays...
2743       ent->mIdleConns.Compact();
2744       ent->mActiveConns.Compact();
2745       ent->mUrgentStartQ.Compact();
2746 
2747       for (auto it = ent->mPendingTransactionTable.Iter(); !it.Done();
2748            it.Next()) {
2749         it.UserData()->Compact();
2750       }
2751     }
2752   }
2753 }
2754 
OnMsgPruneNoTraffic(int32_t,ARefBase *)2755 void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase*) {
2756   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2757   LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
2758 
2759   // Prune connections without traffic
2760   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2761     // Close the connections with no registered traffic.
2762     RefPtr<nsConnectionEntry> ent = iter.Data();
2763 
2764     LOG(("  pruning no traffic [ci=%s]\n", ent->mConnInfo->HashKey().get()));
2765 
2766     if (!ent->mConnInfo->IsHttp3()) {
2767       uint32_t numConns = ent->mActiveConns.Length();
2768       if (numConns) {
2769         // Walk the list backwards to allow us to remove entries easily.
2770         for (int index = numConns - 1; index >= 0; index--) {
2771           RefPtr<nsHttpConnection> conn =
2772               do_QueryObject(ent->mActiveConns[index]);
2773           if (conn && conn->NoTraffic()) {
2774             ent->mActiveConns.RemoveElementAt(index);
2775             DecrementActiveConnCount(conn);
2776             conn->Close(NS_ERROR_ABORT);
2777             LOG(
2778                 ("  closed active connection due to no traffic "
2779                  "[conn=%p]\n",
2780                  conn.get()));
2781           }
2782         }
2783       }
2784     }
2785   }
2786 
2787   mPruningNoTraffic = false;  // not pruning anymore
2788 }
2789 
OnMsgVerifyTraffic(int32_t,ARefBase *)2790 void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase*) {
2791   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2792   LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
2793 
2794   if (mPruningNoTraffic) {
2795     // Called in the time gap when the timeout to prune notraffic
2796     // connections has triggered but the pruning hasn't happened yet.
2797     return;
2798   }
2799 
2800   // Mark connections for traffic verification
2801   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2802     RefPtr<nsConnectionEntry> ent = iter.Data();
2803     if (!ent->mConnInfo->IsHttp3()) {
2804       // Iterate over all active connections and check them.
2805       for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
2806         RefPtr<nsHttpConnection> conn =
2807             do_QueryObject(ent->mActiveConns[index]);
2808         if (conn) {
2809           conn->CheckForTraffic(true);
2810         }
2811       }
2812       // Iterate the idle connections and unmark them for traffic checks.
2813       for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
2814         RefPtr<nsHttpConnection> conn = do_QueryObject(ent->mIdleConns[index]);
2815         if (conn) {
2816           conn->CheckForTraffic(false);
2817         }
2818       }
2819     }
2820   }
2821 
2822   // If the timer is already there. we just re-init it
2823   if (!mTrafficTimer) {
2824     mTrafficTimer = NS_NewTimer();
2825   }
2826 
2827   // failure to create a timer is not a fatal error, but dead
2828   // connections will not be cleaned up as nicely
2829   if (mTrafficTimer) {
2830     // Give active connections time to get more traffic before killing
2831     // them off. Default: 5000 milliseconds
2832     mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
2833                         nsITimer::TYPE_ONE_SHOT);
2834   } else {
2835     NS_WARNING("failed to create timer for VerifyTraffic!");
2836   }
2837 }
2838 
OnMsgDoShiftReloadConnectionCleanup(int32_t,ARefBase * param)2839 void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
2840                                                               ARefBase* param) {
2841   LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
2842   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2843 
2844   nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
2845 
2846   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
2847     ClosePersistentConnections(iter.Data());
2848   }
2849 
2850   if (ci) ResetIPFamilyPreference(ci);
2851 }
2852 
OnMsgReclaimConnection(HttpConnectionBase * conn)2853 void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase* conn) {
2854   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2855 
2856   //
2857   // 1) remove the connection from the active list
2858   // 2) if keep-alive, add connection to idle list
2859   // 3) post event to process the pending transaction queue
2860   //
2861 
2862   MOZ_ASSERT(conn);
2863   nsConnectionEntry* ent = conn->ConnectionInfo()
2864                                ? mCT.GetWeak(conn->ConnectionInfo()->HashKey())
2865                                : nullptr;
2866 
2867   if (!ent) {
2868     // this can happen if the connection is made outside of the
2869     // connection manager and is being "reclaimed" for use with
2870     // future transactions. HTTP/2 tunnels work like this.
2871     ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false);
2872     LOG(
2873         ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
2874          "forced new hash entry %s\n",
2875          conn, conn->ConnectionInfo()->HashKey().get()));
2876   }
2877 
2878   MOZ_ASSERT(ent);
2879   RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
2880 
2881   LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent,
2882        conn));
2883 
2884   // If the connection is in the active list, remove that entry
2885   // and the reference held by the mActiveConns list.
2886   // This is never the final reference on conn as the event context
2887   // is also holding one that is released at the end of this function.
2888 
2889   RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
2890   if (!connTCP || connTCP->EverUsedSpdy()) {
2891     // Spdyand Http3 connections aren't reused in the traditional HTTP way in
2892     // the idleconns list, they are actively multplexed as active
2893     // conns. Even when they have 0 transactions on them they are
2894     // considered active connections. So when one is reclaimed it
2895     // is really complete and is meant to be shut down and not
2896     // reused.
2897     conn->DontReuse();
2898   }
2899 
2900   // a connection that still holds a reference to a transaction was
2901   // not closed naturally (i.e. it was reset or aborted) and is
2902   // therefore not something that should be reused.
2903   if (conn->Transaction()) {
2904     conn->DontReuse();
2905   }
2906 
2907   if (ent->mActiveConns.RemoveElement(conn)) {
2908     DecrementActiveConnCount(conn);
2909     ConditionallyStopTimeoutTick();
2910   } else if (!connTCP || connTCP->EverUsedSpdy()) {
2911     LOG(("HttpConnectionBase %p not found in its connection entry, try ^anon",
2912          conn));
2913     // repeat for flipped anon flag as we share connection entries for spdy
2914     // connections.
2915     RefPtr<nsHttpConnectionInfo> anonInvertedCI(ci->Clone());
2916     anonInvertedCI->SetAnonymous(!ci->GetAnonymous());
2917 
2918     nsConnectionEntry* ent = mCT.GetWeak(anonInvertedCI->HashKey());
2919     if (ent) {
2920       if (ent->mActiveConns.RemoveElement(conn)) {
2921         DecrementActiveConnCount(conn);
2922         ConditionallyStopTimeoutTick();
2923       } else {
2924         LOG(
2925             ("HttpConnectionBase %p could not be removed from its entry's "
2926              "active list",
2927              conn));
2928       }
2929     }
2930   }
2931 
2932   if (connTCP && connTCP->CanReuse()) {
2933     LOG(("  adding connection to idle list\n"));
2934     // Keep The idle connection list sorted with the connections that
2935     // have moved the largest data pipelines at the front because these
2936     // connections have the largest cwnds on the server.
2937 
2938     // The linear search is ok here because the number of idleconns
2939     // in a single entry is generally limited to a small number (i.e. 6)
2940 
2941     uint32_t idx;
2942     for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
2943       nsHttpConnection* idleConn = ent->mIdleConns[idx];
2944       if (idleConn->MaxBytesRead() < connTCP->MaxBytesRead()) break;
2945     }
2946 
2947     ent->mIdleConns.InsertElementAt(idx, connTCP);
2948     mNumIdleConns++;
2949     connTCP->BeginIdleMonitoring();
2950 
2951     // If the added connection was first idle connection or has shortest
2952     // time to live among the watched connections, pruning dead
2953     // connections needs to be done when it can't be reused anymore.
2954     uint32_t timeToLive = connTCP->TimeToLive();
2955     if (!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
2956       PruneDeadConnectionsAfter(timeToLive);
2957   } else {
2958     LOG(("  connection cannot be reused; closing connection\n"));
2959     conn->Close(NS_ERROR_ABORT);
2960   }
2961 
2962   OnMsgProcessPendingQ(0, ci);
2963 }
2964 
OnMsgCompleteUpgrade(int32_t,ARefBase * param)2965 void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase* param) {
2966   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2967 
2968   nsresult rv = NS_OK;
2969   nsCompleteUpgradeData* data = static_cast<nsCompleteUpgradeData*>(param);
2970   MOZ_ASSERT(data->mTrans && data->mTrans->Caps() & NS_HTTP_STICKY_CONNECTION);
2971 
2972   RefPtr<nsAHttpConnection> conn(data->mTrans->Connection());
2973   LOG(
2974       ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
2975        "conn=%p listener=%p wrapped=%d\n",
2976        conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
2977 
2978   if (!conn) {
2979     // Delay any error reporting to happen in transportAvailableFunc
2980     rv = NS_ERROR_UNEXPECTED;
2981   } else {
2982     MOZ_ASSERT(!data->mSocketTransport);
2983     rv = conn->TakeTransport(getter_AddRefs(data->mSocketTransport),
2984                              getter_AddRefs(data->mSocketIn),
2985                              getter_AddRefs(data->mSocketOut));
2986 
2987     if (NS_FAILED(rv)) {
2988       LOG(("  conn->TakeTransport failed with %" PRIx32,
2989            static_cast<uint32_t>(rv)));
2990     }
2991   }
2992 
2993   RefPtr<nsCompleteUpgradeData> upgradeData(data);
2994   auto transportAvailableFunc = [upgradeData{std::move(upgradeData)},
2995                                  aRv(rv)]() {
2996     // Handle any potential previous errors first
2997     // and call OnUpgradeFailed if necessary.
2998     nsresult rv = aRv;
2999 
3000     if (NS_FAILED(rv)) {
3001       rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv);
3002       if (NS_FAILED(rv)) {
3003         LOG(
3004             ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
3005              " listener=%p\n",
3006              upgradeData->mUpgradeListener.get()));
3007       }
3008       return;
3009     }
3010 
3011     rv = upgradeData->mUpgradeListener->OnTransportAvailable(
3012         upgradeData->mSocketTransport, upgradeData->mSocketIn,
3013         upgradeData->mSocketOut);
3014     if (NS_FAILED(rv)) {
3015       LOG(
3016           ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
3017            "failed. listener=%p\n",
3018            upgradeData->mUpgradeListener.get()));
3019     }
3020   };
3021 
3022   if (data->mJsWrapped) {
3023     LOG(
3024         ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
3025          "conn=%p listener=%p wrapped=%d pass to main thread\n",
3026          conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
3027     NS_DispatchToMainThread(
3028         NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade",
3029                                transportAvailableFunc));
3030   } else {
3031     transportAvailableFunc();
3032   }
3033 }
3034 
OnMsgUpdateParam(int32_t inParam,ARefBase *)3035 void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase*) {
3036   uint32_t param = static_cast<uint32_t>(inParam);
3037   uint16_t name = ((param)&0xFFFF0000) >> 16;
3038   uint16_t value = param & 0x0000FFFF;
3039 
3040   switch (name) {
3041     case MAX_CONNECTIONS:
3042       mMaxConns = value;
3043       break;
3044     case MAX_URGENT_START_Q:
3045       mMaxUrgentExcessiveConns = value;
3046       break;
3047     case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
3048       mMaxPersistConnsPerHost = value;
3049       break;
3050     case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
3051       mMaxPersistConnsPerProxy = value;
3052       break;
3053     case MAX_REQUEST_DELAY:
3054       mMaxRequestDelay = value;
3055       break;
3056     case THROTTLING_ENABLED:
3057       SetThrottlingEnabled(!!value);
3058       break;
3059     case THROTTLING_SUSPEND_FOR:
3060       mThrottleSuspendFor = value;
3061       break;
3062     case THROTTLING_RESUME_FOR:
3063       mThrottleResumeFor = value;
3064       break;
3065     case THROTTLING_READ_LIMIT:
3066       mThrottleReadLimit = value;
3067       break;
3068     case THROTTLING_READ_INTERVAL:
3069       mThrottleReadInterval = value;
3070       break;
3071     case THROTTLING_HOLD_TIME:
3072       mThrottleHoldTime = value;
3073       break;
3074     case THROTTLING_MAX_TIME:
3075       mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
3076       break;
3077     case PROXY_BE_CONSERVATIVE:
3078       mBeConservativeForProxy = !!value;
3079       break;
3080     default:
3081       MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
3082   }
3083 }
3084 
3085 // nsHttpConnectionMgr::nsConnectionEntry
~nsConnectionEntry()3086 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() {
3087   LOG(("nsConnectionEntry::~nsConnectionEntry this=%p", this));
3088 
3089   MOZ_ASSERT(!mIdleConns.Length());
3090   MOZ_ASSERT(!mActiveConns.Length());
3091   MOZ_ASSERT(!mHalfOpens.Length());
3092   MOZ_ASSERT(!mUrgentStartQ.Length());
3093   MOZ_ASSERT(!PendingQLength());
3094   MOZ_ASSERT(!mHalfOpenFastOpenBackups.Length());
3095   MOZ_ASSERT(!mDoNotDestroy);
3096 
3097   MOZ_COUNT_DTOR(nsConnectionEntry);
3098 }
3099 
3100 // Read Timeout Tick handlers
3101 
ActivateTimeoutTick()3102 void nsHttpConnectionMgr::ActivateTimeoutTick() {
3103   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3104   LOG(
3105       ("nsHttpConnectionMgr::ActivateTimeoutTick() "
3106        "this=%p mTimeoutTick=%p\n",
3107        this, mTimeoutTick.get()));
3108 
3109   // The timer tick should be enabled if it is not already pending.
3110   // Upon running the tick will rearm itself if there are active
3111   // connections available.
3112 
3113   if (mTimeoutTick && mTimeoutTickArmed) {
3114     // make sure we get one iteration on a quick tick
3115     if (mTimeoutTickNext > 1) {
3116       mTimeoutTickNext = 1;
3117       mTimeoutTick->SetDelay(1000);
3118     }
3119     return;
3120   }
3121 
3122   if (!mTimeoutTick) {
3123     mTimeoutTick = NS_NewTimer();
3124     if (!mTimeoutTick) {
3125       NS_WARNING("failed to create timer for http timeout management");
3126       return;
3127     }
3128     mTimeoutTick->SetTarget(mSocketThreadTarget);
3129   }
3130 
3131   MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
3132   mTimeoutTickArmed = true;
3133   mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
3134 }
3135 
3136 class UINT64Wrapper : public ARefBase {
3137  public:
UINT64Wrapper(uint64_t aUint64)3138   explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper,override)3139   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
3140 
3141   uint64_t GetValue() { return mUint64; }
3142 
3143  private:
3144   uint64_t mUint64;
3145   virtual ~UINT64Wrapper() = default;
3146 };
3147 
UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId)3148 nsresult nsHttpConnectionMgr::UpdateCurrentTopLevelOuterContentWindowId(
3149     uint64_t aWindowId) {
3150   RefPtr<UINT64Wrapper> windowIdWrapper = new UINT64Wrapper(aWindowId);
3151   return PostEvent(
3152       &nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId, 0,
3153       windowIdWrapper);
3154 }
3155 
SetThrottlingEnabled(bool aEnable)3156 void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) {
3157   LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
3158 
3159   mThrottleEnabled = aEnable;
3160 
3161   if (mThrottleEnabled) {
3162     EnsureThrottleTickerIfNeeded();
3163   } else {
3164     DestroyThrottleTicker();
3165     ResumeReadOf(mActiveTransactions[false]);
3166     ResumeReadOf(mActiveTransactions[true]);
3167   }
3168 }
3169 
InThrottlingTimeWindow()3170 bool nsHttpConnectionMgr::InThrottlingTimeWindow() {
3171   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3172 
3173   if (mThrottlingWindowEndsAt.IsNull()) {
3174     return true;
3175   }
3176   return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt;
3177 }
3178 
TouchThrottlingTimeWindow(bool aEnsureTicker)3179 void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker) {
3180   LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
3181 
3182   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3183 
3184   mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
3185 
3186   if (!mThrottleTicker && MOZ_LIKELY(aEnsureTicker) &&
3187       MOZ_LIKELY(mThrottleEnabled)) {
3188     EnsureThrottleTickerIfNeeded();
3189   }
3190 }
3191 
LogActiveTransactions(char operation)3192 void nsHttpConnectionMgr::LogActiveTransactions(char operation) {
3193   if (!LOG_ENABLED()) {
3194     return;
3195   }
3196 
3197   nsTArray<RefPtr<nsHttpTransaction>>* trs = nullptr;
3198   uint32_t au, at, bu = 0, bt = 0;
3199 
3200   trs = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
3201   au = trs ? trs->Length() : 0;
3202   trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
3203   at = trs ? trs->Length() : 0;
3204 
3205   for (auto iter = mActiveTransactions[false].Iter(); !iter.Done();
3206        iter.Next()) {
3207     bu += iter.UserData()->Length();
3208   }
3209   bu -= au;
3210   for (auto iter = mActiveTransactions[true].Iter(); !iter.Done();
3211        iter.Next()) {
3212     bt += iter.UserData()->Length();
3213   }
3214   bt -= at;
3215 
3216   // Shows counts of:
3217   // - unthrottled transaction for the active tab
3218   // - throttled transaction for the active tab
3219   // - unthrottled transaction for background tabs
3220   // - throttled transaction for background tabs
3221   LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
3222 }
3223 
AddActiveTransaction(nsHttpTransaction * aTrans)3224 void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction* aTrans) {
3225   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3226 
3227   uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
3228   bool throttled = aTrans->EligibleForThrottling();
3229 
3230   nsTArray<RefPtr<nsHttpTransaction>>* transactions =
3231       mActiveTransactions[throttled].LookupOrAdd(tabId);
3232 
3233   MOZ_ASSERT(!transactions->Contains(aTrans));
3234 
3235   transactions->AppendElement(aTrans);
3236 
3237   LOG(("nsHttpConnectionMgr::AddActiveTransaction    t=%p tabid=%" PRIx64
3238        "(%d) thr=%d",
3239        aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId,
3240        throttled));
3241   LogActiveTransactions('+');
3242 
3243   if (tabId == mCurrentTopLevelOuterContentWindowId) {
3244     mActiveTabTransactionsExist = true;
3245     if (!throttled) {
3246       mActiveTabUnthrottledTransactionsExist = true;
3247     }
3248   }
3249 
3250   // Shift the throttling window to the future (actually, makes sure
3251   // that throttling will engage when there is anything to throttle.)
3252   // The |false| argument means we don't need this call to ensure
3253   // the ticker, since we do it just below.  Calling
3254   // EnsureThrottleTickerIfNeeded directly does a bit more than call
3255   // from inside of TouchThrottlingTimeWindow.
3256   TouchThrottlingTimeWindow(false);
3257 
3258   if (!mThrottleEnabled) {
3259     return;
3260   }
3261 
3262   EnsureThrottleTickerIfNeeded();
3263 }
3264 
RemoveActiveTransaction(nsHttpTransaction * aTrans,Maybe<bool> const & aOverride)3265 void nsHttpConnectionMgr::RemoveActiveTransaction(
3266     nsHttpTransaction* aTrans, Maybe<bool> const& aOverride) {
3267   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3268 
3269   uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
3270   bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
3271   bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling());
3272 
3273   nsTArray<RefPtr<nsHttpTransaction>>* transactions =
3274       mActiveTransactions[throttled].Get(tabId);
3275 
3276   if (!transactions || !transactions->RemoveElement(aTrans)) {
3277     // Was not tracked as active, probably just ignore.
3278     return;
3279   }
3280 
3281   LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
3282        "(%d) thr=%d",
3283        aTrans, tabId, forActiveTab, throttled));
3284 
3285   if (!transactions->IsEmpty()) {
3286     // There are still transactions of the type, hence nothing in the throttling
3287     // conditions has changed and we don't need to update "Exists" caches nor we
3288     // need to wake any now throttled transactions.
3289     LogActiveTransactions('-');
3290     return;
3291   }
3292 
3293   // To optimize the following logic, always remove the entry when the array is
3294   // empty.
3295   mActiveTransactions[throttled].Remove(tabId);
3296   LogActiveTransactions('-');
3297 
3298   if (forActiveTab) {
3299     // Update caches of the active tab transaction existence, since it's now
3300     // affected
3301     if (!throttled) {
3302       mActiveTabUnthrottledTransactionsExist = false;
3303     }
3304     if (mActiveTabTransactionsExist) {
3305       mActiveTabTransactionsExist =
3306           mActiveTransactions[!throttled].Contains(tabId);
3307     }
3308   }
3309 
3310   if (!mThrottleEnabled) {
3311     return;
3312   }
3313 
3314   bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
3315   bool throttledExist = !mActiveTransactions[true].IsEmpty();
3316 
3317   if (!unthrottledExist && !throttledExist) {
3318     // Nothing active globally, just get rid of the timer completely and we are
3319     // done.
3320     MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
3321     MOZ_ASSERT(!mActiveTabTransactionsExist);
3322 
3323     DestroyThrottleTicker();
3324     return;
3325   }
3326 
3327   if (mThrottleVersion == 1) {
3328     if (!mThrottlingInhibitsReading) {
3329       // There is then nothing to wake up.  Affected transactions will not be
3330       // put to sleep automatically on next tick.
3331       LOG(("  reading not currently inhibited"));
3332       return;
3333     }
3334   }
3335 
3336   if (mActiveTabUnthrottledTransactionsExist) {
3337     // There are still unthrottled transactions for the active tab, hence the
3338     // state is unaffected and we don't need to do anything (nothing to wake).
3339     LOG(("  there are unthrottled for the active tab"));
3340     return;
3341   }
3342 
3343   if (mActiveTabTransactionsExist) {
3344     // There are only trottled transactions for the active tab.
3345     // If the last transaction we just removed was a non-throttled for the
3346     // active tab we can wake the throttled transactions for the active tab.
3347     if (forActiveTab && !throttled) {
3348       LOG(("  resuming throttled for active tab"));
3349       ResumeReadOf(
3350           mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId));
3351     }
3352     return;
3353   }
3354 
3355   if (!unthrottledExist) {
3356     // There are no unthrottled transactions for any tab.  Resume all throttled,
3357     // all are only for background tabs.
3358     LOG(("  delay resuming throttled for background tabs"));
3359     DelayedResumeBackgroundThrottledTransactions();
3360     return;
3361   }
3362 
3363   if (forActiveTab) {
3364     // Removing the last transaction for the active tab frees up the unthrottled
3365     // background tabs transactions.
3366     LOG(("  delay resuming unthrottled for background tabs"));
3367     DelayedResumeBackgroundThrottledTransactions();
3368     return;
3369   }
3370 
3371   LOG(("  not resuming anything"));
3372 }
3373 
UpdateActiveTransaction(nsHttpTransaction * aTrans)3374 void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction* aTrans) {
3375   LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
3376 
3377   // First remove then add.  In case of a download that is the only active
3378   // transaction and has just been marked as download (goes unthrottled to
3379   // throttled), adding first would cause it to be throttled for first few
3380   // milliseconds - becuause it would appear as if there were both throttled
3381   // and unthrottled transactions at the time.
3382 
3383   Maybe<bool> reversed;
3384   reversed.emplace(!aTrans->EligibleForThrottling());
3385   RemoveActiveTransaction(aTrans, reversed);
3386 
3387   AddActiveTransaction(aTrans);
3388 
3389   LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
3390 }
3391 
ShouldThrottle(nsHttpTransaction * aTrans)3392 bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction* aTrans) {
3393   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3394 
3395   LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
3396 
3397   if (mThrottleVersion == 1) {
3398     if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
3399       return false;
3400     }
3401   } else {
3402     if (!mThrottleEnabled) {
3403       return false;
3404     }
3405   }
3406 
3407   uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
3408   bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
3409   bool throttled = aTrans->EligibleForThrottling();
3410 
3411   bool stop = [=]() {
3412     if (mActiveTabTransactionsExist) {
3413       if (!tabId) {
3414         // Chrome initiated and unidentified transactions just respect
3415         // their throttle flag, when something for the active tab is happening.
3416         // This also includes downloads.
3417         LOG(("  active tab loads, trans is tab-less, throttled=%d", throttled));
3418         return throttled;
3419       }
3420       if (!forActiveTab) {
3421         // This is a background tab request, we want them to always throttle
3422         // when there are transactions running for the ative tab.
3423         LOG(("  active tab loads, trans not of the active tab"));
3424         return true;
3425       }
3426 
3427       if (mActiveTabUnthrottledTransactionsExist) {
3428         // Unthrottled transactions for the active tab take precedence
3429         LOG(("  active tab loads unthrottled, trans throttled=%d", throttled));
3430         return throttled;
3431       }
3432 
3433       LOG(("  trans for active tab, don't throttle"));
3434       return false;
3435     }
3436 
3437     MOZ_ASSERT(!forActiveTab);
3438 
3439     if (!mActiveTransactions[false].IsEmpty()) {
3440       // This means there are unthrottled active transactions for background
3441       // tabs. If we are here, there can't be any transactions for the active
3442       // tab. (If there is no transaction for a tab id, there is no entry for it
3443       // in the hashtable.)
3444       LOG(("  backround tab(s) load unthrottled, trans throttled=%d",
3445            throttled));
3446       return throttled;
3447     }
3448 
3449     // There are only unthrottled transactions for background tabs: don't
3450     // throttle.
3451     LOG(("  backround tab(s) load throttled, don't throttle"));
3452     return false;
3453   }();
3454 
3455   if (forActiveTab && !stop) {
3456     // This is an active-tab transaction and is allowed to read.  Hence,
3457     // prolong the throttle time window to make sure all 'lower-decks'
3458     // transactions will actually throttle.
3459     TouchThrottlingTimeWindow();
3460     return false;
3461   }
3462 
3463   // Only stop reading when in the configured throttle max-time (aka time
3464   // window). This window is prolonged (restarted) by a call to
3465   // TouchThrottlingTimeWindow called on new transaction activation or on
3466   // receive of response bytes of an active tab transaction.
3467   bool inWindow = InThrottlingTimeWindow();
3468 
3469   LOG(("  stop=%d, in-window=%d, delayed-bck-timer=%d", stop, inWindow,
3470        !!mDelayedResumeReadTimer));
3471 
3472   if (!forActiveTab) {
3473     // If the delayed background resume timer exists, background transactions
3474     // are scheduled to be woken after a delay, hence leave them throttled.
3475     inWindow = inWindow || mDelayedResumeReadTimer;
3476   }
3477 
3478   return stop && inWindow;
3479 }
3480 
IsConnEntryUnderPressure(nsHttpConnectionInfo * connInfo)3481 bool nsHttpConnectionMgr::IsConnEntryUnderPressure(
3482     nsHttpConnectionInfo* connInfo) {
3483   nsConnectionEntry* ent = mCT.GetWeak(connInfo->HashKey());
3484   if (!ent) {
3485     // No entry, no pressure.
3486     return false;
3487   }
3488 
3489   nsTArray<RefPtr<PendingTransactionInfo>>* transactions =
3490       ent->mPendingTransactionTable.Get(mCurrentTopLevelOuterContentWindowId);
3491 
3492   return transactions && !transactions->IsEmpty();
3493 }
3494 
IsThrottleTickerNeeded()3495 bool nsHttpConnectionMgr::IsThrottleTickerNeeded() {
3496   LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
3497 
3498   if (mActiveTabUnthrottledTransactionsExist &&
3499       mActiveTransactions[false].Count() > 1) {
3500     LOG(("  there are unthrottled transactions for both active and bck"));
3501     return true;
3502   }
3503 
3504   if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) {
3505     LOG(("  there are throttled transactions for both active and bck"));
3506     return true;
3507   }
3508 
3509   if (!mActiveTransactions[true].IsEmpty() &&
3510       !mActiveTransactions[false].IsEmpty()) {
3511     LOG(("  there are both throttled and unthrottled transactions"));
3512     return true;
3513   }
3514 
3515   LOG(("  nothing to throttle"));
3516   return false;
3517 }
3518 
EnsureThrottleTickerIfNeeded()3519 void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
3520   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3521 
3522   LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
3523   if (!IsThrottleTickerNeeded()) {
3524     return;
3525   }
3526 
3527   // There is a new demand to throttle, hence unschedule delayed resume
3528   // of background throttled transastions.
3529   CancelDelayedResumeBackgroundThrottledTransactions();
3530 
3531   if (mThrottleTicker) {
3532     return;
3533   }
3534 
3535   mThrottleTicker = NS_NewTimer();
3536   if (mThrottleTicker) {
3537     if (mThrottleVersion == 1) {
3538       MOZ_ASSERT(!mThrottlingInhibitsReading);
3539 
3540       mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
3541       mThrottlingInhibitsReading = true;
3542     } else {
3543       mThrottleTicker->Init(this, mThrottleReadInterval,
3544                             nsITimer::TYPE_ONE_SHOT);
3545     }
3546   }
3547 
3548   LogActiveTransactions('^');
3549 }
3550 
DestroyThrottleTicker()3551 void nsHttpConnectionMgr::DestroyThrottleTicker() {
3552   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3553 
3554   // Nothing to throttle, hence no need for this timer anymore.
3555   CancelDelayedResumeBackgroundThrottledTransactions();
3556 
3557   MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
3558 
3559   if (!mThrottleTicker) {
3560     return;
3561   }
3562 
3563   LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
3564   mThrottleTicker->Cancel();
3565   mThrottleTicker = nullptr;
3566 
3567   if (mThrottleVersion == 1) {
3568     mThrottlingInhibitsReading = false;
3569   }
3570 
3571   LogActiveTransactions('v');
3572 }
3573 
ThrottlerTick()3574 void nsHttpConnectionMgr::ThrottlerTick() {
3575   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3576 
3577   if (mThrottleVersion == 1) {
3578     mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
3579 
3580     LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d",
3581          mThrottlingInhibitsReading));
3582 
3583     // If there are only background transactions to be woken after a delay, keep
3584     // the ticker so that we woke them only for the resume-for interval and then
3585     // throttle them again until the background-resume delay passes.
3586     if (!mThrottlingInhibitsReading && !mDelayedResumeReadTimer &&
3587         (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
3588       LOG(("  last tick"));
3589       mThrottleTicker = nullptr;
3590     }
3591 
3592     if (mThrottlingInhibitsReading) {
3593       if (mThrottleTicker) {
3594         mThrottleTicker->Init(this, mThrottleSuspendFor,
3595                               nsITimer::TYPE_ONE_SHOT);
3596       }
3597     } else {
3598       if (mThrottleTicker) {
3599         mThrottleTicker->Init(this, mThrottleResumeFor,
3600                               nsITimer::TYPE_ONE_SHOT);
3601       }
3602 
3603       ResumeReadOf(mActiveTransactions[false], true);
3604       ResumeReadOf(mActiveTransactions[true]);
3605     }
3606   } else {
3607     LOG(("nsHttpConnectionMgr::ThrottlerTick"));
3608 
3609     // If there are only background transactions to be woken after a delay, keep
3610     // the ticker so that we still keep the low read limit for that time.
3611     if (!mDelayedResumeReadTimer &&
3612         (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
3613       LOG(("  last tick"));
3614       mThrottleTicker = nullptr;
3615     }
3616 
3617     if (mThrottleTicker) {
3618       mThrottleTicker->Init(this, mThrottleReadInterval,
3619                             nsITimer::TYPE_ONE_SHOT);
3620     }
3621 
3622     ResumeReadOf(mActiveTransactions[false], true);
3623     ResumeReadOf(mActiveTransactions[true]);
3624   }
3625 }
3626 
DelayedResumeBackgroundThrottledTransactions()3627 void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() {
3628   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3629 
3630   if (mThrottleVersion == 1) {
3631     if (mDelayedResumeReadTimer) {
3632       return;
3633     }
3634   } else {
3635     // If the mThrottleTicker doesn't exist, there is nothing currently
3636     // being throttled.  Hence, don't invoke the hold time interval.
3637     // This is called also when a single download transaction becomes
3638     // marked as throttleable.  We would otherwise block it unnecessarily.
3639     if (mDelayedResumeReadTimer || !mThrottleTicker) {
3640       return;
3641     }
3642   }
3643 
3644   LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
3645   NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this,
3646                           mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
3647 }
3648 
CancelDelayedResumeBackgroundThrottledTransactions()3649 void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
3650   if (!mDelayedResumeReadTimer) {
3651     return;
3652   }
3653 
3654   LOG(
3655       ("nsHttpConnectionMgr::"
3656        "CancelDelayedResumeBackgroundThrottledTransactions"));
3657   mDelayedResumeReadTimer->Cancel();
3658   mDelayedResumeReadTimer = nullptr;
3659 }
3660 
ResumeBackgroundThrottledTransactions()3661 void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() {
3662   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3663 
3664   LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
3665   mDelayedResumeReadTimer = nullptr;
3666 
3667   if (!IsThrottleTickerNeeded()) {
3668     DestroyThrottleTicker();
3669   }
3670 
3671   if (!mActiveTransactions[false].IsEmpty()) {
3672     ResumeReadOf(mActiveTransactions[false], true);
3673   } else {
3674     ResumeReadOf(mActiveTransactions[true], true);
3675   }
3676 }
3677 
ResumeReadOf(nsClassHashtable<nsUint64HashKey,nsTArray<RefPtr<nsHttpTransaction>>> & hashtable,bool excludeForActiveTab)3678 void nsHttpConnectionMgr::ResumeReadOf(
3679     nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&
3680         hashtable,
3681     bool excludeForActiveTab) {
3682   for (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
3683     if (excludeForActiveTab &&
3684         iter.Key() == mCurrentTopLevelOuterContentWindowId) {
3685       // These have never been throttled (never stopped reading)
3686       continue;
3687     }
3688     ResumeReadOf(iter.UserData());
3689   }
3690 }
3691 
ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>> * transactions)3692 void nsHttpConnectionMgr::ResumeReadOf(
3693     nsTArray<RefPtr<nsHttpTransaction>>* transactions) {
3694   MOZ_ASSERT(transactions);
3695 
3696   for (const auto& trans : *transactions) {
3697     trans->ResumeReading();
3698   }
3699 }
3700 
NotifyConnectionOfWindowIdChange(uint64_t previousWindowId)3701 void nsHttpConnectionMgr::NotifyConnectionOfWindowIdChange(
3702     uint64_t previousWindowId) {
3703   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3704 
3705   nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
3706   nsTArray<RefPtr<nsAHttpConnection>> connections;
3707 
3708   auto addConnectionHelper =
3709       [&connections](nsTArray<RefPtr<nsHttpTransaction>>* trans) {
3710         if (!trans) {
3711           return;
3712         }
3713 
3714         for (const auto& t : *trans) {
3715           RefPtr<nsAHttpConnection> conn = t->Connection();
3716           if (conn && !connections.Contains(conn)) {
3717             connections.AppendElement(conn);
3718           }
3719         }
3720       };
3721 
3722   // Get unthrottled transactions with the previous and current window id.
3723   transactions = mActiveTransactions[false].Get(previousWindowId);
3724   addConnectionHelper(transactions);
3725   transactions =
3726       mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
3727   addConnectionHelper(transactions);
3728 
3729   // Get throttled transactions with the previous and current window id.
3730   transactions = mActiveTransactions[true].Get(previousWindowId);
3731   addConnectionHelper(transactions);
3732   transactions =
3733       mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
3734   addConnectionHelper(transactions);
3735 
3736   for (const auto& conn : connections) {
3737     conn->TopLevelOuterContentWindowIdChanged(
3738         mCurrentTopLevelOuterContentWindowId);
3739   }
3740 }
3741 
OnMsgUpdateCurrentTopLevelOuterContentWindowId(int32_t aLoading,ARefBase * param)3742 void nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
3743     int32_t aLoading, ARefBase* param) {
3744   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3745 
3746   uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
3747 
3748   if (mCurrentTopLevelOuterContentWindowId == winId) {
3749     // duplicate notification
3750     return;
3751   }
3752 
3753   bool activeTabWasLoading = mActiveTabTransactionsExist;
3754 
3755   uint64_t previousWindowId = mCurrentTopLevelOuterContentWindowId;
3756   mCurrentTopLevelOuterContentWindowId = winId;
3757 
3758   if (gHttpHandler->ActiveTabPriority()) {
3759     NotifyConnectionOfWindowIdChange(previousWindowId);
3760   }
3761 
3762   LOG(
3763       ("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
3764        " id=%" PRIx64 "\n",
3765        mCurrentTopLevelOuterContentWindowId));
3766 
3767   nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
3768 
3769   // Update the "Exists" caches and resume any transactions that now deserve it,
3770   // changing the active tab changes the conditions for throttling.
3771   transactions =
3772       mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
3773   mActiveTabUnthrottledTransactionsExist = !!transactions;
3774 
3775   if (!mActiveTabUnthrottledTransactionsExist) {
3776     transactions =
3777         mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
3778   }
3779   mActiveTabTransactionsExist = !!transactions;
3780 
3781   if (transactions) {
3782     // This means there are some transactions for this newly activated tab,
3783     // resume them but anything else.
3784     LOG(("  resuming newly activated tab transactions"));
3785     ResumeReadOf(transactions);
3786     return;
3787   }
3788 
3789   if (!activeTabWasLoading) {
3790     // There were no transactions for the previously active tab, hence
3791     // all remaning transactions, if there were, were all unthrottled,
3792     // no need to wake them.
3793     return;
3794   }
3795 
3796   if (!mActiveTransactions[false].IsEmpty()) {
3797     LOG(("  resuming unthrottled background transactions"));
3798     ResumeReadOf(mActiveTransactions[false]);
3799     return;
3800   }
3801 
3802   if (!mActiveTransactions[true].IsEmpty()) {
3803     LOG(("  resuming throttled background transactions"));
3804     ResumeReadOf(mActiveTransactions[true]);
3805     return;
3806   }
3807 
3808   DestroyThrottleTicker();
3809 }
3810 
TimeoutTick()3811 void nsHttpConnectionMgr::TimeoutTick() {
3812   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3813   MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
3814 
3815   LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
3816   // The next tick will be between 1 second and 1 hr
3817   // Set it to the max value here, and the TimeoutTick()s can
3818   // reduce it to their local needs.
3819   mTimeoutTickNext = 3600;  // 1hr
3820 
3821   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
3822     RefPtr<nsConnectionEntry> ent = iter.Data();
3823 
3824     if (!ent->mConnInfo->IsHttp3()) {
3825       LOG(
3826           ("nsHttpConnectionMgr::TimeoutTick() this=%p host=%s "
3827            "idle=%zu active=%zu"
3828            " half-len=%zu pending=%zu"
3829            " urgentStart pending=%zu\n",
3830            this, ent->mConnInfo->Origin(), ent->mIdleConns.Length(),
3831            ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
3832            ent->PendingQLength(), ent->mUrgentStartQ.Length()));
3833 
3834       // First call the tick handler for each active connection.
3835       PRIntervalTime tickTime = PR_IntervalNow();
3836       for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
3837         RefPtr<nsHttpConnection> conn =
3838             do_QueryObject(ent->mActiveConns[index]);
3839         if (conn) {
3840           uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime);
3841           mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
3842         }
3843       }
3844 
3845       // Now check for any stalled half open sockets.
3846       if (ent->mHalfOpens.Length()) {
3847         TimeStamp currentTime = TimeStamp::Now();
3848         double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
3849 
3850         for (uint32_t index = ent->mHalfOpens.Length(); index > 0;) {
3851           index--;
3852 
3853           nsHalfOpenSocket* half = ent->mHalfOpens[index];
3854           double delta = half->Duration(currentTime);
3855           // If the socket has timed out, close it so the waiting
3856           // transaction will get the proper signal.
3857           if (delta > maxConnectTime_ms) {
3858             LOG(("Force timeout of half open to %s after %.2fms.\n",
3859                  ent->mConnInfo->HashKey().get(), delta));
3860             if (half->SocketTransport()) {
3861               half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
3862             }
3863             if (half->BackupTransport()) {
3864               half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
3865             }
3866           }
3867 
3868           // If this half open hangs around for 5 seconds after we've
3869           // closed() it then just abandon the socket.
3870           if (delta > maxConnectTime_ms + 5000) {
3871             LOG(("Abandon half open to %s after %.2fms.\n",
3872                  ent->mConnInfo->HashKey().get(), delta));
3873             half->Abandon();
3874           }
3875         }
3876       }
3877       if (ent->mHalfOpens.Length()) {
3878         mTimeoutTickNext = 1;
3879       }
3880     }
3881   }
3882 
3883   if (mTimeoutTick) {
3884     mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
3885     mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
3886   }
3887 }
3888 
3889 // GetOrCreateConnectionEntry finds a ent for a particular CI for use in
3890 // dispatching a transaction according to these rules
3891 // 1] use an ent that matches the ci that can be dispatched immediately
3892 // 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
3893 // 3] otherwise create an ent that matches ci and make new conn on it
3894 
3895 nsHttpConnectionMgr::nsConnectionEntry*
GetOrCreateConnectionEntry(nsHttpConnectionInfo * specificCI,bool prohibitWildCard,bool aNoHttp3)3896 nsHttpConnectionMgr::GetOrCreateConnectionEntry(
3897     nsHttpConnectionInfo* specificCI, bool prohibitWildCard, bool aNoHttp3) {
3898   // step 1
3899   nsConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
3900   if (specificEnt && specificEnt->AvailableForDispatchNow()) {
3901     return specificEnt;
3902   }
3903 
3904   // step 1 repeated for an inverted anonymous flag; we return an entry
3905   // only when it has an h2 established connection that is not authenticated
3906   // with a client certificate.
3907   RefPtr<nsHttpConnectionInfo> anonInvertedCI(specificCI->Clone());
3908   anonInvertedCI->SetAnonymous(!specificCI->GetAnonymous());
3909   nsConnectionEntry* invertedEnt = mCT.GetWeak(anonInvertedCI->HashKey());
3910   if (invertedEnt) {
3911     HttpConnectionBase* h2orh3conn = GetH2orH3ActiveConn(invertedEnt, aNoHttp3);
3912     if (h2orh3conn && h2orh3conn->IsExperienced() &&
3913         h2orh3conn->NoClientCertAuth()) {
3914       MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3());
3915       LOG(
3916           ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
3917            "connections, ent=%p",
3918            invertedEnt));
3919       return invertedEnt;
3920     }
3921   }
3922 
3923   if (!specificCI->UsingHttpsProxy()) {
3924     prohibitWildCard = true;
3925   }
3926 
3927   // step 2
3928   if (!prohibitWildCard && !aNoHttp3) {
3929     RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
3930     DebugOnly<nsresult> rv =
3931         specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
3932     MOZ_ASSERT(NS_SUCCEEDED(rv));
3933     nsConnectionEntry* wildCardEnt = mCT.GetWeak(wildCardProxyCI->HashKey());
3934     if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
3935       return wildCardEnt;
3936     }
3937   }
3938 
3939   // step 3
3940   if (!specificEnt) {
3941     RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
3942     specificEnt = new nsConnectionEntry(clone);
3943     mCT.Put(clone->HashKey(), RefPtr{specificEnt});
3944   }
3945   return specificEnt;
3946 }
3947 
OnHeadersAvailable(nsAHttpTransaction * trans,nsHttpRequestHead * req,nsHttpResponseHead * resp,bool * reset)3948 nsresult ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction* trans,
3949                                               nsHttpRequestHead* req,
3950                                               nsHttpResponseHead* resp,
3951                                               bool* reset) {
3952   return mConn->OnHeadersAvailable(trans, req, resp, reset);
3953 }
3954 
CloseTransaction(nsAHttpTransaction * trans,nsresult reason)3955 void ConnectionHandle::CloseTransaction(nsAHttpTransaction* trans,
3956                                         nsresult reason) {
3957   mConn->CloseTransaction(trans, reason);
3958 }
3959 
TakeTransport(nsISocketTransport ** aTransport,nsIAsyncInputStream ** aInputStream,nsIAsyncOutputStream ** aOutputStream)3960 nsresult ConnectionHandle::TakeTransport(nsISocketTransport** aTransport,
3961                                          nsIAsyncInputStream** aInputStream,
3962                                          nsIAsyncOutputStream** aOutputStream) {
3963   return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
3964 }
3965 
OnMsgSpeculativeConnect(int32_t,ARefBase * param)3966 void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase* param) {
3967   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3968 
3969   SpeculativeConnectArgs* args = static_cast<SpeculativeConnectArgs*>(param);
3970 
3971   LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
3972        args->mTrans->ConnectionInfo()->HashKey().get()));
3973 
3974   nsConnectionEntry* ent =
3975       GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false,
3976                                  args->mTrans->Caps() & NS_HTTP_DISALLOW_HTTP3);
3977 
3978   uint32_t parallelSpeculativeConnectLimit =
3979       gHttpHandler->ParallelSpeculativeConnectLimit();
3980   bool ignoreIdle = false;
3981   bool isFromPredictor = false;
3982   bool allow1918 = false;
3983 
3984   if (args->mOverridesOK) {
3985     parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
3986     ignoreIdle = args->mIgnoreIdle;
3987     isFromPredictor = args->mIsFromPredictor;
3988     allow1918 = args->mAllow1918;
3989   }
3990 
3991   bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
3992   if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
3993       ((ignoreIdle &&
3994         (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
3995        !ent->mIdleConns.Length()) &&
3996       !(keepAlive && RestrictConnections(ent)) &&
3997       !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
3998     DebugOnly<nsresult> rv =
3999         CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true,
4000                         isFromPredictor, false, allow1918, nullptr);
4001     MOZ_ASSERT(NS_SUCCEEDED(rv));
4002   } else {
4003     LOG(
4004         ("OnMsgSpeculativeConnect Transport "
4005          "not created due to existing connection count\n"));
4006   }
4007 }
4008 
IsPersistent()4009 bool ConnectionHandle::IsPersistent() {
4010   MOZ_ASSERT(OnSocketThread());
4011   return mConn->IsPersistent();
4012 }
4013 
IsReused()4014 bool ConnectionHandle::IsReused() {
4015   MOZ_ASSERT(OnSocketThread());
4016   return mConn->IsReused();
4017 }
4018 
DontReuse()4019 void ConnectionHandle::DontReuse() {
4020   MOZ_ASSERT(OnSocketThread());
4021   mConn->DontReuse();
4022 }
4023 
PushBack(const char * buf,uint32_t bufLen)4024 nsresult ConnectionHandle::PushBack(const char* buf, uint32_t bufLen) {
4025   return mConn->PushBack(buf, bufLen);
4026 }
4027 
4028 //////////////////////// nsHalfOpenSocket
4029 NS_IMPL_ADDREF(nsHttpConnectionMgr::nsHalfOpenSocket)
4030 NS_IMPL_RELEASE(nsHttpConnectionMgr::nsHalfOpenSocket)
4031 
4032 NS_INTERFACE_MAP_BEGIN(nsHttpConnectionMgr::nsHalfOpenSocket)
4033   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
4034   NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
4035   NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
4036   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
4037   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
4038   NS_INTERFACE_MAP_ENTRY(nsINamed)
4039   // we have no macro that covers this case.
4040   if (aIID.Equals(NS_GET_IID(nsHttpConnectionMgr::nsHalfOpenSocket))) {
4041     AddRef();
4042     *aInstancePtr = this;
4043     return NS_OK;
4044   } else
4045 NS_INTERFACE_MAP_END
4046 
nsHalfOpenSocket(nsConnectionEntry * ent,nsAHttpTransaction * trans,uint32_t caps,bool speculative,bool isFromPredictor,bool urgentStart)4047 nsHttpConnectionMgr::nsHalfOpenSocket::nsHalfOpenSocket(
4048     nsConnectionEntry* ent, nsAHttpTransaction* trans, uint32_t caps,
4049     bool speculative, bool isFromPredictor, bool urgentStart)
4050     : mTransaction(trans),
4051       mDispatchedMTransaction(false),
4052       mCaps(caps),
4053       mSpeculative(speculative),
4054       mUrgentStart(urgentStart),
4055       mIsFromPredictor(isFromPredictor),
4056       mAllow1918(true),
4057       mHasConnected(false),
4058       mPrimaryConnectedOK(false),
4059       mBackupConnectedOK(false),
4060       mBackupConnStatsSet(false),
4061       mFreeToUse(true),
4062       mPrimaryStreamStatus(NS_OK),
4063       mFastOpenInProgress(false),
4064       mEnt(ent) {
4065   MOZ_ASSERT(ent && trans, "constructor with null arguments");
4066   LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n", this,
4067        trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
4068 
4069   mIsHttp3 = mEnt->mConnInfo->IsHttp3();
4070   if (speculative) {
4071     Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN>
4072         totalSpeculativeConn;
4073     ++totalSpeculativeConn;
4074 
4075     if (isFromPredictor) {
4076       Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED>
4077           totalPreconnectsCreated;
4078       ++totalPreconnectsCreated;
4079     }
4080   }
4081 
4082   if (mEnt->mConnInfo->FirstHopSSL()) {
4083     mFastOpenStatus = TFO_UNKNOWN;
4084   } else {
4085     mFastOpenStatus = TFO_HTTP;
4086   }
4087   MOZ_ASSERT(mEnt);
4088 }
4089 
~nsHalfOpenSocket()4090 nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket() {
4091   MOZ_ASSERT(!mStreamOut);
4092   MOZ_ASSERT(!mBackupStreamOut);
4093   LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
4094 
4095   if (mEnt) mEnt->RemoveHalfOpen(this);
4096 }
4097 
BeConservativeIfProxied(nsIProxyInfo * proxy)4098 bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo* proxy) {
4099   if (mBeConservativeForProxy) {
4100     // The pref says to be conservative for proxies.
4101     return true;
4102   }
4103 
4104   if (!proxy) {
4105     // There is no proxy, so be conservative by default.
4106     return true;
4107   }
4108 
4109   // Be conservative only if there is no proxy host set either.
4110   // This logic was copied from nsSSLIOLayerAddToSocket.
4111   nsAutoCString proxyHost;
4112   proxy->GetHost(proxyHost);
4113   return proxyHost.IsEmpty();
4114 }
4115 
SetupStreams(nsISocketTransport ** transport,nsIAsyncInputStream ** instream,nsIAsyncOutputStream ** outstream,bool isBackup)4116 nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupStreams(
4117     nsISocketTransport** transport, nsIAsyncInputStream** instream,
4118     nsIAsyncOutputStream** outstream, bool isBackup) {
4119   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4120 
4121   MOZ_ASSERT(mEnt);
4122   nsresult rv;
4123   nsTArray<nsCString> socketTypes;
4124   const nsHttpConnectionInfo* ci = mEnt->mConnInfo;
4125   if (mIsHttp3) {
4126     socketTypes.AppendElement(NS_LITERAL_CSTRING("quic"));
4127   } else {
4128     if (ci->FirstHopSSL()) {
4129       socketTypes.AppendElement(NS_LITERAL_CSTRING("ssl"));
4130     } else {
4131       const nsCString& defaultType = gHttpHandler->DefaultSocketType();
4132       if (!defaultType.IsVoid()) {
4133         socketTypes.AppendElement(defaultType);
4134       }
4135     }
4136   }
4137 
4138   nsCOMPtr<nsISocketTransport> socketTransport;
4139   nsCOMPtr<nsISocketTransportService> sts;
4140 
4141   sts = services::GetSocketTransportService();
4142   if (!sts) {
4143     return NS_ERROR_NOT_AVAILABLE;
4144   }
4145 
4146   LOG(
4147       ("nsHalfOpenSocket::SetupStreams [this=%p ent=%s] "
4148        "setup routed transport to origin %s:%d via %s:%d\n",
4149        this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
4150        ci->RoutedHost(), ci->RoutedPort()));
4151 
4152   nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
4153   if (routedSTS) {
4154     rv = routedSTS->CreateRoutedTransport(
4155         socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
4156         ci->RoutedPort(), ci->ProxyInfo(), getter_AddRefs(socketTransport));
4157   } else {
4158     if (!ci->GetRoutedHost().IsEmpty()) {
4159       // There is a route requested, but the legacy nsISocketTransportService
4160       // can't handle it.
4161       // Origin should be reachable on origin host name, so this should
4162       // not be a problem - but log it.
4163       LOG(
4164           ("nsHalfOpenSocket this=%p using legacy nsISocketTransportService "
4165            "means explicit route %s:%d will be ignored.\n",
4166            this, ci->RoutedHost(), ci->RoutedPort()));
4167     }
4168 
4169     rv = sts->CreateTransport(socketTypes, ci->GetOrigin(), ci->OriginPort(),
4170                               ci->ProxyInfo(), getter_AddRefs(socketTransport));
4171   }
4172   NS_ENSURE_SUCCESS(rv, rv);
4173 
4174   uint32_t tmpFlags = 0;
4175   if (mCaps & NS_HTTP_REFRESH_DNS) tmpFlags = nsISocketTransport::BYPASS_CACHE;
4176 
4177   tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
4178       NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));
4179 
4180   if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
4181     tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
4182 
4183   if (ci->GetPrivate() || ci->GetIsolated()) {
4184     tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
4185   }
4186 
4187   if (ci->GetLessThanTls13()) {
4188     tmpFlags |= nsISocketTransport::DONT_TRY_ESNI;
4189   }
4190 
4191   if (((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) &&
4192       gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
4193     LOG(("Setting Socket to BE_CONSERVATIVE"));
4194     tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
4195   }
4196 
4197   if (mCaps & NS_HTTP_DISABLE_IPV4) {
4198     tmpFlags |= nsISocketTransport::DISABLE_IPV4;
4199   } else if (mCaps & NS_HTTP_DISABLE_IPV6) {
4200     tmpFlags |= nsISocketTransport::DISABLE_IPV6;
4201   } else if (mEnt->PreferenceKnown()) {
4202     if (mEnt->mPreferIPv6) {
4203       tmpFlags |= nsISocketTransport::DISABLE_IPV4;
4204     } else if (mEnt->mPreferIPv4) {
4205       tmpFlags |= nsISocketTransport::DISABLE_IPV6;
4206     }
4207 
4208     // In case the host is no longer accessible via the preferred IP family,
4209     // try the opposite one and potentially restate the preference.
4210     tmpFlags |= nsISocketTransport::RETRY_WITH_DIFFERENT_IP_FAMILY;
4211 
4212     // From the same reason, let the backup socket fail faster to try the other
4213     // family.
4214     uint16_t fallbackTimeout =
4215         isBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
4216     if (fallbackTimeout) {
4217       socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
4218                                   fallbackTimeout);
4219     }
4220   } else if (isBackup && gHttpHandler->FastFallbackToIPv4()) {
4221     // For backup connections, we disable IPv6. That's because some users have
4222     // broken IPv6 connectivity (leading to very long timeouts), and disabling
4223     // IPv6 on the backup connection gives them a much better user experience
4224     // with dual-stack hosts, though they still pay the 250ms delay for each new
4225     // connection. This strategy is also known as "happy eyeballs".
4226     tmpFlags |= nsISocketTransport::DISABLE_IPV6;
4227   }
4228 
4229   if (!Allow1918()) {
4230     tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
4231   }
4232 
4233   if ((mFastOpenStatus != TFO_HTTP) && !isBackup) {
4234     if (mEnt->mUseFastOpen) {
4235       socketTransport->SetFastOpenCallback(this);
4236     } else {
4237       mFastOpenStatus = TFO_DISABLED;
4238     }
4239   }
4240 
4241   MOZ_ASSERT(!(tmpFlags & nsISocketTransport::DISABLE_IPV4) ||
4242                  !(tmpFlags & nsISocketTransport::DISABLE_IPV6),
4243              "Both types should not be disabled at the same time.");
4244   socketTransport->SetConnectionFlags(tmpFlags);
4245   socketTransport->SetTlsFlags(ci->GetTlsFlags());
4246 
4247   const OriginAttributes& originAttributes =
4248       mEnt->mConnInfo->GetOriginAttributes();
4249   if (originAttributes != OriginAttributes()) {
4250     socketTransport->SetOriginAttributes(originAttributes);
4251   }
4252 
4253   socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
4254 
4255   rv = socketTransport->SetEventSink(this, nullptr);
4256   NS_ENSURE_SUCCESS(rv, rv);
4257 
4258   rv = socketTransport->SetSecurityCallbacks(this);
4259   NS_ENSURE_SUCCESS(rv, rv);
4260 
4261   Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
4262                         mEnt->mUsedForConnection);
4263   mEnt->mUsedForConnection = true;
4264 
4265   nsCOMPtr<nsIOutputStream> sout;
4266   rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
4267                                          getter_AddRefs(sout));
4268   NS_ENSURE_SUCCESS(rv, rv);
4269 
4270   nsCOMPtr<nsIInputStream> sin;
4271   rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
4272                                         getter_AddRefs(sin));
4273   NS_ENSURE_SUCCESS(rv, rv);
4274 
4275   socketTransport.forget(transport);
4276   CallQueryInterface(sin, instream);
4277   CallQueryInterface(sout, outstream);
4278 
4279   rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
4280   if (NS_SUCCEEDED(rv)) gHttpHandler->ConnMgr()->StartedConnect();
4281 
4282   return rv;
4283 }
4284 
SetupPrimaryStreams()4285 nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() {
4286   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4287 
4288   nsresult rv;
4289 
4290   mPrimarySynStarted = TimeStamp::Now();
4291   rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn),
4292                     getter_AddRefs(mStreamOut), false);
4293 
4294   LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%" PRIx32 "]",
4295        this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv)));
4296   if (NS_FAILED(rv)) {
4297     if (mStreamOut) mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
4298     if (mSocketTransport) {
4299       mSocketTransport->SetFastOpenCallback(nullptr);
4300     }
4301     mStreamOut = nullptr;
4302     mStreamIn = nullptr;
4303     mSocketTransport = nullptr;
4304   }
4305   return rv;
4306 }
4307 
SetupBackupStreams()4308 nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams() {
4309   MOZ_ASSERT(mTransaction);
4310 
4311   mBackupSynStarted = TimeStamp::Now();
4312   nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
4313                              getter_AddRefs(mBackupStreamIn),
4314                              getter_AddRefs(mBackupStreamOut), true);
4315 
4316   LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%" PRIx32 "]",
4317        this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv)));
4318   if (NS_FAILED(rv)) {
4319     if (mBackupStreamOut) mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
4320     mBackupStreamOut = nullptr;
4321     mBackupStreamIn = nullptr;
4322     mBackupTransport = nullptr;
4323   }
4324   return rv;
4325 }
4326 
SetupBackupTimer()4327 void nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() {
4328   MOZ_ASSERT(mEnt);
4329   uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
4330   MOZ_ASSERT(!mSynTimer, "timer already initd");
4331   if (!timeout && mFastOpenInProgress) {
4332     timeout = 250;
4333   }
4334   // When using Fast Open the correct transport will be setup for sure (it is
4335   // guaranteed), but it can be that it will happened a bit later.
4336   if (mFastOpenInProgress || (timeout && !mSpeculative)) {
4337     // Setup the timer that will establish a backup socket
4338     // if we do not get a writable event on the main one.
4339     // We do this because a lost SYN takes a very long time
4340     // to repair at the TCP level.
4341     //
4342     // Failure to setup the timer is something we can live with,
4343     // so don't return an error in that case.
4344     NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
4345                             nsITimer::TYPE_ONE_SHOT);
4346     LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
4347   } else if (timeout) {
4348     LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n",
4349          this));
4350   }
4351 }
4352 
CancelBackupTimer()4353 void nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer() {
4354   // If the syntimer is still armed, we can cancel it because no backup
4355   // socket should be formed at this point
4356   if (!mSynTimer) return;
4357 
4358   LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
4359   mSynTimer->Cancel();
4360 
4361   // Keeping the reference to the timer to remember we have already
4362   // performed the backup connection.
4363 }
4364 
Abandon()4365 void nsHttpConnectionMgr::nsHalfOpenSocket::Abandon() {
4366   LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
4367        mEnt->mConnInfo->Origin(), mSocketTransport.get(),
4368        mBackupTransport.get(), mStreamOut.get(), mBackupStreamOut.get()));
4369 
4370   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4371 
4372   RefPtr<nsHalfOpenSocket> deleteProtector(this);
4373 
4374   // Tell socket (and backup socket) to forget the half open socket.
4375   if (mSocketTransport) {
4376     mSocketTransport->SetEventSink(nullptr, nullptr);
4377     mSocketTransport->SetSecurityCallbacks(nullptr);
4378     mSocketTransport->SetFastOpenCallback(nullptr);
4379     mSocketTransport = nullptr;
4380   }
4381   if (mBackupTransport) {
4382     mBackupTransport->SetEventSink(nullptr, nullptr);
4383     mBackupTransport->SetSecurityCallbacks(nullptr);
4384     mBackupTransport = nullptr;
4385   }
4386 
4387   // Tell output stream (and backup) to forget the half open socket.
4388   if (mStreamOut) {
4389     if (!mFastOpenInProgress) {
4390       // If mFastOpenInProgress is true HalfOpen are not in mHalfOpen
4391       // list and are not counted so we do not need to decrease counter.
4392       gHttpHandler->ConnMgr()->RecvdConnect();
4393     }
4394     mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
4395     mStreamOut = nullptr;
4396   }
4397   if (mBackupStreamOut) {
4398     gHttpHandler->ConnMgr()->RecvdConnect();
4399     mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
4400     mBackupStreamOut = nullptr;
4401   }
4402 
4403   // Lose references to input stream (and backup).
4404   if (mStreamIn) {
4405     mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
4406     mStreamIn = nullptr;
4407   }
4408   if (mBackupStreamIn) {
4409     mBackupStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
4410     mBackupStreamIn = nullptr;
4411   }
4412 
4413   // Stop the timer - we don't want any new backups.
4414   CancelBackupTimer();
4415 
4416   // Remove the half open from the connection entry.
4417   if (mEnt) {
4418     mEnt->mDoNotDestroy = false;
4419     mEnt->RemoveHalfOpen(this);
4420   }
4421   mEnt = nullptr;
4422 }
4423 
Duration(TimeStamp epoch)4424 double nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch) {
4425   if (mPrimarySynStarted.IsNull()) return 0;
4426 
4427   return (epoch - mPrimarySynStarted).ToMilliseconds();
4428 }
4429 
4430 NS_IMETHODIMP  // method for nsITimerCallback
Notify(nsITimer * timer)4431 nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer* timer) {
4432   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4433   MOZ_ASSERT(timer == mSynTimer, "wrong timer");
4434 
4435   MOZ_ASSERT(!mBackupTransport);
4436   MOZ_ASSERT(mSynTimer);
4437   MOZ_ASSERT(mEnt);
4438 
4439   DebugOnly<nsresult> rv = SetupBackupStreams();
4440   MOZ_ASSERT(NS_SUCCEEDED(rv));
4441 
4442   // Keeping the reference to the timer to remember we have already
4443   // performed the backup connection.
4444 
4445   return NS_OK;
4446 }
4447 
4448 NS_IMETHODIMP  // method for nsINamed
GetName(nsACString & aName)4449 nsHttpConnectionMgr::nsHalfOpenSocket::GetName(nsACString& aName) {
4450   aName.AssignLiteral("nsHttpConnectionMgr::nsHalfOpenSocket");
4451   return NS_OK;
4452 }
4453 
4454 already_AddRefed<nsHttpConnectionMgr::PendingTransactionInfo>
FindTransactionHelper(bool removeWhenFound)4455 nsHttpConnectionMgr::nsHalfOpenSocket::FindTransactionHelper(
4456     bool removeWhenFound) {
4457   nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
4458       gHttpHandler->ConnMgr()->GetTransactionPendingQHelper(mEnt, mTransaction);
4459 
4460   int32_t index =
4461       pendingQ ? pendingQ->IndexOf(mTransaction, 0, PendingComparator()) : -1;
4462 
4463   RefPtr<PendingTransactionInfo> info;
4464   if (index != -1) {
4465     info = (*pendingQ)[index];
4466     if (removeWhenFound) {
4467       pendingQ->RemoveElementAt(index);
4468     }
4469   }
4470   return info.forget();
4471 }
4472 
4473 // method for nsIAsyncOutputStreamCallback
4474 NS_IMETHODIMP
OnOutputStreamReady(nsIAsyncOutputStream * out)4475 nsHttpConnectionMgr::nsHalfOpenSocket::OnOutputStreamReady(
4476     nsIAsyncOutputStream* out) {
4477   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4478   MOZ_ASSERT(mStreamOut || mBackupStreamOut);
4479   MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut, "stream mismatch");
4480   MOZ_ASSERT(mEnt);
4481 
4482   LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
4483        mEnt->mConnInfo->Origin(), out == mStreamOut ? "primary" : "backup"));
4484 
4485   mEnt->mDoNotDestroy = true;
4486   gHttpHandler->ConnMgr()->RecvdConnect();
4487 
4488   CancelBackupTimer();
4489 
4490   if (mFastOpenInProgress) {
4491     LOG(
4492         ("nsHalfOpenSocket::OnOutputStreamReady backup stream is ready, "
4493          "close the fast open socket %p [this=%p ent=%s]\n",
4494          mSocketTransport.get(), this, mEnt->mConnInfo->Origin()));
4495     // If fast open is used, right after a socket for the primary stream is
4496     // created a HttpConnectionBase is created for that socket. The connection
4497     // listens for  OnOutputStreamReady not HalfOpenSocket. So this stream
4498     // cannot be mStreamOut.
4499     MOZ_ASSERT((out == mBackupStreamOut) && mConnectionNegotiatingFastOpen);
4500     // Here the backup, non-TFO connection has connected successfully,
4501     // before the TFO connection.
4502     //
4503     // The primary, TFO connection will be cancelled and the transaction
4504     // will be rewind. CloseConnectionFastOpenTakesTooLongOrError will
4505     // return the rewind transaction. The transaction will be put back to
4506     // the pending queue and as well connected to this halfOpenSocket.
4507     // SetupConn should set up a new HttpConnectionBase with the backup
4508     // socketTransport and the rewind transaction.
4509     mSocketTransport->SetFastOpenCallback(nullptr);
4510     mConnectionNegotiatingFastOpen->SetFastOpen(false);
4511     mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
4512     RefPtr<nsAHttpTransaction> trans =
4513         mConnectionNegotiatingFastOpen
4514             ->CloseConnectionFastOpenTakesTooLongOrError(true);
4515     mSocketTransport = nullptr;
4516     mStreamOut = nullptr;
4517     mStreamIn = nullptr;
4518 
4519     if (trans && trans->QueryHttpTransaction()) {
4520       RefPtr<PendingTransactionInfo> pendingTransInfo =
4521           new PendingTransactionInfo(trans->QueryHttpTransaction());
4522       pendingTransInfo->mHalfOpen =
4523           do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this));
4524       if (trans->Caps() & NS_HTTP_URGENT_START) {
4525         gHttpHandler->ConnMgr()->InsertTransactionSorted(
4526             mEnt->mUrgentStartQ, pendingTransInfo, true);
4527       } else {
4528         mEnt->InsertTransaction(pendingTransInfo, true);
4529       }
4530     }
4531     if (mEnt->mUseFastOpen) {
4532       gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
4533       mEnt->mUseFastOpen = false;
4534     }
4535 
4536     mFastOpenInProgress = false;
4537     mConnectionNegotiatingFastOpen = nullptr;
4538     if (mFastOpenStatus == TFO_NOT_TRIED) {
4539       mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED;
4540     } else if (mFastOpenStatus == TFO_TRIED) {
4541       mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_TRIED;
4542     } else if (mFastOpenStatus == TFO_DATA_SENT) {
4543       mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_SENT;
4544     } else {
4545       // This is TFO_DATA_COOKIE_NOT_ACCEPTED (I think this cannot
4546       // happened, because the primary connection will be already
4547       // connected or in recovery and mFastOpenInProgress==false).
4548       mFastOpenStatus =
4549           TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED;
4550     }
4551   }
4552 
4553   if (((mFastOpenStatus == TFO_DISABLED) || (mFastOpenStatus == TFO_HTTP)) &&
4554       !mBackupConnStatsSet) {
4555     // Collect telemetry for backup connection being faster than primary
4556     // connection. We want to collect this telemetry only for cases where
4557     // TFO is not used.
4558     mBackupConnStatsSet = true;
4559     Telemetry::Accumulate(Telemetry::NETWORK_HTTP_BACKUP_CONN_WON_1,
4560                           (out == mBackupStreamOut));
4561   }
4562 
4563   if (mFastOpenStatus == TFO_UNKNOWN) {
4564     MOZ_ASSERT(out == mStreamOut);
4565     if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVING_HOST) {
4566       mFastOpenStatus = TFO_UNKNOWN_RESOLVING;
4567     } else if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVED_HOST) {
4568       mFastOpenStatus = TFO_UNKNOWN_RESOLVED;
4569     } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) {
4570       mFastOpenStatus = TFO_UNKNOWN_CONNECTING;
4571     } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTED_TO) {
4572       mFastOpenStatus = TFO_UNKNOWN_CONNECTED;
4573     }
4574   }
4575   nsresult rv = SetupConn(out, false);
4576   if (mEnt) {
4577     mEnt->mDoNotDestroy = false;
4578   }
4579   return rv;
4580 }
4581 
FastOpenEnabled()4582 bool nsHttpConnectionMgr::nsHalfOpenSocket::FastOpenEnabled() {
4583   LOG(("nsHalfOpenSocket::FastOpenEnabled [this=%p]\n", this));
4584 
4585   MOZ_ASSERT(mEnt);
4586 
4587   if (!mEnt) {
4588     return false;
4589   }
4590 
4591   MOZ_ASSERT(mEnt->mConnInfo->FirstHopSSL());
4592 
4593   // If mEnt is present this HalfOpen must be in the mHalfOpens,
4594   // but we want to be sure!!!
4595   if (!mEnt->mHalfOpens.Contains(this)) {
4596     return false;
4597   }
4598 
4599   if (!gHttpHandler->UseFastOpen()) {
4600     // fast open was turned off.
4601     LOG(("nsHalfOpenSocket::FastEnabled - fast open was turned off.\n"));
4602     mEnt->mUseFastOpen = false;
4603     mFastOpenStatus = TFO_DISABLED;
4604     return false;
4605   }
4606   // We can use FastOpen if we have a transaction or if it is ssl
4607   // connection. For ssl we will use a null transaction to drive the SSL
4608   // handshake to completion if there is not a pending transaction. Afterwards
4609   // the connection will be 100% ready for the next transaction to use it.
4610   // Make an exception for SSL tunneled HTTP proxy as the NullHttpTransaction
4611   // does not know how to drive Connect.
4612   if (mEnt->mConnInfo->UsingConnect()) {
4613     LOG(("nsHalfOpenSocket::FastOpenEnabled - It is using Connect."));
4614     mFastOpenStatus = TFO_DISABLED_CONNECT;
4615     return false;
4616   }
4617   return true;
4618 }
4619 
StartFastOpen()4620 nsresult nsHttpConnectionMgr::nsHalfOpenSocket::StartFastOpen() {
4621   MOZ_ASSERT(mStreamOut);
4622   MOZ_ASSERT(!mBackupTransport);
4623   MOZ_ASSERT(mEnt);
4624   MOZ_ASSERT(mFastOpenStatus == TFO_UNKNOWN);
4625 
4626   LOG(("nsHalfOpenSocket::StartFastOpen [this=%p]\n", this));
4627 
4628   RefPtr<nsHalfOpenSocket> deleteProtector(this);
4629 
4630   mFastOpenInProgress = true;
4631   mEnt->mDoNotDestroy = true;
4632   // Remove this HalfOpen from mEnt->mHalfOpens.
4633   // The new connection will take care of closing this HalfOpen from now on!
4634   if (!mEnt->mHalfOpens.RemoveElement(this)) {
4635     MOZ_ASSERT(false, "HalfOpen is not in mHalfOpens!");
4636     mSocketTransport->SetFastOpenCallback(nullptr);
4637     CancelBackupTimer();
4638     mFastOpenInProgress = false;
4639     Abandon();
4640     mFastOpenStatus = TFO_INIT_FAILED;
4641     return NS_ERROR_ABORT;
4642   }
4643 
4644   MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
4645   if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) {  // just in case
4646     gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
4647   }
4648 
4649   // Count this socketTransport as connected.
4650   gHttpHandler->ConnMgr()->RecvdConnect();
4651 
4652   // Remove HalfOpen from callbacks, the new connection will take them.
4653   mSocketTransport->SetEventSink(nullptr, nullptr);
4654   mSocketTransport->SetSecurityCallbacks(nullptr);
4655   mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
4656 
4657   nsresult rv = SetupConn(mStreamOut, true);
4658   if (!mConnectionNegotiatingFastOpen) {
4659     LOG(
4660         ("nsHalfOpenSocket::StartFastOpen SetupConn failed "
4661          "[this=%p rv=%x]\n",
4662          this, static_cast<uint32_t>(rv)));
4663     if (NS_SUCCEEDED(rv)) {
4664       rv = NS_ERROR_ABORT;
4665     }
4666     // If SetupConn failed this will CloseTransaction and socketTransport
4667     // with an error, therefore we can close this HalfOpen. socketTransport
4668     // will remove reference to this HalfOpen as well.
4669     mSocketTransport->SetFastOpenCallback(nullptr);
4670     CancelBackupTimer();
4671     mFastOpenInProgress = false;
4672 
4673     // The connection is responsible to take care of the halfOpen so we
4674     // need to clean it up.
4675     Abandon();
4676     mFastOpenStatus = TFO_INIT_FAILED;
4677   } else {
4678     LOG(("nsHalfOpenSocket::StartFastOpen [this=%p conn=%p]\n", this,
4679          mConnectionNegotiatingFastOpen.get()));
4680 
4681     mEnt->mHalfOpenFastOpenBackups.AppendElement(this);
4682     // SetupBackupTimer should setup timer which will hold a ref to this
4683     // halfOpen. It will failed only if it cannot create timer. Anyway just
4684     // to be sure I will add this deleteProtector!!!
4685     if (!mSynTimer) {
4686       // For Fast Open we will setup backup timer also for
4687       // NullTransaction.
4688       // So maybe it is not set and we need to set it here.
4689       SetupBackupTimer();
4690     }
4691   }
4692   if (mEnt) {
4693     mEnt->mDoNotDestroy = false;
4694   }
4695   return rv;
4696 }
4697 
SetFastOpenConnected(nsresult aError,bool aWillRetry)4698 void nsHttpConnectionMgr::nsHalfOpenSocket::SetFastOpenConnected(
4699     nsresult aError, bool aWillRetry) {
4700   MOZ_ASSERT(mFastOpenInProgress);
4701   MOZ_ASSERT(mEnt);
4702 
4703   LOG(("nsHalfOpenSocket::SetFastOpenConnected [this=%p conn=%p error=%x]\n",
4704        this, mConnectionNegotiatingFastOpen.get(),
4705        static_cast<uint32_t>(aError)));
4706 
4707   // mConnectionNegotiatingFastOpen is set after a StartFastOpen creates
4708   // and activates a HttpConnectionBase successfully (SetupConn calls
4709   // DispatchTransaction and DispatchAbstractTransaction which calls
4710   // conn->Activate).
4711   // HttpConnectionBase::Activate can fail which will close socketTransport
4712   // and socketTransport will call this function. The FastOpen clean up
4713   // in case HttpConnectionBase::Activate fails will be done in StartFastOpen.
4714   // Also OnMsgReclaimConnection can decided that we do not need this
4715   // transaction and cancel it as well.
4716   // In all other cases mConnectionNegotiatingFastOpen must not be nullptr.
4717   if (!mConnectionNegotiatingFastOpen) {
4718     return;
4719   }
4720 
4721   MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
4722              (mFastOpenStatus == TFO_DATA_SENT) ||
4723              (mFastOpenStatus == TFO_TRIED) ||
4724              (mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED) ||
4725              (mFastOpenStatus == TFO_DISABLED));
4726 
4727   RefPtr<nsHalfOpenSocket> deleteProtector(this);
4728 
4729   mEnt->mDoNotDestroy = true;
4730 
4731   // Delete 2 points of entry to FastOpen function so that we do not reenter.
4732   mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
4733   mSocketTransport->SetFastOpenCallback(nullptr);
4734 
4735   mConnectionNegotiatingFastOpen->SetFastOpen(false);
4736 
4737   // Check if we want to restart connection!
4738   if (aWillRetry && ((aError == NS_ERROR_CONNECTION_REFUSED) ||
4739 #if defined(_WIN64) && defined(WIN95)
4740                      // On Windows PR_ContinueConnect can return
4741                      // NS_ERROR_FAILURE. This will be fixed in bug 1386719 and
4742                      // this is just a temporary work around.
4743                      (aError == NS_ERROR_FAILURE) ||
4744 #endif
4745                      (aError == NS_ERROR_PROXY_CONNECTION_REFUSED) ||
4746                      (aError == NS_ERROR_NET_TIMEOUT))) {
4747     if (mEnt->mUseFastOpen) {
4748       gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
4749       mEnt->mUseFastOpen = false;
4750     }
4751     // This is called from nsSocketTransport::RecoverFromError. The
4752     // socket will try connect and we need to rewind nsHttpTransaction.
4753 
4754     RefPtr<nsAHttpTransaction> trans =
4755         mConnectionNegotiatingFastOpen
4756             ->CloseConnectionFastOpenTakesTooLongOrError(false);
4757     if (trans && trans->QueryHttpTransaction()) {
4758       RefPtr<PendingTransactionInfo> pendingTransInfo =
4759           new PendingTransactionInfo(trans->QueryHttpTransaction());
4760       pendingTransInfo->mHalfOpen =
4761           do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this));
4762       if (trans->Caps() & NS_HTTP_URGENT_START) {
4763         gHttpHandler->ConnMgr()->InsertTransactionSorted(
4764             mEnt->mUrgentStartQ, pendingTransInfo, true);
4765       } else {
4766         mEnt->InsertTransaction(pendingTransInfo, true);
4767       }
4768     }
4769     // We are doing a restart without fast open, so the easiest way is to
4770     // return mSocketTransport to the halfOpenSock and destroy connection.
4771     // This makes http2 implemenntation easier.
4772     // mConnectionNegotiatingFastOpen is going away and halfOpen is taking
4773     // this mSocketTransport so add halfOpen to mEnt and update
4774     // mNumActiveConns.
4775     mEnt->mHalfOpens.AppendElement(this);
4776     gHttpHandler->ConnMgr()->mNumHalfOpenConns++;
4777     gHttpHandler->ConnMgr()->StartedConnect();
4778 
4779     // Restore callbacks.
4780     mStreamOut->AsyncWait(this, 0, 0, nullptr);
4781     mSocketTransport->SetEventSink(this, nullptr);
4782     mSocketTransport->SetSecurityCallbacks(this);
4783     mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
4784 
4785     if ((aError == NS_ERROR_CONNECTION_REFUSED) ||
4786         (aError == NS_ERROR_PROXY_CONNECTION_REFUSED)) {
4787       mFastOpenStatus = TFO_FAILED_CONNECTION_REFUSED;
4788     } else if (aError == NS_ERROR_NET_TIMEOUT) {
4789       mFastOpenStatus = TFO_FAILED_NET_TIMEOUT;
4790     } else {
4791       mFastOpenStatus = TFO_FAILED_UNKNOW_ERROR;
4792     }
4793 
4794   } else {
4795     // On success or other error we proceed with connection, we just need
4796     // to close backup timer and halfOpenSock.
4797     CancelBackupTimer();
4798     if (NS_SUCCEEDED(aError)) {
4799       NetAddr peeraddr;
4800       if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
4801         mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
4802       }
4803       gHttpHandler->ResetFastOpenConsecutiveFailureCounter();
4804     }
4805     mSocketTransport = nullptr;
4806     mStreamOut = nullptr;
4807     mStreamIn = nullptr;
4808 
4809     // If backup transport has already started put this HalfOpen back to
4810     // mEnt list.
4811     if (mBackupTransport) {
4812       mFastOpenStatus = TFO_BACKUP_CONN;
4813       mEnt->mHalfOpens.AppendElement(this);
4814       gHttpHandler->ConnMgr()->mNumHalfOpenConns++;
4815     }
4816   }
4817 
4818   mFastOpenInProgress = false;
4819   mConnectionNegotiatingFastOpen = nullptr;
4820   if (mEnt) {
4821     mEnt->mDoNotDestroy = false;
4822   } else {
4823     MOZ_ASSERT(!mBackupTransport);
4824     MOZ_ASSERT(!mBackupStreamOut);
4825   }
4826 }
4827 
SetFastOpenStatus(uint8_t tfoStatus)4828 void nsHttpConnectionMgr::nsHalfOpenSocket::SetFastOpenStatus(
4829     uint8_t tfoStatus) {
4830   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4831   MOZ_ASSERT(mFastOpenInProgress);
4832 
4833   mFastOpenStatus = tfoStatus;
4834   mConnectionNegotiatingFastOpen->SetFastOpenStatus(tfoStatus);
4835   if (mConnectionNegotiatingFastOpen->Transaction()) {
4836     // The transaction could already be canceled in the meantime, hence
4837     // nullified.
4838     mConnectionNegotiatingFastOpen->Transaction()->SetFastOpenStatus(tfoStatus);
4839   }
4840 }
4841 
CancelFastOpenConnection()4842 void nsHttpConnectionMgr::nsHalfOpenSocket::CancelFastOpenConnection() {
4843   MOZ_ASSERT(mFastOpenInProgress);
4844 
4845   LOG(("nsHalfOpenSocket::CancelFastOpenConnection [this=%p conn=%p]\n", this,
4846        mConnectionNegotiatingFastOpen.get()));
4847 
4848   RefPtr<nsHalfOpenSocket> deleteProtector(this);
4849   mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
4850   mSocketTransport->SetFastOpenCallback(nullptr);
4851   mConnectionNegotiatingFastOpen->SetFastOpen(false);
4852   RefPtr<nsAHttpTransaction> trans =
4853       mConnectionNegotiatingFastOpen
4854           ->CloseConnectionFastOpenTakesTooLongOrError(true);
4855   mSocketTransport = nullptr;
4856   mStreamOut = nullptr;
4857   mStreamIn = nullptr;
4858 
4859   if (trans && trans->QueryHttpTransaction()) {
4860     RefPtr<PendingTransactionInfo> pendingTransInfo =
4861         new PendingTransactionInfo(trans->QueryHttpTransaction());
4862 
4863     if (trans->Caps() & NS_HTTP_URGENT_START) {
4864       gHttpHandler->ConnMgr()->InsertTransactionSorted(mEnt->mUrgentStartQ,
4865                                                        pendingTransInfo, true);
4866     } else {
4867       mEnt->InsertTransaction(pendingTransInfo, true);
4868     }
4869   }
4870 
4871   mFastOpenInProgress = false;
4872   mConnectionNegotiatingFastOpen = nullptr;
4873   Abandon();
4874 
4875   MOZ_ASSERT(!mBackupTransport);
4876   MOZ_ASSERT(!mBackupStreamOut);
4877 }
4878 
FastOpenNotSupported()4879 void nsHttpConnectionMgr::nsHalfOpenSocket::FastOpenNotSupported() {
4880   MOZ_ASSERT(mFastOpenInProgress);
4881   gHttpHandler->SetFastOpenNotSupported();
4882 }
4883 
SetupConn(nsIAsyncOutputStream * out,bool aFastOpen)4884 nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupConn(
4885     nsIAsyncOutputStream* out, bool aFastOpen) {
4886   MOZ_ASSERT(!aFastOpen || (out == mStreamOut));
4887   // We cannot ask for a connection for TFO and Http3 ata the same time.
4888   MOZ_ASSERT(!(mIsHttp3 && aFastOpen));
4889   // assign the new socket to the http connection
4890   RefPtr<HttpConnectionBase> conn;
4891   if (!mIsHttp3) {
4892     conn = new nsHttpConnection();
4893   } else {
4894     conn = new HttpConnectionUDP();
4895   }
4896 
4897   LOG(
4898       ("nsHalfOpenSocket::SetupConn "
4899        "Created new nshttpconnection %p %s\n",
4900        conn.get(), mIsHttp3 ? "using http3" : ""));
4901 
4902   NullHttpTransaction* nullTrans = mTransaction->QueryNullTransaction();
4903   if (nullTrans) {
4904     conn->BootstrapTimings(nullTrans->Timings());
4905   }
4906 
4907   // Some capabilities are needed before a transaciton actually gets
4908   // scheduled (e.g. how to negotiate false start)
4909   conn->SetTransactionCaps(mTransaction->Caps());
4910 
4911   NetAddr peeraddr;
4912   nsCOMPtr<nsIInterfaceRequestor> callbacks;
4913   mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
4914   nsresult rv;
4915   if (out == mStreamOut) {
4916     TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
4917     rv = conn->Init(
4918         mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
4919         mSocketTransport, mStreamIn, mStreamOut,
4920         mPrimaryConnectedOK || aFastOpen, callbacks,
4921         PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
4922 
4923     bool resetPreference = false;
4924     mSocketTransport->GetResetIPFamilyPreference(&resetPreference);
4925     if (resetPreference) {
4926       mEnt->ResetIPFamilyPreference();
4927     }
4928 
4929     if (!aFastOpen && NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
4930       mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
4931     }
4932 
4933     // The nsHttpConnection object now owns these streams and sockets
4934     if (!aFastOpen) {
4935       mStreamOut = nullptr;
4936       mStreamIn = nullptr;
4937       mSocketTransport = nullptr;
4938     } else {
4939       RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
4940       MOZ_ASSERT(connTCP);
4941       if (connTCP) {
4942         connTCP->SetFastOpen(true);
4943       }
4944     }
4945   } else if (out == mBackupStreamOut) {
4946     TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
4947     rv = conn->Init(
4948         mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
4949         mBackupTransport, mBackupStreamIn, mBackupStreamOut, mBackupConnectedOK,
4950         callbacks,
4951         PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
4952 
4953     bool resetPreference = false;
4954     mBackupTransport->GetResetIPFamilyPreference(&resetPreference);
4955     if (resetPreference) {
4956       mEnt->ResetIPFamilyPreference();
4957     }
4958 
4959     if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr))) {
4960       mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
4961     }
4962 
4963     // The nsHttpConnection object now owns these streams and sockets
4964     mBackupStreamOut = nullptr;
4965     mBackupStreamIn = nullptr;
4966     mBackupTransport = nullptr;
4967   } else {
4968     MOZ_ASSERT(false, "unexpected stream");
4969     rv = NS_ERROR_UNEXPECTED;
4970   }
4971 
4972   if (NS_FAILED(rv)) {
4973     LOG(
4974         ("nsHalfOpenSocket::SetupConn "
4975          "conn->init (%p) failed %" PRIx32 "\n",
4976          conn.get(), static_cast<uint32_t>(rv)));
4977 
4978     RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
4979     if (connTCP) {
4980       // Set TFO status.
4981       if ((mFastOpenStatus == TFO_HTTP) || (mFastOpenStatus == TFO_DISABLED) ||
4982           (mFastOpenStatus == TFO_DISABLED_CONNECT)) {
4983         connTCP->SetFastOpenStatus(mFastOpenStatus);
4984       } else {
4985         connTCP->SetFastOpenStatus(TFO_INIT_FAILED);
4986       }
4987     }
4988     return rv;
4989   }
4990 
4991   // This half-open socket has created a connection.  This flag excludes it
4992   // from counter of actual connections used for checking limits.
4993   if (!aFastOpen) {
4994     mHasConnected = true;
4995   }
4996 
4997   // if this is still in the pending list, remove it and dispatch it
4998   RefPtr<PendingTransactionInfo> pendingTransInfo = FindTransactionHelper(true);
4999   if (pendingTransInfo) {
5000     MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
5001 
5002     gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
5003     if (mIsHttp3) {
5004       // Each connection must have a ConnectionHandle wrapper.
5005       // In case of Http < 2 the a ConnectionHandle is created for each
5006       // transaction in DispatchAbstractTransaction.
5007       // In case of Http2/ and Http3, ConnectionHandle is created only once.
5008       // Http2 connection always starts as http1 connection and the first
5009       // transaction use DispatchAbstractTransaction to be dispatched and
5010       // a ConnectionHandle is created. All consecutive transactions for
5011       // Http2 use a short-cut in DispatchTransaction and call
5012       // HttpConnectionBase::Activate (DispatchAbstractTransaction is never
5013       // called).
5014       // In case of Http3 the short-cut HttpConnectionBase::Activate is always
5015       // used also for the first transaction, therefore we need to create
5016       // ConnectionHandle here.
5017       RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
5018       pendingTransInfo->mTransaction->SetConnection(handle);
5019     }
5020     rv = gHttpHandler->ConnMgr()->DispatchTransaction(
5021         mEnt, pendingTransInfo->mTransaction, conn);
5022   } else {
5023     // this transaction was dispatched off the pending q before all the
5024     // sockets established themselves.
5025 
5026     // After about 1 second allow for the possibility of restarting a
5027     // transaction due to server close. Keep at sub 1 second as that is the
5028     // minimum granularity we can expect a server to be timing out with.
5029     RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
5030     if (connTCP) {
5031       connTCP->SetIsReusedAfter(950);
5032     }
5033 
5034     // if we are using ssl and no other transactions are waiting right now,
5035     // then form a null transaction to drive the SSL handshake to
5036     // completion. Afterwards the connection will be 100% ready for the next
5037     // transaction to use it. Make an exception for SSL tunneled HTTP proxy as
5038     // the NullHttpTransaction does not know how to drive Connect
5039     if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mUrgentStartQ.Length() &&
5040         !mEnt->PendingQLength() && !mEnt->mConnInfo->UsingConnect()) {
5041       LOG(
5042           ("nsHalfOpenSocket::SetupConn null transaction will "
5043            "be used to finish SSL handshake on conn %p\n",
5044            conn.get()));
5045       RefPtr<nsAHttpTransaction> trans;
5046       if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
5047         // null transactions cannot be put in the entry queue, so that
5048         // explains why it is not present.
5049         mDispatchedMTransaction = true;
5050         trans = mTransaction;
5051       } else {
5052         trans = new NullHttpTransaction(mEnt->mConnInfo, callbacks, mCaps);
5053       }
5054 
5055       gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
5056       rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(mEnt, trans,
5057                                                                 mCaps, conn, 0);
5058     } else {
5059       // otherwise just put this in the persistent connection pool
5060       LOG(
5061           ("nsHalfOpenSocket::SetupConn no transaction match "
5062            "returning conn %p to pool\n",
5063            conn.get()));
5064       gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
5065 
5066       // We expect that there is at least one tranasction in the pending
5067       // queue that can take this connection, but it can happened that
5068       // all transactions are blocked or they have took other idle
5069       // connections. In that case the connection has been added to the
5070       // idle queue.
5071       // If the connection is in the idle queue but it is using ssl, make
5072       // a nulltransaction for it to finish ssl handshake!
5073 
5074       // !!! It can be that mEnt is null after OnMsgReclaimConnection.!!!
5075       if (mEnt && mEnt->mConnInfo->FirstHopSSL() &&
5076           !mEnt->mConnInfo->UsingConnect()) {
5077         int32_t idx = mEnt->mIdleConns.IndexOf(conn);
5078         if (idx != -1) {
5079           RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
5080           MOZ_ASSERT(connTCP);
5081           if (connTCP) {
5082             DebugOnly<nsresult> rvDeb =
5083                 gHttpHandler->ConnMgr()->RemoveIdleConnection(connTCP);
5084             MOZ_ASSERT(NS_SUCCEEDED(rvDeb));
5085             connTCP->EndIdleMonitoring();
5086           }
5087           RefPtr<nsAHttpTransaction> trans;
5088           if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
5089             mDispatchedMTransaction = true;
5090             trans = mTransaction;
5091           } else {
5092             trans = new NullHttpTransaction(mEnt->mConnInfo, callbacks, mCaps);
5093           }
5094           gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
5095           rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
5096               mEnt, trans, mCaps, conn, 0);
5097         }
5098       }
5099     }
5100   }
5101 
5102   // If this connection has a transaction get reference to its
5103   // ConnectionHandler.
5104   RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
5105   if (connTCP) {
5106     if (aFastOpen) {
5107       MOZ_ASSERT(mEnt);
5108       MOZ_ASSERT(static_cast<int32_t>(mEnt->mIdleConns.IndexOf(connTCP)) == -1);
5109       int32_t idx = mEnt->mActiveConns.IndexOf(conn);
5110       if (NS_SUCCEEDED(rv) && (idx != -1)) {
5111         mConnectionNegotiatingFastOpen = connTCP;
5112       } else {
5113         connTCP->SetFastOpen(false);
5114         connTCP->SetFastOpenStatus(TFO_INIT_FAILED);
5115       }
5116     } else {
5117       connTCP->SetFastOpenStatus(mFastOpenStatus);
5118       if ((mFastOpenStatus != TFO_HTTP) && (mFastOpenStatus != TFO_DISABLED) &&
5119           (mFastOpenStatus != TFO_DISABLED_CONNECT)) {
5120         mFastOpenStatus = TFO_BACKUP_CONN;  // Set this to TFO_BACKUP_CONN
5121                                             // so that if a backup
5122                                             // connection is established we
5123                                             // do not report values twice.
5124       }
5125     }
5126   }
5127 
5128   // If this halfOpenConn was speculative, but at the end the conn got a
5129   // non-null transaction than this halfOpen is not speculative anymore!
5130   if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
5131     Claim();
5132   }
5133 
5134   return rv;
5135 }
5136 
5137 // register a connection to receive CanJoinConnection() for particular
5138 // origin keys
RegisterOriginCoalescingKey(HttpConnectionBase * conn,const nsACString & host,int32_t port)5139 void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn,
5140                                                       const nsACString& host,
5141                                                       int32_t port) {
5142   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
5143   nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr;
5144   if (!ci || !conn->CanDirectlyActivate()) {
5145     return;
5146   }
5147 
5148   nsCString newKey;
5149   BuildOriginFrameHashKey(newKey, ci, host, port);
5150   nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(newKey);
5151   if (!listOfWeakConns) {
5152     listOfWeakConns = new nsTArray<nsWeakPtr>(1);
5153     mCoalescingHash.Put(newKey, listOfWeakConns);
5154   }
5155   listOfWeakConns->AppendElement(
5156       do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
5157 
5158   LOG(
5159       ("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
5160        "Established New Coalescing Key %s to %p %s\n",
5161        newKey.get(), conn, ci->HashKey().get()));
5162 }
5163 
5164 // method for nsITransportEventSink
5165 NS_IMETHODIMP
OnTransportStatus(nsITransport * trans,nsresult status,int64_t progress,int64_t progressMax)5166 nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport* trans,
5167                                                          nsresult status,
5168                                                          int64_t progress,
5169                                                          int64_t progressMax) {
5170   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
5171 
5172   MOZ_ASSERT((trans == mSocketTransport) || (trans == mBackupTransport));
5173   MOZ_ASSERT(mEnt);
5174   if (mTransaction) {
5175     if ((trans == mSocketTransport) ||
5176         ((trans == mBackupTransport) &&
5177          (status == NS_NET_STATUS_CONNECTED_TO) && mSocketTransport)) {
5178       // Send this status event only if the transaction is still pending,
5179       // i.e. it has not found a free already connected socket.
5180       // Sockets in halfOpen state can only get following events:
5181       // NS_NET_STATUS_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST,
5182       // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
5183       // mBackupTransport is only started after
5184       // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore all
5185       // mBackupTransport events until NS_NET_STATUS_CONNECTED_TO.
5186       // mBackupTransport must be connected before mSocketTransport.
5187       mTransaction->OnTransportStatus(trans, status, progress);
5188     }
5189   }
5190 
5191   MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
5192   if (status == NS_NET_STATUS_CONNECTED_TO) {
5193     if (trans == mSocketTransport) {
5194       mPrimaryConnectedOK = true;
5195     } else {
5196       mBackupConnectedOK = true;
5197     }
5198   }
5199 
5200   // The rest of this method only applies to the primary transport
5201   if (trans != mSocketTransport) {
5202     return NS_OK;
5203   }
5204 
5205   mPrimaryStreamStatus = status;
5206 
5207   // if we are doing spdy coalescing and haven't recorded the ip address
5208   // for this entry before then make the hash key if our dns lookup
5209   // just completed. We can't do coalescing if using a proxy because the
5210   // ip addresses are not available to the client.
5211 
5212   if (status == NS_NET_STATUS_CONNECTING_TO && gHttpHandler->IsSpdyEnabled() &&
5213       gHttpHandler->CoalesceSpdy() && mEnt && mEnt->mConnInfo &&
5214       mEnt->mConnInfo->EndToEndSSL() && mEnt->AllowSpdy() &&
5215       !mEnt->mConnInfo->UsingProxy() && mEnt->mCoalescingKeys.IsEmpty()) {
5216     nsCOMPtr<nsIDNSRecord> dnsRecord(do_GetInterface(mSocketTransport));
5217     nsTArray<NetAddr> addressSet;
5218     nsresult rv = NS_ERROR_NOT_AVAILABLE;
5219     if (dnsRecord) {
5220       rv = dnsRecord->GetAddresses(addressSet);
5221     }
5222 
5223     if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) {
5224       for (uint32_t i = 0; i < addressSet.Length(); ++i) {
5225         nsCString* newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
5226         newKey->SetLength(kIPv6CStrBufSize + 26);
5227         NetAddrToString(&addressSet[i], newKey->BeginWriting(),
5228                         kIPv6CStrBufSize);
5229         newKey->SetLength(strlen(newKey->BeginReading()));
5230         if (mEnt->mConnInfo->GetAnonymous()) {
5231           newKey->AppendLiteral("~A:");
5232         } else {
5233           newKey->AppendLiteral("~.:");
5234         }
5235         newKey->AppendInt(mEnt->mConnInfo->OriginPort());
5236         newKey->AppendLiteral("/[");
5237         nsAutoCString suffix;
5238         mEnt->mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
5239         newKey->Append(suffix);
5240         newKey->AppendLiteral("]viaDNS");
5241         LOG((
5242             "nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
5243             "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
5244             "%s [%s]",
5245             i, mEnt->mConnInfo->Origin(), newKey->get()));
5246       }
5247       gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
5248     }
5249   }
5250 
5251   switch (status) {
5252     case NS_NET_STATUS_CONNECTING_TO:
5253       // Passed DNS resolution, now trying to connect, start the backup timer
5254       // only prevent creating another backup transport.
5255       // We also check for mEnt presence to not instantiate the timer after
5256       // this half open socket has already been abandoned.  It may happen
5257       // when we get this notification right between main-thread calls to
5258       // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
5259       // where the first abandons all half open socket instances and only
5260       // after that the second stops the socket thread.
5261       // Http3 has its own syn-retransmission, therefore it does not need a
5262       // backup connection.
5263       if (mEnt && !mBackupTransport && !mSynTimer && !mIsHttp3) {
5264         SetupBackupTimer();
5265       }
5266       break;
5267 
5268     case NS_NET_STATUS_CONNECTED_TO:
5269       // TCP connection's up, now transfer or SSL negotiantion starts,
5270       // no need for backup socket
5271       CancelBackupTimer();
5272       break;
5273 
5274     default:
5275       break;
5276   }
5277 
5278   return NS_OK;
5279 }
5280 
5281 // method for nsIInterfaceRequestor
5282 NS_IMETHODIMP
GetInterface(const nsIID & iid,void ** result)5283 nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID& iid,
5284                                                     void** result) {
5285   if (mTransaction) {
5286     nsCOMPtr<nsIInterfaceRequestor> callbacks;
5287     mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
5288     if (callbacks) return callbacks->GetInterface(iid, result);
5289   }
5290   return NS_ERROR_NO_INTERFACE;
5291 }
5292 
AcceptsTransaction(nsHttpTransaction * trans)5293 bool nsHttpConnectionMgr::nsHalfOpenSocket::AcceptsTransaction(
5294     nsHttpTransaction* trans) {
5295   // When marked as urgent start, only accept urgent start marked transactions.
5296   // Otherwise, accept any kind of transaction.
5297   return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
5298 }
5299 
Claim()5300 bool nsHttpConnectionMgr::nsHalfOpenSocket::Claim() {
5301   if (mSpeculative) {
5302     mSpeculative = false;
5303     uint32_t flags;
5304     if (mSocketTransport &&
5305         NS_SUCCEEDED(mSocketTransport->GetConnectionFlags(&flags))) {
5306       flags &= ~nsISocketTransport::DISABLE_RFC1918;
5307       mSocketTransport->SetConnectionFlags(flags);
5308     }
5309 
5310     Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN>
5311         usedSpeculativeConn;
5312     ++usedSpeculativeConn;
5313 
5314     if (mIsFromPredictor) {
5315       Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED>
5316           totalPreconnectsUsed;
5317       ++totalPreconnectsUsed;
5318     }
5319 
5320     // Http3 has its own syn-retransmission, therefore it does not need a
5321     // backup connection.
5322     if ((mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) && mEnt &&
5323         !mBackupTransport && !mSynTimer && !mIsHttp3) {
5324       SetupBackupTimer();
5325     }
5326   }
5327 
5328   if (mFreeToUse) {
5329     mFreeToUse = false;
5330     return true;
5331   }
5332   return false;
5333 }
5334 
Unclaim()5335 void nsHttpConnectionMgr::nsHalfOpenSocket::Unclaim() {
5336   MOZ_ASSERT(!mSpeculative && !mFreeToUse);
5337   // We will keep the backup-timer running. Most probably this halfOpen will
5338   // be used by a transaction from which this transaction took the halfOpen.
5339   // (this is happening because of the transaction priority.)
5340   mFreeToUse = true;
5341 }
5342 
TakeHttpConnection()5343 already_AddRefed<HttpConnectionBase> ConnectionHandle::TakeHttpConnection() {
5344   // return our connection object to the caller and clear it internally
5345   // do not drop our reference - the caller now owns it.
5346   MOZ_ASSERT(mConn);
5347   return mConn.forget();
5348 }
5349 
HttpConnection()5350 already_AddRefed<HttpConnectionBase> ConnectionHandle::HttpConnection() {
5351   RefPtr<HttpConnectionBase> rv(mConn);
5352   return rv.forget();
5353 }
5354 
TopLevelOuterContentWindowIdChanged(uint64_t windowId)5355 void ConnectionHandle::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
5356   // Do nothing.
5357 }
5358 
5359 // nsConnectionEntry
5360 
nsConnectionEntry(nsHttpConnectionInfo * ci)5361 nsHttpConnectionMgr::nsConnectionEntry::nsConnectionEntry(
5362     nsHttpConnectionInfo* ci)
5363     : mConnInfo(ci),
5364       mUsingSpdy(false),
5365       mCanUseSpdy(true),
5366       mPreferIPv4(false),
5367       mPreferIPv6(false),
5368       mUsedForConnection(false),
5369       mDoNotDestroy(false) {
5370   MOZ_COUNT_CTOR(nsConnectionEntry);
5371 
5372   if (mConnInfo->FirstHopSSL() && !mConnInfo->IsHttp3()) {
5373     mUseFastOpen = gHttpHandler->UseFastOpen();
5374   } else {
5375     // Only allow the TCP fast open on a secure connection.
5376     mUseFastOpen = false;
5377   }
5378 
5379   LOG(("nsConnectionEntry::nsConnectionEntry this=%p key=%s", this,
5380        ci->HashKey().get()));
5381 }
5382 
AvailableForDispatchNow()5383 bool nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow() {
5384   if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
5385     return true;
5386   }
5387 
5388   return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false) ? true
5389                                                                    : false;
5390 }
5391 
GetConnectionData(nsTArray<HttpRetParams> * aArg)5392 bool nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams>* aArg) {
5393   for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
5394     RefPtr<nsConnectionEntry> ent = iter.Data();
5395 
5396     if (ent->mConnInfo->GetPrivate()) {
5397       continue;
5398     }
5399 
5400     HttpRetParams data;
5401     data.host = ent->mConnInfo->Origin();
5402     data.port = ent->mConnInfo->OriginPort();
5403     for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
5404       HttpConnInfo info;
5405       RefPtr<nsHttpConnection> connTCP = do_QueryObject(ent->mActiveConns[i]);
5406       if (connTCP) {
5407         info.ttl = connTCP->TimeToLive();
5408       } else {
5409         info.ttl = 0;
5410       }
5411       info.rtt = ent->mActiveConns[i]->Rtt();
5412       info.SetHTTPProtocolVersion(ent->mActiveConns[i]->Version());
5413       data.active.AppendElement(info);
5414     }
5415     for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
5416       HttpConnInfo info;
5417       info.ttl = ent->mIdleConns[i]->TimeToLive();
5418       info.rtt = ent->mIdleConns[i]->Rtt();
5419       info.SetHTTPProtocolVersion(ent->mIdleConns[i]->Version());
5420       data.idle.AppendElement(info);
5421     }
5422     for (uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
5423       HalfOpenSockets hSocket;
5424       hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
5425       data.halfOpens.AppendElement(hSocket);
5426     }
5427     if (ent->mConnInfo->IsHttp3()) {
5428       data.httpVersion = NS_LITERAL_CSTRING("HTTP/3");
5429     } else if (ent->mUsingSpdy) {
5430       data.httpVersion = NS_LITERAL_CSTRING("HTTP/2");
5431     } else {
5432       data.httpVersion = NS_LITERAL_CSTRING("HTTP <= 1.1");
5433     }
5434     data.ssl = ent->mConnInfo->EndToEndSSL();
5435     aArg->AppendElement(data);
5436   }
5437 
5438   return true;
5439 }
5440 
ResetIPFamilyPreference(nsHttpConnectionInfo * ci)5441 void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) {
5442   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
5443   nsConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
5444   if (ent) {
5445     ent->ResetIPFamilyPreference();
5446   }
5447 }
5448 
UnconnectedHalfOpens()5449 uint32_t nsHttpConnectionMgr::nsConnectionEntry::UnconnectedHalfOpens() {
5450   uint32_t unconnectedHalfOpens = 0;
5451   for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
5452     if (!mHalfOpens[i]->HasConnected()) ++unconnectedHalfOpens;
5453   }
5454   return unconnectedHalfOpens;
5455 }
5456 
RemoveHalfOpen(nsHalfOpenSocket * halfOpen)5457 void nsHttpConnectionMgr::nsConnectionEntry::RemoveHalfOpen(
5458     nsHalfOpenSocket* halfOpen) {
5459   // A failure to create the transport object at all
5460   // will result in it not being present in the halfopen table. That's expected.
5461   if (mHalfOpens.RemoveElement(halfOpen)) {
5462     if (halfOpen->IsSpeculative()) {
5463       Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
5464           unusedSpeculativeConn;
5465       ++unusedSpeculativeConn;
5466 
5467       if (halfOpen->IsFromPredictor()) {
5468         Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
5469             totalPreconnectsUnused;
5470         ++totalPreconnectsUnused;
5471       }
5472     }
5473 
5474     MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
5475     if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) {  // just in case
5476       gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
5477     }
5478   } else {
5479     mHalfOpenFastOpenBackups.RemoveElement(halfOpen);
5480   }
5481 
5482   if (!UnconnectedHalfOpens()) {
5483     // perhaps this reverted RestrictConnections()
5484     // use the PostEvent version of processpendingq to avoid
5485     // altering the pending q vector from an arbitrary stack
5486     nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
5487     if (NS_FAILED(rv)) {
5488       LOG(
5489           ("nsHttpConnectionMgr::nsConnectionEntry::RemoveHalfOpen\n"
5490            "    failed to process pending queue\n"));
5491     }
5492   }
5493 }
5494 
BlacklistSpdy(const nsHttpConnectionInfo * ci)5495 void nsHttpConnectionMgr::BlacklistSpdy(const nsHttpConnectionInfo* ci) {
5496   LOG(("nsHttpConnectionMgr::BlacklistSpdy blacklisting ci %s",
5497        ci->HashKey().BeginReading()));
5498   nsConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
5499   if (!ent) {
5500     LOG(("nsHttpConnectionMgr::BlacklistSpdy no entry found?!"));
5501     return;
5502   }
5503 
5504   ent->DisallowSpdy();
5505 }
5506 
DisallowSpdy()5507 void nsHttpConnectionMgr::nsConnectionEntry::DisallowSpdy() {
5508   mCanUseSpdy = false;
5509 
5510   // If we have any spdy connections, we want to go ahead and close them when
5511   // they're done so we can free up some connections.
5512   for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
5513     if (mActiveConns[i]->UsingSpdy()) {
5514       mActiveConns[i]->DontReuse();
5515     }
5516   }
5517   for (uint32_t i = 0; i < mIdleConns.Length(); ++i) {
5518     if (mIdleConns[i]->UsingSpdy()) {
5519       mIdleConns[i]->DontReuse();
5520     }
5521   }
5522 
5523   // Can't coalesce if we're not using spdy
5524   mCoalescingKeys.Clear();
5525 }
5526 
RecordIPFamilyPreference(uint16_t family)5527 void nsHttpConnectionMgr::nsConnectionEntry::RecordIPFamilyPreference(
5528     uint16_t family) {
5529   LOG(("nsConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
5530 
5531   if (family == PR_AF_INET && !mPreferIPv6) {
5532     mPreferIPv4 = true;
5533   }
5534 
5535   if (family == PR_AF_INET6 && !mPreferIPv4) {
5536     mPreferIPv6 = true;
5537   }
5538 
5539   LOG(("  %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
5540        (bool)mPreferIPv6));
5541 }
5542 
ResetIPFamilyPreference()5543 void nsHttpConnectionMgr::nsConnectionEntry::ResetIPFamilyPreference() {
5544   LOG(("nsConnectionEntry::ResetIPFamilyPreference %p", this));
5545 
5546   mPreferIPv4 = false;
5547   mPreferIPv6 = false;
5548 }
5549 
PreferenceKnown() const5550 bool net::nsHttpConnectionMgr::nsConnectionEntry::PreferenceKnown() const {
5551   return (bool)mPreferIPv4 || (bool)mPreferIPv6;
5552 }
5553 
PendingQLength() const5554 size_t nsHttpConnectionMgr::nsConnectionEntry::PendingQLength() const {
5555   size_t length = 0;
5556   for (auto it = mPendingTransactionTable.ConstIter(); !it.Done(); it.Next()) {
5557     length += it.UserData()->Length();
5558   }
5559 
5560   return length;
5561 }
5562 
InsertTransaction(PendingTransactionInfo * info,bool aInsertAsFirstForTheSamePriority)5563 void nsHttpConnectionMgr::nsConnectionEntry::InsertTransaction(
5564     PendingTransactionInfo* info,
5565     bool aInsertAsFirstForTheSamePriority /*= false*/) {
5566   LOG(
5567       ("nsHttpConnectionMgr::nsConnectionEntry::InsertTransaction"
5568        " trans=%p, windowId=%" PRIu64 "\n",
5569        info->mTransaction.get(),
5570        info->mTransaction->TopLevelOuterContentWindowId()));
5571 
5572   uint64_t windowId = TabIdForQueuing(info->mTransaction);
5573   nsTArray<RefPtr<PendingTransactionInfo>>* infoArray;
5574   if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
5575     infoArray = new nsTArray<RefPtr<PendingTransactionInfo>>();
5576     mPendingTransactionTable.Put(windowId, infoArray);
5577   }
5578 
5579   gHttpHandler->ConnMgr()->InsertTransactionSorted(
5580       *infoArray, info, aInsertAsFirstForTheSamePriority);
5581 }
5582 
AppendPendingQForFocusedWindow(uint64_t windowId,nsTArray<RefPtr<PendingTransactionInfo>> & result,uint32_t maxCount)5583 void nsHttpConnectionMgr::nsConnectionEntry::AppendPendingQForFocusedWindow(
5584     uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
5585     uint32_t maxCount) {
5586   nsTArray<RefPtr<PendingTransactionInfo>>* infoArray = nullptr;
5587   if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
5588     result.Clear();
5589     return;
5590   }
5591 
5592   uint32_t countToAppend = maxCount;
5593   countToAppend = countToAppend > infoArray->Length() || countToAppend == 0
5594                       ? infoArray->Length()
5595                       : countToAppend;
5596 
5597   result.InsertElementsAt(result.Length(), infoArray->Elements(),
5598                           countToAppend);
5599   infoArray->RemoveElementsAt(0, countToAppend);
5600 
5601   LOG(
5602       ("nsConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
5603        "pendingQ count=%zu window.count=%zu for focused window (id=%" PRIu64
5604        ")\n",
5605        mConnInfo->HashKey().get(), result.Length(), infoArray->Length(),
5606        windowId));
5607 }
5608 
AppendPendingQForNonFocusedWindows(uint64_t windowId,nsTArray<RefPtr<PendingTransactionInfo>> & result,uint32_t maxCount)5609 void nsHttpConnectionMgr::nsConnectionEntry::AppendPendingQForNonFocusedWindows(
5610     uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
5611     uint32_t maxCount) {
5612   // XXX Adjust the order of transactions in a smarter manner.
5613   uint32_t totalCount = 0;
5614   for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
5615     if (windowId && it.Key() == windowId) {
5616       continue;
5617     }
5618 
5619     uint32_t count = 0;
5620     for (; count < it.UserData()->Length(); ++count) {
5621       if (maxCount && totalCount == maxCount) {
5622         break;
5623       }
5624 
5625       // Because elements in |result| could come from multiple penndingQ,
5626       // call |InsertTransactionSorted| to make sure the order is correct.
5627       gHttpHandler->ConnMgr()->InsertTransactionSorted(
5628           result, it.UserData()->ElementAt(count));
5629       ++totalCount;
5630     }
5631     it.UserData()->RemoveElementsAt(0, count);
5632 
5633     if (maxCount && totalCount == maxCount) {
5634       if (it.UserData()->Length()) {
5635         // There are still some pending transactions for background
5636         // tabs but we limit their dispatch.  This is considered as
5637         // an active tab optimization.
5638         nsHttp::NotifyActiveTabLoadOptimization();
5639       }
5640       break;
5641     }
5642   }
5643 
5644   LOG(
5645       ("nsConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
5646        "pendingQ count=%zu for non focused window\n",
5647        mConnInfo->HashKey().get(), result.Length()));
5648 }
5649 
RemoveEmptyPendingQ()5650 void nsHttpConnectionMgr::nsConnectionEntry::RemoveEmptyPendingQ() {
5651   for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
5652     if (it.UserData()->IsEmpty()) {
5653       it.Remove();
5654     }
5655   }
5656 }
5657 
MoveToWildCardConnEntry(nsHttpConnectionInfo * specificCI,nsHttpConnectionInfo * wildCardCI,HttpConnectionBase * proxyConn)5658 void nsHttpConnectionMgr::MoveToWildCardConnEntry(
5659     nsHttpConnectionInfo* specificCI, nsHttpConnectionInfo* wildCardCI,
5660     HttpConnectionBase* proxyConn) {
5661   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
5662   MOZ_ASSERT(specificCI->UsingHttpsProxy());
5663 
5664   LOG(
5665       ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
5666        "change CI from %s to %s\n",
5667        proxyConn, specificCI->HashKey().get(), wildCardCI->HashKey().get()));
5668 
5669   nsConnectionEntry* ent = mCT.GetWeak(specificCI->HashKey());
5670   LOG(
5671       ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
5672        "%d)\n",
5673        proxyConn, ent, ent ? ent->mUsingSpdy : 0));
5674 
5675   if (!ent || !ent->mUsingSpdy) {
5676     return;
5677   }
5678 
5679   nsConnectionEntry* wcEnt =
5680       GetOrCreateConnectionEntry(wildCardCI, true, false);
5681   if (wcEnt == ent) {
5682     // nothing to do!
5683     return;
5684   }
5685   wcEnt->mUsingSpdy = true;
5686 
5687   LOG(
5688       ("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
5689        "idle=%zu active=%zu half=%zu pending=%zu\n",
5690        ent, ent->mIdleConns.Length(), ent->mActiveConns.Length(),
5691        ent->mHalfOpens.Length(), ent->PendingQLength()));
5692 
5693   LOG(
5694       ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
5695        "idle=%zu active=%zu half=%zu pending=%zu\n",
5696        wcEnt, wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
5697        wcEnt->mHalfOpens.Length(), wcEnt->PendingQLength()));
5698 
5699   int32_t count = ent->mActiveConns.Length();
5700   RefPtr<HttpConnectionBase> deleteProtector(proxyConn);
5701   for (int32_t i = 0; i < count; ++i) {
5702     if (ent->mActiveConns[i] == proxyConn) {
5703       ent->mActiveConns.RemoveElementAt(i);
5704       wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
5705       return;
5706     }
5707   }
5708 
5709   RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn);
5710   if (proxyConnTCP) {
5711     count = ent->mIdleConns.Length();
5712     for (int32_t i = 0; i < count; ++i) {
5713       if (ent->mIdleConns[i] == proxyConnTCP) {
5714         ent->mIdleConns.RemoveElementAt(i);
5715         wcEnt->mIdleConns.InsertElementAt(0, proxyConnTCP);
5716         return;
5717       }
5718     }
5719   }
5720 }
5721 
AsHttpConnectionMgr()5722 nsHttpConnectionMgr* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; }
5723 
AsHttpConnectionMgrParent()5724 HttpConnectionMgrParent* nsHttpConnectionMgr::AsHttpConnectionMgrParent() {
5725   return nullptr;
5726 }
5727 
5728 }  // namespace net
5729 }  // namespace mozilla
5730