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