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