netwerk/protocol/http/nsHttpConnectionMgr.cpp

branch
TOR_BUG_9701
changeset 11
deefc01c0e14
equal deleted inserted replaced
-1:000000000000 0:eb5afc0ded76
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 "nsHttpPipeline.h"
18 #include "nsHttpHandler.h"
19 #include "nsIHttpChannelInternal.h"
20 #include "nsNetCID.h"
21 #include "nsCOMPtr.h"
22 #include "nsNetUtil.h"
23 #include "mozilla/net/DNS.h"
24 #include "nsISocketTransport.h"
25 #include "nsISSLSocketControl.h"
26 #include "mozilla/Telemetry.h"
27 #include "mozilla/net/DashboardTypes.h"
28 #include "NullHttpTransaction.h"
29 #include "nsITransport.h"
30 #include "nsISocketTransportService.h"
31 #include <algorithm>
32 #include "Http2Compression.h"
33 #include "mozilla/ChaosMode.h"
34 #include "mozilla/unused.h"
35 #include <stdlib.h>
36 #include "nsHttpRequestHead.h"
37
38 // defined by the socket transport service while active
39 extern PRThread *gSocketThread;
40
41 namespace mozilla {
42 namespace net {
43
44 //-----------------------------------------------------------------------------
45
46 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
47
48 static void
49 InsertTransactionSorted(nsTArray<nsHttpTransaction*> &pendingQ, nsHttpTransaction *trans)
50 {
51 // insert into queue with smallest valued number first. search in reverse
52 // order under the assumption that many of the existing transactions will
53 // have the same priority (usually 0).
54 uint32_t len = pendingQ.Length();
55
56 if (pendingQ.IsEmpty()) {
57 pendingQ.InsertElementAt(0, trans);
58 return;
59 }
60
61 pendingQ.InsertElementAt(0, trans);
62
63 // FIXME: Refactor into standalone helper (for nsHttpPipeline)
64 // Or at least simplify this function if this shuffle ends up
65 // being an improvement.
66 uint32_t i = 0;
67 for (i=0; i < len; ++i) {
68 uint32_t ridx = rand() % len;
69
70 nsHttpTransaction *tmp = pendingQ[i];
71 pendingQ[i] = pendingQ[ridx];
72 pendingQ[ridx] = tmp;
73 }
74 }
75
76 //-----------------------------------------------------------------------------
77
78 nsHttpConnectionMgr::nsHttpConnectionMgr()
79 : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
80 , mMaxConns(0)
81 , mMaxPersistConnsPerHost(0)
82 , mMaxPersistConnsPerProxy(0)
83 , mIsShuttingDown(false)
84 , mNumActiveConns(0)
85 , mNumIdleConns(0)
86 , mNumSpdyActiveConns(0)
87 , mNumHalfOpenConns(0)
88 , mTimeOfNextWakeUp(UINT64_MAX)
89 , mTimeoutTickArmed(false)
90 , mTimeoutTickNext(1)
91 {
92 LOG(("Creating nsHttpConnectionMgr @%x\n", this));
93 }
94
95 nsHttpConnectionMgr::~nsHttpConnectionMgr()
96 {
97 LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
98 if (mTimeoutTick)
99 mTimeoutTick->Cancel();
100 }
101
102 nsresult
103 nsHttpConnectionMgr::EnsureSocketThreadTarget()
104 {
105 nsresult rv;
106 nsCOMPtr<nsIEventTarget> sts;
107 nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
108 if (NS_SUCCEEDED(rv))
109 sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
110
111 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
112
113 // do nothing if already initialized or if we've shut down
114 if (mSocketThreadTarget || mIsShuttingDown)
115 return NS_OK;
116
117 mSocketThreadTarget = sts;
118
119 return rv;
120 }
121
122 nsresult
123 nsHttpConnectionMgr::Init(uint16_t maxConns,
124 uint16_t maxPersistConnsPerHost,
125 uint16_t maxPersistConnsPerProxy,
126 uint16_t maxRequestDelay,
127 uint16_t maxPipelinedRequests,
128 uint16_t maxOptimisticPipelinedRequests)
129 {
130 LOG(("nsHttpConnectionMgr::Init\n"));
131
132 {
133 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
134
135 mMaxConns = maxConns;
136 mMaxPersistConnsPerHost = maxPersistConnsPerHost;
137 mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
138 mMaxRequestDelay = maxRequestDelay;
139 mMaxPipelinedRequests = maxPipelinedRequests;
140 mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
141
142 mIsShuttingDown = false;
143 }
144
145 return EnsureSocketThreadTarget();
146 }
147
148 nsresult
149 nsHttpConnectionMgr::Shutdown()
150 {
151 LOG(("nsHttpConnectionMgr::Shutdown\n"));
152
153 bool shutdown = false;
154 {
155 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
156
157 // do nothing if already shutdown
158 if (!mSocketThreadTarget)
159 return NS_OK;
160
161 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown,
162 0, &shutdown);
163
164 // release our reference to the STS to prevent further events
165 // from being posted. this is how we indicate that we are
166 // shutting down.
167 mIsShuttingDown = true;
168 mSocketThreadTarget = 0;
169
170 if (NS_FAILED(rv)) {
171 NS_WARNING("unable to post SHUTDOWN message");
172 return rv;
173 }
174 }
175
176 // wait for shutdown event to complete
177 while (!shutdown)
178 NS_ProcessNextEvent(NS_GetCurrentThread());
179 Http2CompressionCleanup();
180
181 return NS_OK;
182 }
183
184 nsresult
185 nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, int32_t iparam, void *vparam)
186 {
187 EnsureSocketThreadTarget();
188
189 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
190
191 nsresult rv;
192 if (!mSocketThreadTarget) {
193 NS_WARNING("cannot post event if not initialized");
194 rv = NS_ERROR_NOT_INITIALIZED;
195 }
196 else {
197 nsRefPtr<nsIRunnable> event = new nsConnEvent(this, handler, iparam, vparam);
198 rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
199 }
200 return rv;
201 }
202
203 void
204 nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds)
205 {
206 LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
207
208 if(!mTimer)
209 mTimer = do_CreateInstance("@mozilla.org/timer;1");
210
211 // failure to create a timer is not a fatal error, but idle connections
212 // will not be cleaned up until we try to use them.
213 if (mTimer) {
214 mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
215 mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT);
216 } else {
217 NS_WARNING("failed to create: timer for pruning the dead connections!");
218 }
219 }
220
221 void
222 nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer()
223 {
224 // Leave the timer in place if there are connections that potentially
225 // need management
226 if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
227 return;
228
229 LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
230
231 // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
232 mTimeOfNextWakeUp = UINT64_MAX;
233 if (mTimer) {
234 mTimer->Cancel();
235 mTimer = nullptr;
236 }
237 }
238
239 void
240 nsHttpConnectionMgr::ConditionallyStopTimeoutTick()
241 {
242 LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
243 "armed=%d active=%d\n", mTimeoutTickArmed, mNumActiveConns));
244
245 if (!mTimeoutTickArmed)
246 return;
247
248 if (mNumActiveConns)
249 return;
250
251 LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
252
253 mTimeoutTick->Cancel();
254 mTimeoutTickArmed = false;
255 }
256
257 //-----------------------------------------------------------------------------
258 // nsHttpConnectionMgr::nsIObserver
259 //-----------------------------------------------------------------------------
260
261 NS_IMETHODIMP
262 nsHttpConnectionMgr::Observe(nsISupports *subject,
263 const char *topic,
264 const char16_t *data)
265 {
266 LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
267
268 if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
269 nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
270 if (timer == mTimer) {
271 PruneDeadConnections();
272 }
273 else if (timer == mTimeoutTick) {
274 TimeoutTick();
275 }
276 else {
277 MOZ_ASSERT(false, "unexpected timer-callback");
278 LOG(("Unexpected timer object\n"));
279 return NS_ERROR_UNEXPECTED;
280 }
281 }
282
283 return NS_OK;
284 }
285
286
287 //-----------------------------------------------------------------------------
288
289 nsresult
290 nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority)
291 {
292 LOG(("nsHttpConnectionMgr::AddTransaction [trans=%x %d]\n", trans, priority));
293
294 NS_ADDREF(trans);
295 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
296 if (NS_FAILED(rv))
297 NS_RELEASE(trans);
298 return rv;
299 }
300
301 nsresult
302 nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
303 {
304 LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%x %d]\n", trans, priority));
305
306 NS_ADDREF(trans);
307 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
308 if (NS_FAILED(rv))
309 NS_RELEASE(trans);
310 return rv;
311 }
312
313 nsresult
314 nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
315 {
316 LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%x reason=%x]\n", trans, reason));
317
318 NS_ADDREF(trans);
319 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
320 static_cast<int32_t>(reason), trans);
321 if (NS_FAILED(rv))
322 NS_RELEASE(trans);
323 return rv;
324 }
325
326 nsresult
327 nsHttpConnectionMgr::PruneDeadConnections()
328 {
329 return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
330 }
331
332 nsresult
333 nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
334 {
335 nsRefPtr<nsHttpConnectionInfo> connInfo(aCI);
336
337 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
338 0, connInfo);
339 if (NS_SUCCEEDED(rv))
340 unused << connInfo.forget();
341 return rv;
342 }
343
344 class SpeculativeConnectArgs
345 {
346 public:
347 SpeculativeConnectArgs() { mOverridesOK = false; }
348 virtual ~SpeculativeConnectArgs() {}
349
350 // Added manually so we can use nsRefPtr without inheriting from
351 // nsISupports
352 NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
353 NS_IMETHOD_(MozExternalRefCountType) Release(void);
354
355 public: // intentional!
356 nsRefPtr<NullHttpTransaction> mTrans;
357
358 bool mOverridesOK;
359 uint32_t mParallelSpeculativeConnectLimit;
360 bool mIgnoreIdle;
361 bool mIgnorePossibleSpdyConnections;
362
363 // As above, added manually so we can use nsRefPtr without inheriting from
364 // nsISupports
365 protected:
366 ThreadSafeAutoRefCnt mRefCnt;
367 NS_DECL_OWNINGTHREAD
368 };
369
370 NS_IMPL_ADDREF(SpeculativeConnectArgs)
371 NS_IMPL_RELEASE(SpeculativeConnectArgs)
372
373 nsresult
374 nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
375 nsIInterfaceRequestor *callbacks,
376 uint32_t caps)
377 {
378 MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
379
380 LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
381 ci->HashKey().get()));
382
383 // Hosts that are Local IP Literals should not be speculatively
384 // connected - Bug 853423.
385 if (ci && ci->HostIsLocalIPLiteral()) {
386 LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
387 "address [%s]", ci->Host()));
388 return NS_OK;
389 }
390
391 nsRefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
392
393 // Wrap up the callbacks and the target to ensure they're released on the target
394 // thread properly.
395 nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
396 NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
397
398 caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
399 args->mTrans = new NullHttpTransaction(ci, wrappedCallbacks, caps);
400
401 nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
402 do_GetInterface(callbacks);
403 if (overrider) {
404 args->mOverridesOK = true;
405 overrider->GetParallelSpeculativeConnectLimit(
406 &args->mParallelSpeculativeConnectLimit);
407 overrider->GetIgnoreIdle(&args->mIgnoreIdle);
408 overrider->GetIgnorePossibleSpdyConnections(
409 &args->mIgnorePossibleSpdyConnections);
410 }
411
412 nsresult rv =
413 PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
414 if (NS_SUCCEEDED(rv))
415 unused << args.forget();
416 return rv;
417 }
418
419 nsresult
420 nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
421 {
422 EnsureSocketThreadTarget();
423
424 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
425 NS_IF_ADDREF(*target = mSocketThreadTarget);
426 return NS_OK;
427 }
428
429 nsresult
430 nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
431 {
432 LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%x]\n", conn));
433
434 NS_ADDREF(conn);
435 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
436 if (NS_FAILED(rv))
437 NS_RELEASE(conn);
438 return rv;
439 }
440
441 // A structure used to marshall 2 pointers across the various necessary
442 // threads to complete an HTTP upgrade.
443 class nsCompleteUpgradeData
444 {
445 public:
446 nsCompleteUpgradeData(nsAHttpConnection *aConn,
447 nsIHttpUpgradeListener *aListener)
448 : mConn(aConn), mUpgradeListener(aListener) {}
449
450 nsRefPtr<nsAHttpConnection> mConn;
451 nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
452 };
453
454 nsresult
455 nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
456 nsIHttpUpgradeListener *aUpgradeListener)
457 {
458 nsCompleteUpgradeData *data =
459 new nsCompleteUpgradeData(aConn, aUpgradeListener);
460 nsresult rv;
461 rv = PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
462 if (NS_FAILED(rv))
463 delete data;
464 return rv;
465 }
466
467 nsresult
468 nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value)
469 {
470 uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
471 return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0,
472 (void *)(uintptr_t) param);
473 }
474
475 nsresult
476 nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
477 {
478 LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
479
480 NS_ADDREF(ci);
481 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
482 if (NS_FAILED(rv))
483 NS_RELEASE(ci);
484 return rv;
485 }
486
487 nsresult
488 nsHttpConnectionMgr::ProcessPendingQ()
489 {
490 LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
491 return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
492 }
493
494 void
495 nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, void *param)
496 {
497 nsRefPtr<EventTokenBucket> tokenBucket =
498 dont_AddRef(static_cast<EventTokenBucket *>(param));
499 gHttpHandler->SetRequestTokenBucket(tokenBucket);
500 }
501
502 nsresult
503 nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket)
504 {
505 nsRefPtr<EventTokenBucket> bucket(aBucket);
506
507 // Call From main thread when a new EventTokenBucket has been made in order
508 // to post the new value to the socket thread.
509 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
510 0, bucket);
511 if (NS_SUCCEEDED(rv))
512 unused << bucket.forget();
513 return rv;
514 }
515
516 // Given a nsHttpConnectionInfo find the connection entry object that
517 // contains either the nshttpconnection or nshttptransaction parameter.
518 // Normally this is done by the hashkey lookup of connectioninfo,
519 // but if spdy coalescing is in play it might be found in a redirected
520 // entry
521 nsHttpConnectionMgr::nsConnectionEntry *
522 nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
523 nsHttpConnection *conn,
524 nsHttpTransaction *trans)
525 {
526 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
527 if (!ci)
528 return nullptr;
529
530 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
531
532 // If there is no sign of coalescing (or it is disabled) then just
533 // return the primary hash lookup
534 if (!ent || !ent->mUsingSpdy || ent->mCoalescingKey.IsEmpty())
535 return ent;
536
537 // If there is no preferred coalescing entry for this host (or the
538 // preferred entry is the one that matched the mCT hash lookup) then
539 // there is only option
540 nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey);
541 if (!preferred || (preferred == ent))
542 return ent;
543
544 if (conn) {
545 // The connection could be either in preferred or ent. It is most
546 // likely the only active connection in preferred - so start with that.
547 if (preferred->mActiveConns.Contains(conn))
548 return preferred;
549 if (preferred->mIdleConns.Contains(conn))
550 return preferred;
551 }
552
553 if (trans && preferred->mPendingQ.Contains(trans))
554 return preferred;
555
556 // Neither conn nor trans found in preferred, use the default entry
557 return ent;
558 }
559
560 nsresult
561 nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
562 {
563 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
564 LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
565 this, conn));
566
567 if (!conn->ConnectionInfo())
568 return NS_ERROR_UNEXPECTED;
569
570 nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
571 conn, nullptr);
572
573 if (!ent || !ent->mIdleConns.RemoveElement(conn))
574 return NS_ERROR_UNEXPECTED;
575
576 conn->Close(NS_ERROR_ABORT);
577 NS_RELEASE(conn);
578 mNumIdleConns--;
579 ConditionallyStopPruneDeadConnectionsTimer();
580 return NS_OK;
581 }
582
583 // This function lets a connection, after completing the NPN phase,
584 // report whether or not it is using spdy through the usingSpdy
585 // argument. It would not be necessary if NPN were driven out of
586 // the connection manager. The connection entry associated with the
587 // connection is then updated to indicate whether or not we want to use
588 // spdy with that host and update the preliminary preferred host
589 // entries used for de-sharding hostsnames.
590 void
591 nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
592 bool usingSpdy)
593 {
594 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
595
596 nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
597 conn, nullptr);
598
599 if (!ent)
600 return;
601
602 ent->mTestedSpdy = true;
603
604 if (!usingSpdy)
605 return;
606
607 ent->mUsingSpdy = true;
608 mNumSpdyActiveConns++;
609
610 uint32_t ttl = conn->TimeToLive();
611 uint64_t timeOfExpire = NowInSeconds() + ttl;
612 if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
613 PruneDeadConnectionsAfter(ttl);
614
615 // Lookup preferred directly from the hash instead of using
616 // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
617 // check at this point because the cert is never part of the hash
618 // lookup. Filtering on that has to be done at the time of use
619 // rather than the time of registration (i.e. now).
620 nsConnectionEntry *joinedConnection;
621 nsConnectionEntry *preferred =
622 mSpdyPreferredHash.Get(ent->mCoalescingKey);
623
624 LOG(("ReportSpdyConnection %s %s ent=%p preferred=%p\n",
625 ent->mConnInfo->Host(), ent->mCoalescingKey.get(),
626 ent, preferred));
627
628 if (!preferred) {
629 if (!ent->mCoalescingKey.IsEmpty()) {
630 mSpdyPreferredHash.Put(ent->mCoalescingKey, ent);
631 ent->mSpdyPreferred = true;
632 preferred = ent;
633 }
634 } else if ((preferred != ent) &&
635 (joinedConnection = GetSpdyPreferredEnt(ent)) &&
636 (joinedConnection != ent)) {
637 //
638 // A connection entry (e.g. made with a different hostname) with
639 // the same IP address is preferred for future transactions over this
640 // connection entry. Gracefully close down the connection to help
641 // new transactions migrate over.
642
643 LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
644 "migrate to preferred\n", conn, ent));
645
646 conn->DontReuse();
647 } else if (preferred != ent) {
648 LOG (("ReportSpdyConnection preferred host may be in false start or "
649 "may have insufficient cert. Leave mapping in place but do not "
650 "abandon this connection yet."));
651 }
652
653 PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
654 }
655
656 void
657 nsHttpConnectionMgr::ReportSpdyCWNDSetting(nsHttpConnectionInfo *ci,
658 uint32_t cwndValue)
659 {
660 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
661
662 if (!gHttpHandler->UseSpdyPersistentSettings())
663 return;
664
665 if (!ci)
666 return;
667
668 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
669 if (!ent)
670 return;
671
672 ent = GetSpdyPreferredEnt(ent);
673 if (!ent) // just to be thorough - but that map should always exist
674 return;
675
676 cwndValue = std::max(2U, cwndValue);
677 cwndValue = std::min(128U, cwndValue);
678
679 ent->mSpdyCWND = cwndValue;
680 ent->mSpdyCWNDTimeStamp = TimeStamp::Now();
681 return;
682 }
683
684 // a value of 0 means no setting is available
685 uint32_t
686 nsHttpConnectionMgr::GetSpdyCWNDSetting(nsHttpConnectionInfo *ci)
687 {
688 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
689
690 if (!gHttpHandler->UseSpdyPersistentSettings())
691 return 0;
692
693 if (!ci)
694 return 0;
695
696 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
697 if (!ent)
698 return 0;
699
700 ent = GetSpdyPreferredEnt(ent);
701 if (!ent) // just to be thorough - but that map should always exist
702 return 0;
703
704 if (ent->mSpdyCWNDTimeStamp.IsNull())
705 return 0;
706
707 // For privacy tracking reasons, and the fact that CWND is not
708 // meaningful after some time, we don't honor stored CWND after 8
709 // hours.
710 TimeDuration age = TimeStamp::Now() - ent->mSpdyCWNDTimeStamp;
711 if (age.ToMilliseconds() > (1000 * 60 * 60 * 8))
712 return 0;
713
714 return ent->mSpdyCWND;
715 }
716
717 nsHttpConnectionMgr::nsConnectionEntry *
718 nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
719 {
720 if (!gHttpHandler->IsSpdyEnabled() ||
721 !gHttpHandler->CoalesceSpdy() ||
722 aOriginalEntry->mCoalescingKey.IsEmpty())
723 return nullptr;
724
725 nsConnectionEntry *preferred =
726 mSpdyPreferredHash.Get(aOriginalEntry->mCoalescingKey);
727
728 // if there is no redirection no cert validation is required
729 if (preferred == aOriginalEntry)
730 return aOriginalEntry;
731
732 // if there is no preferred host or it is no longer using spdy
733 // then skip pooling
734 if (!preferred || !preferred->mUsingSpdy)
735 return nullptr;
736
737 // if there is not an active spdy session in this entry then
738 // we cannot pool because the cert upon activation may not
739 // be the same as the old one. Active sessions are prohibited
740 // from changing certs.
741
742 nsHttpConnection *activeSpdy = nullptr;
743
744 for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) {
745 if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
746 activeSpdy = preferred->mActiveConns[index];
747 break;
748 }
749 }
750
751 if (!activeSpdy) {
752 // remove the preferred status of this entry if it cannot be
753 // used for pooling.
754 preferred->mSpdyPreferred = false;
755 RemoveSpdyPreferredEnt(preferred->mCoalescingKey);
756 LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection "
757 "preferred host mapping %s to %s removed due to inactivity.\n",
758 aOriginalEntry->mConnInfo->Host(),
759 preferred->mConnInfo->Host()));
760
761 return nullptr;
762 }
763
764 // Check that the server cert supports redirection
765 nsresult rv;
766 bool isJoined = false;
767
768 nsCOMPtr<nsISupports> securityInfo;
769 nsCOMPtr<nsISSLSocketControl> sslSocketControl;
770 nsAutoCString negotiatedNPN;
771
772 activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo));
773 if (!securityInfo) {
774 NS_WARNING("cannot obtain spdy security info");
775 return nullptr;
776 }
777
778 sslSocketControl = do_QueryInterface(securityInfo, &rv);
779 if (NS_FAILED(rv)) {
780 NS_WARNING("sslSocketControl QI Failed");
781 return nullptr;
782 }
783
784 if (gHttpHandler->SpdyInfo()->ProtocolEnabled(0))
785 rv = sslSocketControl->JoinConnection(gHttpHandler->SpdyInfo()->VersionString[0],
786 aOriginalEntry->mConnInfo->GetHost(),
787 aOriginalEntry->mConnInfo->Port(),
788 &isJoined);
789 else
790 rv = NS_OK; /* simulate failed join */
791
792 // JoinConnection() may have failed due to spdy version level. Try the other
793 // level we support (if any)
794 if (NS_SUCCEEDED(rv) && !isJoined && gHttpHandler->SpdyInfo()->ProtocolEnabled(1)) {
795 rv = sslSocketControl->JoinConnection(gHttpHandler->SpdyInfo()->VersionString[1],
796 aOriginalEntry->mConnInfo->GetHost(),
797 aOriginalEntry->mConnInfo->Port(),
798 &isJoined);
799 }
800
801 if (NS_FAILED(rv) || !isJoined) {
802 LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection "
803 "Host %s cannot be confirmed to be joined "
804 "with %s connections. rv=%x isJoined=%d",
805 preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(),
806 rv, isJoined));
807 Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false);
808 return nullptr;
809 }
810
811 // IP pooling confirmed
812 LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection "
813 "Host %s has cert valid for %s connections, "
814 "so %s will be coalesced with %s",
815 preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(),
816 aOriginalEntry->mConnInfo->Host(), preferred->mConnInfo->Host()));
817 Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
818 return preferred;
819 }
820
821 void
822 nsHttpConnectionMgr::RemoveSpdyPreferredEnt(nsACString &aHashKey)
823 {
824 if (aHashKey.IsEmpty())
825 return;
826
827 mSpdyPreferredHash.Remove(aHashKey);
828 }
829
830 //-----------------------------------------------------------------------------
831 // enumeration callbacks
832
833 PLDHashOperator
834 nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
835 nsAutoPtr<nsConnectionEntry> &ent,
836 void *closure)
837 {
838 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
839
840 if (self->ProcessPendingQForEntry(ent, false))
841 return PL_DHASH_STOP;
842
843 return PL_DHASH_NEXT;
844 }
845
846 PLDHashOperator
847 nsHttpConnectionMgr::ProcessAllTransactionsCB(const nsACString &key,
848 nsAutoPtr<nsConnectionEntry> &ent,
849 void *closure)
850 {
851 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
852 self->ProcessPendingQForEntry(ent, true);
853 return PL_DHASH_NEXT;
854 }
855
856 // If the global number of connections is preventing the opening of
857 // new connections to a host without idle connections, then
858 // close them regardless of their TTL
859 PLDHashOperator
860 nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key,
861 nsAutoPtr<nsConnectionEntry> &ent,
862 void *closure)
863 {
864 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
865
866 while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) {
867 if (!ent->mIdleConns.Length()) {
868 // There are no idle conns left in this connection entry
869 return PL_DHASH_NEXT;
870 }
871 nsHttpConnection *conn = ent->mIdleConns[0];
872 ent->mIdleConns.RemoveElementAt(0);
873 conn->Close(NS_ERROR_ABORT);
874 NS_RELEASE(conn);
875 self->mNumIdleConns--;
876 self->ConditionallyStopPruneDeadConnectionsTimer();
877 }
878 return PL_DHASH_STOP;
879 }
880
881 // If the global number of connections is preventing the opening of
882 // new connections to a host without idle connections, then
883 // close any spdy asap
884 PLDHashOperator
885 nsHttpConnectionMgr::PurgeExcessSpdyConnectionsCB(const nsACString &key,
886 nsAutoPtr<nsConnectionEntry> &ent,
887 void *closure)
888 {
889 if (!ent->mUsingSpdy)
890 return PL_DHASH_NEXT;
891
892 nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
893 for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
894 nsHttpConnection *conn = ent->mActiveConns[index];
895 if (conn->UsingSpdy() && conn->CanReuse()) {
896 conn->DontReuse();
897 // stop on <= (particularly =) beacuse this dontreuse causes async close
898 if (self->mNumIdleConns + self->mNumActiveConns + 1 <= self->mMaxConns)
899 return PL_DHASH_STOP;
900 }
901 }
902 return PL_DHASH_NEXT;
903 }
904
905 PLDHashOperator
906 nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key,
907 nsAutoPtr<nsConnectionEntry> &ent,
908 void *closure)
909 {
910 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
911 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
912
913 LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
914
915 // Find out how long it will take for next idle connection to not be reusable
916 // anymore.
917 uint32_t timeToNextExpire = UINT32_MAX;
918 int32_t count = ent->mIdleConns.Length();
919 if (count > 0) {
920 for (int32_t i=count-1; i>=0; --i) {
921 nsHttpConnection *conn = ent->mIdleConns[i];
922 if (!conn->CanReuse()) {
923 ent->mIdleConns.RemoveElementAt(i);
924 conn->Close(NS_ERROR_ABORT);
925 NS_RELEASE(conn);
926 self->mNumIdleConns--;
927 } else {
928 timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
929 }
930 }
931 }
932
933 if (ent->mUsingSpdy) {
934 for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
935 nsHttpConnection *conn = ent->mActiveConns[index];
936 if (conn->UsingSpdy()) {
937 if (!conn->CanReuse()) {
938 // marking it dont reuse will create an active tear down if
939 // the spdy session is idle.
940 conn->DontReuse();
941 }
942 else {
943 timeToNextExpire = std::min(timeToNextExpire,
944 conn->TimeToLive());
945 }
946 }
947 }
948 }
949
950 // If time to next expire found is shorter than time to next wake-up, we need to
951 // change the time for next wake-up.
952 if (timeToNextExpire != UINT32_MAX) {
953 uint32_t now = NowInSeconds();
954 uint64_t timeOfNextExpire = now + timeToNextExpire;
955 // If pruning of dead connections is not already scheduled to happen
956 // or time found for next connection to expire is is before
957 // mTimeOfNextWakeUp, we need to schedule the pruning to happen
958 // after timeToNextExpire.
959 if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) {
960 self->PruneDeadConnectionsAfter(timeToNextExpire);
961 }
962 } else {
963 self->ConditionallyStopPruneDeadConnectionsTimer();
964 }
965
966 // if this entry is empty, we have too many entries,
967 // and this doesn't represent some painfully determined
968 // red condition, then we can clean it up and restart from
969 // yellow
970 if (ent->PipelineState() != PS_RED &&
971 self->mCT.Count() > 125 &&
972 ent->mIdleConns.Length() == 0 &&
973 ent->mActiveConns.Length() == 0 &&
974 ent->mHalfOpens.Length() == 0 &&
975 ent->mPendingQ.Length() == 0 &&
976 ((!ent->mTestedSpdy && !ent->mUsingSpdy) ||
977 !gHttpHandler->IsSpdyEnabled() ||
978 self->mCT.Count() > 300)) {
979 LOG((" removing empty connection entry\n"));
980 return PL_DHASH_REMOVE;
981 }
982
983 // otherwise use this opportunity to compact our arrays...
984 ent->mIdleConns.Compact();
985 ent->mActiveConns.Compact();
986 ent->mPendingQ.Compact();
987
988 return PL_DHASH_NEXT;
989 }
990
991 PLDHashOperator
992 nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key,
993 nsAutoPtr<nsConnectionEntry> &ent,
994 void *closure)
995 {
996 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
997
998 nsHttpTransaction *trans;
999 nsHttpConnection *conn;
1000
1001 // close all active connections
1002 while (ent->mActiveConns.Length()) {
1003 conn = ent->mActiveConns[0];
1004
1005 ent->mActiveConns.RemoveElementAt(0);
1006 self->DecrementActiveConnCount(conn);
1007
1008 conn->Close(NS_ERROR_ABORT);
1009 NS_RELEASE(conn);
1010 }
1011
1012 // close all idle connections
1013 while (ent->mIdleConns.Length()) {
1014 conn = ent->mIdleConns[0];
1015
1016 ent->mIdleConns.RemoveElementAt(0);
1017 self->mNumIdleConns--;
1018
1019 conn->Close(NS_ERROR_ABORT);
1020 NS_RELEASE(conn);
1021 }
1022 // If all idle connections are removed,
1023 // we can stop pruning dead connections.
1024 self->ConditionallyStopPruneDeadConnectionsTimer();
1025
1026 // close all pending transactions
1027 while (ent->mPendingQ.Length()) {
1028 trans = ent->mPendingQ[0];
1029
1030 ent->mPendingQ.RemoveElementAt(0);
1031
1032 trans->Close(NS_ERROR_ABORT);
1033 NS_RELEASE(trans);
1034 }
1035
1036 // close all half open tcp connections
1037 for (int32_t i = ((int32_t) ent->mHalfOpens.Length()) - 1; i >= 0; i--)
1038 ent->mHalfOpens[i]->Abandon();
1039
1040 return PL_DHASH_REMOVE;
1041 }
1042
1043 //-----------------------------------------------------------------------------
1044
1045 bool
1046 nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
1047 {
1048 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1049
1050 LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
1051 ent->mConnInfo->HashKey().get()));
1052
1053 ProcessSpdyPendingQ(ent);
1054
1055 nsHttpTransaction *trans;
1056 nsresult rv;
1057 bool dispatchedSuccessfully = false;
1058 int dispatchCount = 0;
1059 #ifdef WTF_DEBUG
1060 uint32_t total = ent->mPendingQ.Length();
1061 #endif
1062
1063 // if !considerAll iterate the pending list until one is dispatched successfully.
1064 // Keep iterating afterwards only until a transaction fails to dispatch.
1065 // if considerAll == true then try and dispatch all items.
1066 for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) {
1067 trans = ent->mPendingQ[i];
1068
1069 // When this entry has already established a half-open
1070 // connection, we want to prevent any duplicate half-open
1071 // connections from being established and bound to this
1072 // transaction.
1073 bool alreadyHalfOpen = false;
1074 if (ent->SupportsPipelining()) {
1075 alreadyHalfOpen = (ent->UnconnectedHalfOpens() > 0);
1076 } else {
1077 for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
1078 if (ent->mHalfOpens[j]->Transaction() == trans) {
1079 alreadyHalfOpen = true;
1080 break;
1081 }
1082 }
1083 }
1084
1085 rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans);
1086 if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
1087 if (NS_SUCCEEDED(rv))
1088 LOG((" dispatching pending transaction...\n"));
1089 else
1090 LOG((" removing pending transaction based on "
1091 "TryDispatchTransaction returning hard error %x\n", rv));
1092
1093 if (ent->mPendingQ.RemoveElement(trans)) {
1094 dispatchedSuccessfully = true;
1095 dispatchCount++;
1096 NS_RELEASE(trans);
1097 continue; // dont ++i as we just made the array shorter
1098 }
1099
1100 LOG((" transaction not found in pending queue\n"));
1101 }
1102
1103 // We want to keep walking the dispatch table to ensure requests
1104 // get combined properly.
1105 //if (dispatchedSuccessfully && !considerAll)
1106 // break;
1107
1108 ++i;
1109 }
1110
1111 #ifdef WTF_DEBUG
1112 if (dispatchedSuccessfully) {
1113 fprintf(stderr, "WTF-queue: Dispatched %d/%d pending transactions for %s\n",
1114 dispatchCount, total, ent->mConnInfo->Host());
1115 return true;
1116 }
1117 #endif
1118
1119 return dispatchedSuccessfully;
1120 }
1121
1122 bool
1123 nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
1124 {
1125 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1126
1127 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
1128 if (ent)
1129 return ProcessPendingQForEntry(ent, false);
1130 return false;
1131 }
1132
1133 bool
1134 nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
1135 {
1136 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1137
1138 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
1139 if (ent)
1140 return ent->SupportsPipelining();
1141 return false;
1142 }
1143
1144 // nsHttpPipelineFeedback used to hold references across events
1145
1146 class nsHttpPipelineFeedback
1147 {
1148 public:
1149 nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
1150 nsHttpConnectionMgr::PipelineFeedbackInfoType info,
1151 nsHttpConnection *conn, uint32_t data)
1152 : mConnInfo(ci)
1153 , mConn(conn)
1154 , mInfo(info)
1155 , mData(data)
1156 {
1157 }
1158
1159 ~nsHttpPipelineFeedback()
1160 {
1161 }
1162
1163 nsRefPtr<nsHttpConnectionInfo> mConnInfo;
1164 nsRefPtr<nsHttpConnection> mConn;
1165 nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
1166 uint32_t mData;
1167 };
1168
1169 void
1170 nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
1171 PipelineFeedbackInfoType info,
1172 nsHttpConnection *conn,
1173 uint32_t data)
1174 {
1175 if (!ci)
1176 return;
1177
1178 // Post this to the socket thread if we are not running there already
1179 if (PR_GetCurrentThread() != gSocketThread) {
1180 nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info,
1181 conn, data);
1182
1183 nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback,
1184 0, fb);
1185 if (NS_FAILED(rv))
1186 delete fb;
1187 return;
1188 }
1189
1190 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
1191
1192 if (ent)
1193 ent->OnPipelineFeedbackInfo(info, conn, data);
1194 }
1195
1196 void
1197 nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
1198 {
1199 MOZ_ASSERT(uri);
1200
1201 nsAutoCString host;
1202 int32_t port = -1;
1203 nsAutoCString username;
1204 bool usingSSL = false;
1205 bool isHttp = false;
1206
1207 nsresult rv = uri->SchemeIs("https", &usingSSL);
1208 if (NS_SUCCEEDED(rv) && usingSSL)
1209 isHttp = true;
1210 if (NS_SUCCEEDED(rv) && !isHttp)
1211 rv = uri->SchemeIs("http", &isHttp);
1212 if (NS_SUCCEEDED(rv))
1213 rv = uri->GetAsciiHost(host);
1214 if (NS_SUCCEEDED(rv))
1215 rv = uri->GetPort(&port);
1216 if (NS_SUCCEEDED(rv))
1217 uri->GetUsername(username);
1218 if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
1219 return;
1220
1221 // report the event for all the permutations of anonymous and
1222 // private versions of this host
1223 nsRefPtr<nsHttpConnectionInfo> ci =
1224 new nsHttpConnectionInfo(host, port, username, nullptr, usingSSL);
1225 ci->SetAnonymous(false);
1226 ci->SetPrivate(false);
1227 PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
1228
1229 ci = ci->Clone();
1230 ci->SetAnonymous(false);
1231 ci->SetPrivate(true);
1232 PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
1233
1234 ci = ci->Clone();
1235 ci->SetAnonymous(true);
1236 ci->SetPrivate(false);
1237 PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
1238
1239 ci = ci->Clone();
1240 ci->SetAnonymous(true);
1241 ci->SetPrivate(true);
1242 PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
1243 }
1244
1245 // we're at the active connection limit if any one of the following conditions is true:
1246 // (1) at max-connections
1247 // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
1248 // (3) keep-alive disabled and at max-connections-per-server
1249 bool
1250 nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps)
1251 {
1252 nsHttpConnectionInfo *ci = ent->mConnInfo;
1253
1254 LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
1255 ci->HashKey().get(), caps));
1256
1257 // update maxconns if potentially limited by the max socket count
1258 // this requires a dynamic reduction in the max socket count to a point
1259 // lower than the max-connections pref.
1260 uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
1261 if (mMaxConns > maxSocketCount) {
1262 mMaxConns = maxSocketCount;
1263 LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u",
1264 this, mMaxConns));
1265 }
1266
1267 // If there are more active connections than the global limit, then we're
1268 // done. Purging idle connections won't get us below it.
1269 if (mNumActiveConns >= mMaxConns) {
1270 LOG((" num active conns == max conns\n"));
1271 return true;
1272 }
1273
1274 // Add in the in-progress tcp connections, we will assume they are
1275 // keepalive enabled.
1276 // Exclude half-open's that has already created a usable connection.
1277 // This prevents the limit being stuck on ipv6 connections that
1278 // eventually time out after typical 21 seconds of no ACK+SYN reply.
1279 uint32_t totalCount =
1280 ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
1281
1282 uint16_t maxPersistConns;
1283
1284 if (ci->UsingHttpProxy() && !ci->UsingConnect())
1285 maxPersistConns = mMaxPersistConnsPerProxy;
1286 else
1287 maxPersistConns = mMaxPersistConnsPerHost;
1288
1289 LOG((" connection count = %d, limit %d\n", totalCount, maxPersistConns));
1290
1291 // use >= just to be safe
1292 bool result = (totalCount >= maxPersistConns);
1293 LOG((" result: %s", result ? "true" : "false"));
1294 return result;
1295 }
1296
1297 void
1298 nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent)
1299 {
1300 LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
1301 ent->mConnInfo->HashKey().get()));
1302 while (ent->mIdleConns.Length()) {
1303 nsHttpConnection *conn = ent->mIdleConns[0];
1304 ent->mIdleConns.RemoveElementAt(0);
1305 mNumIdleConns--;
1306 conn->Close(NS_ERROR_ABORT);
1307 NS_RELEASE(conn);
1308 }
1309
1310 int32_t activeCount = ent->mActiveConns.Length();
1311 for (int32_t i=0; i < activeCount; i++)
1312 ent->mActiveConns[i]->DontReuse();
1313 }
1314
1315 PLDHashOperator
1316 nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key,
1317 nsAutoPtr<nsConnectionEntry> &ent,
1318 void *closure)
1319 {
1320 nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
1321 self->ClosePersistentConnections(ent);
1322 return PL_DHASH_NEXT;
1323 }
1324
1325 bool
1326 nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent,
1327 bool ignorePossibleSpdyConnections)
1328 {
1329 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1330
1331 // If this host is trying to negotiate a SPDY session right now,
1332 // don't create any new ssl connections until the result of the
1333 // negotiation is known.
1334
1335 bool doRestrict = ent->mConnInfo->UsingSSL() &&
1336 gHttpHandler->IsSpdyEnabled() &&
1337 ((!ent->mTestedSpdy && !ignorePossibleSpdyConnections) ||
1338 ent->mUsingSpdy) &&
1339 (ent->mHalfOpens.Length() || ent->mActiveConns.Length());
1340
1341 // If there are no restrictions, we are done
1342 if (!doRestrict)
1343 return false;
1344
1345 // If the restriction is based on a tcp handshake in progress
1346 // let that connect and then see if it was SPDY or not
1347 if (ent->UnconnectedHalfOpens() && !ignorePossibleSpdyConnections)
1348 return true;
1349
1350 // There is a concern that a host is using a mix of HTTP/1 and SPDY.
1351 // In that case we don't want to restrict connections just because
1352 // there is a single active HTTP/1 session in use.
1353 if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
1354 bool confirmedRestrict = false;
1355 for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
1356 nsHttpConnection *conn = ent->mActiveConns[index];
1357 if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
1358 confirmedRestrict = true;
1359 break;
1360 }
1361 }
1362 doRestrict = confirmedRestrict;
1363 if (!confirmedRestrict) {
1364 LOG(("nsHttpConnectionMgr spdy connection restriction to "
1365 "%s bypassed.\n", ent->mConnInfo->Host()));
1366 }
1367 }
1368 return doRestrict;
1369 }
1370
1371 // returns NS_OK if a connection was started
1372 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
1373 // ephemeral limits
1374 // returns other NS_ERROR on hard failure conditions
1375 nsresult
1376 nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
1377 nsHttpTransaction *trans)
1378 {
1379 LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
1380 this, ent, trans));
1381 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1382
1383 uint32_t halfOpenLength = ent->mHalfOpens.Length();
1384 for (uint32_t i = 0; i < halfOpenLength; i++) {
1385 if (ent->mHalfOpens[i]->IsSpeculative()) {
1386 // We've found a speculative connection in the half
1387 // open list. Remove the speculative bit from it and that
1388 // connection can later be used for this transaction
1389 // (or another one in the pending queue) - we don't
1390 // need to open a new connection here.
1391 LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
1392 "Found a speculative half open connection\n",
1393 ent->mConnInfo->HashKey().get()));
1394 ent->mHalfOpens[i]->SetSpeculative(false);
1395
1396 // return OK because we have essentially opened a new connection
1397 // by converting a speculative half-open to general use
1398 return NS_OK;
1399 }
1400 }
1401
1402 // If this host is trying to negotiate a SPDY session right now,
1403 // don't create any new connections until the result of the
1404 // negotiation is known.
1405 if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
1406 (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
1407 RestrictConnections(ent)) {
1408 LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
1409 "Not Available Due to RestrictConnections()\n",
1410 ent->mConnInfo->HashKey().get()));
1411 return NS_ERROR_NOT_AVAILABLE;
1412 }
1413
1414 // We need to make a new connection. If that is going to exceed the
1415 // global connection limit then try and free up some room by closing
1416 // an idle connection to another host. We know it won't select "ent"
1417 // beacuse we have already determined there are no idle connections
1418 // to our destination
1419
1420 if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns)
1421 mCT.Enumerate(PurgeExcessIdleConnectionsCB, this);
1422
1423 if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) &&
1424 mNumActiveConns && gHttpHandler->IsSpdyEnabled())
1425 mCT.Enumerate(PurgeExcessSpdyConnectionsCB, this);
1426
1427 if (AtActiveConnectionLimit(ent, trans->Caps()))
1428 return NS_ERROR_NOT_AVAILABLE;
1429
1430 #ifdef WTF_DEBUG
1431 fprintf(stderr, "WTF: MakeNewConnection() is creating a transport (pipelines %d) for host %s\n",
1432 ent->SupportsPipelining(), ent->mConnInfo->Host());
1433 #endif
1434 nsresult rv = CreateTransport(ent, trans, trans->Caps(), false);
1435 if (NS_FAILED(rv)) {
1436 /* hard failure */
1437 LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
1438 "CreateTransport() hard failure.\n",
1439 ent->mConnInfo->HashKey().get(), trans));
1440 trans->Close(rv);
1441 if (rv == NS_ERROR_NOT_AVAILABLE)
1442 rv = NS_ERROR_FAILURE;
1443 return rv;
1444 }
1445
1446 return NS_OK;
1447 }
1448
1449 bool
1450 nsHttpConnectionMgr::AddToBestPipeline(nsConnectionEntry *ent,
1451 nsHttpTransaction *trans,
1452 nsHttpTransaction::Classifier classification,
1453 uint16_t depthLimit)
1454 {
1455 if (classification == nsAHttpTransaction::CLASS_SOLO)
1456 return false;
1457
1458 uint32_t maxdepth = ent->MaxPipelineDepth(classification);
1459 if (maxdepth == 0) {
1460 ent->CreditPenalty();
1461 maxdepth = ent->MaxPipelineDepth(classification);
1462 }
1463
1464 if (ent->PipelineState() == PS_RED)
1465 return false;
1466
1467 if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
1468 return false;
1469
1470 // The maximum depth of a pipeline in yellow is 1 pipeline of
1471 // depth 2 for entire CI. When that transaction completes successfully
1472 // we transition to green and that expands the allowed depth
1473 // to any number of pipelines of up to depth 4. When a transaction
1474 // queued at position 3 or deeper succeeds we open it all the way
1475 // up to depths limited only by configuration. The staggered start
1476 // in green is simply because a successful yellow test of depth=2
1477 // might really just be a race condition (i.e. depth=1 from the
1478 // server's point of view), while depth=3 is a stronger indicator -
1479 // keeping the pipelines to a modest depth during that period limits
1480 // the damage if something is going to go wrong.
1481
1482 maxdepth = std::min<uint32_t>(maxdepth, depthLimit);
1483
1484 if (maxdepth < 2)
1485 return false;
1486
1487 // Find out how many requests of this class we have
1488 uint32_t sameClass = 0;
1489 uint32_t allClasses = ent->mPendingQ.Length();
1490 for (uint32_t i = 0; i < allClasses; ++i) {
1491 if (trans != ent->mPendingQ[i] &&
1492 classification == ent->mPendingQ[i]->Classification()) {
1493 sameClass++;
1494 }
1495 }
1496
1497 nsAHttpTransaction *activeTrans;
1498 nsHttpPipeline *pipeline;
1499 nsHttpConnection *bestConn = nullptr;
1500 uint32_t activeCount = ent->mActiveConns.Length();
1501 uint32_t pipelineDepth;
1502 uint32_t requestLen;
1503 uint32_t totalDepth = 0;
1504
1505 // Now, try to find the best pipeline
1506 nsTArray<nsHttpConnection *> validConns;
1507 nsTArray<nsHttpConnection *> betterConns;
1508 nsTArray<nsHttpConnection *> bestConns;
1509 uint32_t numPipelines = 0;
1510
1511 for (uint32_t i = 0; i < activeCount; ++i) {
1512 nsHttpConnection *conn = ent->mActiveConns[i];
1513
1514 if (!conn->SupportsPipelining())
1515 continue;
1516
1517 activeTrans = conn->Transaction();
1518
1519 if (!activeTrans ||
1520 activeTrans->IsDone() ||
1521 NS_FAILED(activeTrans->Status()))
1522 continue;
1523
1524 pipeline = activeTrans->QueryPipeline();
1525 if (!pipeline)
1526 continue;
1527
1528 numPipelines++;
1529
1530 pipelineDepth = activeTrans->PipelineDepth();
1531 requestLen = pipeline->RequestDepth();
1532
1533 totalDepth += pipelineDepth;
1534
1535 // If we're within striking distance of our pipeline
1536 // packaging goal, give a little slack on the depth
1537 // limit to allow us to try to get there. Don't give
1538 // too much slack, though, or we'll tend to have
1539 // request packages of the same size when we have
1540 // many content elements appear at once.
1541 if (maxdepth +
1542 PR_MIN(mMaxOptimisticPipelinedRequests,
1543 requestLen + allClasses)
1544 <= pipelineDepth)
1545 continue;
1546
1547 validConns.AppendElement(conn);
1548
1549 // Prefer a pipeline that either has at least two requests
1550 // queued already, or for which we can add multiple requests
1551 if (requestLen + allClasses < mMaxOptimisticPipelinedRequests)
1552 continue;
1553
1554 betterConns.AppendElement(conn);
1555
1556 // Prefer a pipeline with the same classification if
1557 // our current classes will put it over the line
1558 if (conn->Classification() != classification)
1559 continue;
1560 if (requestLen + sameClass < mMaxOptimisticPipelinedRequests)
1561 continue;
1562
1563 bestConns.AppendElement(conn);
1564 }
1565
1566 const char *type;
1567 if (bestConns.Length()) {
1568 type = "best";
1569 bestConn = bestConns[rand()%bestConns.Length()];
1570 } else if (betterConns.Length()) {
1571 type = "better";
1572 bestConn = betterConns[rand()%betterConns.Length()];
1573 } else if (validConns.Length() && totalDepth == 0) {
1574 // We only use valid conns if it's a last resort
1575 // (No other requests are pending or in flight)
1576 type = "valid";
1577 bestConn = validConns[rand()%validConns.Length()];
1578 } else {
1579 return false;
1580 }
1581
1582 activeTrans = bestConn->Transaction();
1583 nsresult rv = activeTrans->AddTransaction(trans);
1584 if (NS_FAILED(rv))
1585 return false;
1586
1587 LOG((" scheduling trans %p on pipeline at position %d, type %s\n",
1588 trans, trans->PipelinePosition(), type));
1589
1590 #ifdef WTF_DEBUG
1591 pipeline = activeTrans->QueryPipeline();
1592 fprintf(stderr,
1593 "WTF-depth: Added trans to %s of %d/%d/%d/%d pipelines. Request len %d/%d/%d for %s\n",
1594 type, bestConns.Length(), betterConns.Length(), validConns.Length(),
1595 numPipelines, pipeline->RequestDepth(), activeTrans->PipelineDepth(),
1596 maxdepth, ent->mConnInfo->Host());
1597 #endif
1598
1599 if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
1600 ent->SetYellowConnection(bestConn);
1601
1602 if (!trans->GetPendingTime().IsNull()) {
1603 if (trans->UsesPipelining())
1604 AccumulateTimeDelta(
1605 Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
1606 trans->GetPendingTime(), TimeStamp::Now());
1607 else
1608 AccumulateTimeDelta(
1609 Telemetry::TRANSACTION_WAIT_TIME_HTTP,
1610 trans->GetPendingTime(), TimeStamp::Now());
1611 trans->SetPendingTime(false);
1612 }
1613 return true;
1614 }
1615
1616 bool
1617 nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
1618 nsHttpTransaction::Classifier classification)
1619 {
1620 // A connection entry is declared to be "under pressure" if most of the
1621 // allowed parallel connections are already used up. In that case we want to
1622 // favor existing pipelines over more parallelism so as to reserve any
1623 // unused parallel connections for types that don't have existing pipelines.
1624 //
1625 // The definition of connection pressure is a pretty liberal one here - that
1626 // is why we are using the more restrictive maxPersist* counters.
1627 //
1628 // Pipelines are also favored when the requested classification is already
1629 // using 3 or more of the connections. Failure to do this could result in
1630 // one class (e.g. images) establishing self replenishing queues on all the
1631 // connections that would starve the other transaction types.
1632
1633 int32_t currentConns = ent->mActiveConns.Length();
1634 int32_t maxConns =
1635 (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ?
1636 mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
1637
1638 // Leave room for at least 3 distinct types to operate concurrently,
1639 // this satisfies the typical {html, js/css, img} page.
1640 if (currentConns >= (maxConns - 2))
1641 return true; /* prefer pipeline */
1642
1643 int32_t sameClass = 0;
1644 for (int32_t i = 0; i < currentConns; ++i)
1645 if (classification == ent->mActiveConns[i]->Classification())
1646 if (++sameClass == 3)
1647 return true; /* prefer pipeline */
1648
1649 return false; /* normal behavior */
1650 }
1651
1652 // returns OK if a connection is found for the transaction
1653 // and the transaction is started.
1654 // returns ERROR_NOT_AVAILABLE if no connection can be found and it
1655 // should be queued until circumstances change
1656 // returns other ERROR when transaction has a hard failure and should
1657 // not remain in the pending queue
1658 nsresult
1659 nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
1660 bool onlyReusedConnection,
1661 nsHttpTransaction *trans)
1662 {
1663 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1664 LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
1665 "[ci=%s caps=%x]\n",
1666 ent->mConnInfo->HashKey().get(), uint32_t(trans->Caps())));
1667
1668 nsHttpTransaction::Classifier classification = trans->Classification();
1669 uint32_t caps = trans->Caps();
1670
1671 bool allowNewPipelines = true;
1672
1673 // no keep-alive means no pipelines either
1674 if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
1675 caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
1676
1677 nsRefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
1678
1679 // step 0
1680 // look for existing spdy connection - that's always best because it is
1681 // essentially pipelining without head of line blocking
1682
1683 if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
1684 nsRefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
1685 if (conn) {
1686 if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) {
1687 LOG((" dispatch to spdy: [conn=%x]\n", conn.get()));
1688 trans->RemoveDispatchedAsBlocking(); /* just in case */
1689 DispatchTransaction(ent, trans, conn);
1690 return NS_OK;
1691 }
1692 unusedSpdyPersistentConnection = conn;
1693 }
1694 }
1695
1696 // If this is not a blocking transaction and the loadgroup for it is
1697 // currently processing one or more blocking transactions then we
1698 // need to just leave it in the queue until those are complete unless it is
1699 // explicitly marked as unblocked.
1700 if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
1701 if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
1702 nsILoadGroupConnectionInfo *loadGroupCI = trans->LoadGroupConnectionInfo();
1703 if (loadGroupCI) {
1704 uint32_t blockers = 0;
1705 if (NS_SUCCEEDED(loadGroupCI->GetBlockingTransactionCount(&blockers)) &&
1706 blockers) {
1707 // need to wait for blockers to clear
1708 LOG((" blocked by load group: [blockers=%d]\n", blockers));
1709 return NS_ERROR_NOT_AVAILABLE;
1710 }
1711 }
1712 }
1713 }
1714 else {
1715 // Mark the transaction and its load group as blocking right now to prevent
1716 // other transactions from being reordered in the queue due to slow syns.
1717 trans->DispatchedAsBlocking();
1718 }
1719
1720 // step 1: Try a pipeline
1721 if (caps & NS_HTTP_ALLOW_PIPELINING &&
1722 AddToBestPipeline(ent, trans, classification,
1723 mMaxPipelinedRequests)) {
1724 return NS_OK;
1725 }
1726
1727 // XXX: Kill this block? It's new.. but it may be needed for SPDY
1728 // Subject most transactions at high parallelism to rate pacing.
1729 // It will only be actually submitted to the
1730 // token bucket once, and if possible it is granted admission synchronously.
1731 // It is important to leave a transaction in the pending queue when blocked by
1732 // pacing so it can be found on cancel if necessary.
1733 // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
1734 // limited.
1735 if (gHttpHandler->UseRequestTokenBucket() &&
1736 (mNumActiveConns >= mNumSpdyActiveConns) && // just check for robustness sake
1737 ((mNumActiveConns - mNumSpdyActiveConns) >= gHttpHandler->RequestTokenBucketMinParallelism()) &&
1738 !(caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED))) {
1739 if (!trans->TryToRunPacedRequest()) {
1740 LOG((" blocked due to rate pacing\n"));
1741 return NS_ERROR_NOT_AVAILABLE;
1742 }
1743 }
1744
1745 // Step 2: Decide if we should forbid new pipeline creation.
1746 //
1747 // FIXME: We repurposed mMaxOptimisticPipelinedRequests here to mean:
1748 // "Don't make a new pipeline until you have this many requests pending and
1749 // no potential connections to put them on". It might be nice to give this
1750 // its own pref..
1751 if (HasPipelines(ent) &&
1752 ent->mPendingQ.Length() < mMaxOptimisticPipelinedRequests &&
1753 trans->Classification() != nsAHttpTransaction::CLASS_SOLO &&
1754 caps & NS_HTTP_ALLOW_PIPELINING)
1755 allowNewPipelines = false;
1756
1757 // step 3: consider an idle persistent connection
1758 if (allowNewPipelines && (caps & NS_HTTP_ALLOW_KEEPALIVE)) {
1759 nsRefPtr<nsHttpConnection> conn;
1760 while (!conn && (ent->mIdleConns.Length() > 0)) {
1761 conn = ent->mIdleConns[0];
1762 ent->mIdleConns.RemoveElementAt(0);
1763 mNumIdleConns--;
1764 nsHttpConnection *temp = conn;
1765 NS_RELEASE(temp);
1766
1767 // we check if the connection can be reused before even checking if
1768 // it is a "matching" connection.
1769 if (!conn->CanReuse()) {
1770 LOG((" dropping stale connection: [conn=%x]\n", conn.get()));
1771 conn->Close(NS_ERROR_ABORT);
1772 conn = nullptr;
1773 }
1774 else {
1775 LOG((" reusing connection [conn=%x]\n", conn.get()));
1776 conn->EndIdleMonitoring();
1777 }
1778
1779 // If there are no idle connections left at all, we need to make
1780 // sure that we are not pruning dead connections anymore.
1781 ConditionallyStopPruneDeadConnectionsTimer();
1782 }
1783 if (conn) {
1784 // This will update the class of the connection to be the class of
1785 // the transaction dispatched on it.
1786 AddActiveConn(conn, ent);
1787 DispatchTransaction(ent, trans, conn);
1788 return NS_OK;
1789 }
1790 }
1791
1792 // step 4: Maybe make a connection?
1793 if (!onlyReusedConnection && allowNewPipelines) {
1794 nsresult rv = MakeNewConnection(ent, trans);
1795 if (NS_SUCCEEDED(rv)) {
1796 // this function returns NOT_AVAILABLE for asynchronous connects
1797 return NS_ERROR_NOT_AVAILABLE;
1798 }
1799
1800 if (rv != NS_ERROR_NOT_AVAILABLE) {
1801 // not available return codes should try next step as they are
1802 // not hard errors. Other codes should stop now
1803 return rv;
1804 }
1805 }
1806
1807 // step 5
1808 if (unusedSpdyPersistentConnection) {
1809 // to avoid deadlocks, we need to throw away this perfectly valid SPDY
1810 // connection to make room for a new one that can service a no KEEPALIVE
1811 // request
1812 unusedSpdyPersistentConnection->DontReuse();
1813 }
1814
1815 // XXX: We dequeue and queue the same url here sometimes..
1816 #ifdef WTF_DEBUG
1817 nsHttpRequestHead *head = trans->RequestHead();
1818 fprintf(stderr, "WTF: Queuing url %s%s\n",
1819 ent->mConnInfo->Host(),
1820 head ? head->RequestURI().BeginReading() : "<unknown?>");
1821 #endif
1822
1823
1824 return NS_ERROR_NOT_AVAILABLE; /* queue it */
1825 }
1826
1827 nsresult
1828 nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
1829 nsHttpTransaction *trans,
1830 nsHttpConnection *conn)
1831 {
1832 uint32_t caps = trans->Caps();
1833 int32_t priority = trans->Priority();
1834 nsresult rv;
1835
1836 LOG(("nsHttpConnectionMgr::DispatchTransaction "
1837 "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n",
1838 ent->mConnInfo->HashKey().get(), trans, caps, conn, priority));
1839
1840 // It is possible for a rate-paced transaction to be dispatched independent
1841 // of the token bucket when the amount of parallelization has changed or
1842 // when a muxed connection (e.g. spdy or pipelines) becomes available.
1843 trans->CancelPacing(NS_OK);
1844
1845 if (conn->UsingSpdy()) {
1846 LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s,"
1847 "Connection host = %s\n",
1848 trans->ConnectionInfo()->Host(),
1849 conn->ConnectionInfo()->Host()));
1850 rv = conn->Activate(trans, caps, priority);
1851 MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
1852 if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
1853 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
1854 trans->GetPendingTime(), TimeStamp::Now());
1855 trans->SetPendingTime(false);
1856 }
1857 return rv;
1858 }
1859
1860 MOZ_ASSERT(conn && !conn->Transaction(),
1861 "DispatchTranaction() on non spdy active connection");
1862
1863 if (!(caps & NS_HTTP_ALLOW_PIPELINING))
1864 conn->Classify(nsAHttpTransaction::CLASS_SOLO);
1865 else
1866 conn->Classify(trans->Classification());
1867
1868 rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
1869
1870 if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
1871 if (trans->UsesPipelining())
1872 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
1873 trans->GetPendingTime(), TimeStamp::Now());
1874 else
1875 AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
1876 trans->GetPendingTime(), TimeStamp::Now());
1877 trans->SetPendingTime(false);
1878 }
1879 return rv;
1880 }
1881
1882
1883 // Use this method for dispatching nsAHttpTransction's. It can only safely be
1884 // used upon first use of a connection when NPN has not negotiated SPDY vs
1885 // HTTP/1 yet as multiplexing onto an existing SPDY session requires a
1886 // concrete nsHttpTransaction
1887 nsresult
1888 nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
1889 nsAHttpTransaction *aTrans,
1890 uint32_t caps,
1891 nsHttpConnection *conn,
1892 int32_t priority)
1893 {
1894 MOZ_ASSERT(!conn->UsingSpdy(),
1895 "Spdy Must Not Use DispatchAbstractTransaction");
1896 LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction "
1897 "[ci=%s trans=%x caps=%x conn=%x]\n",
1898 ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
1899
1900 /* Use pipeline datastructure even if connection does not currently qualify
1901 to pipeline this transaction because a different pipeline-eligible
1902 transaction might be placed on the active connection. Make an exception
1903 for CLASS_SOLO as that connection will never pipeline until it goes
1904 quiescent */
1905
1906 nsRefPtr<nsAHttpTransaction> transaction;
1907 nsresult rv;
1908 if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) {
1909 LOG((" using pipeline datastructure.\n"));
1910 nsRefPtr<nsHttpPipeline> pipeline;
1911 rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline));
1912 if (!NS_SUCCEEDED(rv))
1913 return rv;
1914 transaction = pipeline;
1915 #ifdef WTF_DEBUG
1916 if (HasPipelines(ent) &&
1917 ent->mPendingQ.Length()+1 < mMaxOptimisticPipelinedRequests) {
1918 fprintf(stderr, "WTF-new-bug: New pipeline created from %d idle conns for host %s with %d/%d pending\n",
1919 ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(),
1920 mMaxOptimisticPipelinedRequests);
1921 } else {
1922 fprintf(stderr, "WTF-new: New pipeline created from %d idle conns for host %s with %d/%d pending\n",
1923 ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(),
1924 mMaxOptimisticPipelinedRequests);
1925 }
1926 #endif
1927 }
1928 else {
1929 LOG((" not using pipeline datastructure due to class solo.\n"));
1930 transaction = aTrans;
1931 #ifdef WTF_TEST
1932 nsHttpRequestHead *head = transaction->RequestHead();
1933 fprintf(stderr, "WTF-order: Pipeline forbidden for url %s%s\n",
1934 ent->mConnInfo->Host(),
1935 head ? head->RequestURI().BeginReading() : "<unknown?>");
1936 #endif
1937 }
1938
1939 nsRefPtr<nsConnectionHandle> handle = new nsConnectionHandle(conn);
1940
1941 // give the transaction the indirect reference to the connection.
1942 transaction->SetConnection(handle);
1943
1944 rv = conn->Activate(transaction, caps, priority);
1945 if (NS_FAILED(rv)) {
1946 LOG((" conn->Activate failed [rv=%x]\n", rv));
1947 ent->mActiveConns.RemoveElement(conn);
1948 if (conn == ent->mYellowConnection)
1949 ent->OnYellowComplete();
1950 DecrementActiveConnCount(conn);
1951 ConditionallyStopTimeoutTick();
1952
1953 // sever back references to connection, and do so without triggering
1954 // a call to ReclaimConnection ;-)
1955 transaction->SetConnection(nullptr);
1956 NS_RELEASE(handle->mConn);
1957 // destroy the connection
1958 NS_RELEASE(conn);
1959 }
1960
1961 // As transaction goes out of scope it will drop the last refernece to the
1962 // pipeline if activation failed, in which case this will destroy
1963 // the pipeline, which will cause each the transactions owned by the
1964 // pipeline to be restarted.
1965
1966 return rv;
1967 }
1968
1969 nsresult
1970 nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
1971 nsAHttpTransaction *firstTrans,
1972 nsHttpPipeline **result)
1973 {
1974 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1975
1976 /* form a pipeline here even if nothing is pending so that we
1977 can stream-feed it as new transactions arrive */
1978
1979 /* the first transaction can go in unconditionally - 1 transaction
1980 on a nsHttpPipeline object is not a real HTTP pipeline */
1981
1982 nsRefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
1983 pipeline->AddTransaction(firstTrans);
1984 NS_ADDREF(*result = pipeline);
1985 return NS_OK;
1986 }
1987
1988 void
1989 nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent)
1990 {
1991 enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3 };
1992
1993 if (!ent->mConnInfo->UsingProxy())
1994 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
1995 else if (ent->mConnInfo->UsingHttpProxy())
1996 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
1997 else
1998 Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
1999 }
2000
2001 nsresult
2002 nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
2003 {
2004 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2005
2006 // since "adds" and "cancels" are processed asynchronously and because
2007 // various events might trigger an "add" directly on the socket thread,
2008 // we must take care to avoid dispatching a transaction that has already
2009 // been canceled (see bug 190001).
2010 if (NS_FAILED(trans->Status())) {
2011 LOG((" transaction was canceled... dropping event!\n"));
2012 return NS_OK;
2013 }
2014
2015 trans->SetPendingTime();
2016
2017 nsresult rv = NS_OK;
2018 nsHttpConnectionInfo *ci = trans->ConnectionInfo();
2019 MOZ_ASSERT(ci);
2020
2021 nsConnectionEntry *ent = GetOrCreateConnectionEntry(ci);
2022
2023 // SPDY coalescing of hostnames means we might redirect from this
2024 // connection entry onto the preferred one.
2025 nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
2026 if (preferredEntry && (preferredEntry != ent)) {
2027 LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
2028 "redirected via coalescing from %s to %s\n", trans,
2029 ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
2030
2031 ent = preferredEntry;
2032 }
2033
2034 ReportProxyTelemetry(ent);
2035
2036 // Check if the transaction already has a sticky reference to a connection.
2037 // If so, then we can just use it directly by transferring its reference
2038 // to the new connection variable instead of searching for a new one
2039
2040 nsAHttpConnection *wrappedConnection = trans->Connection();
2041 nsRefPtr<nsHttpConnection> conn;
2042 if (wrappedConnection)
2043 conn = dont_AddRef(wrappedConnection->TakeHttpConnection());
2044
2045 if (conn) {
2046 MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
2047 MOZ_ASSERT(((int32_t)ent->mActiveConns.IndexOf(conn)) != -1,
2048 "Sticky Connection Not In Active List");
2049 LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
2050 "sticky connection=%p\n", trans, conn.get()));
2051 trans->SetConnection(nullptr);
2052 #ifdef WTF_TEST
2053 fprintf(stderr, "WTF-bad: Sticky connection status on 1 transaction to host %s\n",
2054 ent->mConnInfo->Host());
2055 #endif
2056 rv = DispatchTransaction(ent, trans, conn);
2057 return rv;
2058 } else {
2059 // XXX: maybe check the queue first and directly call TryDispatch?
2060 InsertTransactionSorted(ent->mPendingQ, trans);
2061 NS_ADDREF(trans);
2062 ProcessPendingQForEntry(ent, true);
2063 return NS_OK;
2064 }
2065 }
2066
2067
2068 void
2069 nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
2070 nsConnectionEntry *ent)
2071 {
2072 NS_ADDREF(conn);
2073 ent->mActiveConns.AppendElement(conn);
2074 mNumActiveConns++;
2075 ActivateTimeoutTick();
2076 }
2077
2078 void
2079 nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn)
2080 {
2081 mNumActiveConns--;
2082 if (conn->EverUsedSpdy())
2083 mNumSpdyActiveConns--;
2084 }
2085
2086 void
2087 nsHttpConnectionMgr::StartedConnect()
2088 {
2089 mNumActiveConns++;
2090 ActivateTimeoutTick(); // likely disabled by RecvdConnect()
2091 }
2092
2093 void
2094 nsHttpConnectionMgr::RecvdConnect()
2095 {
2096 mNumActiveConns--;
2097 ConditionallyStopTimeoutTick();
2098 }
2099
2100 nsresult
2101 nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
2102 nsAHttpTransaction *trans,
2103 uint32_t caps,
2104 bool speculative)
2105 {
2106 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2107
2108 nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
2109 if (speculative)
2110 sock->SetSpeculative(true);
2111 nsresult rv = sock->SetupPrimaryStreams();
2112 NS_ENSURE_SUCCESS(rv, rv);
2113
2114 ent->mHalfOpens.AppendElement(sock);
2115 mNumHalfOpenConns++;
2116 return NS_OK;
2117 }
2118
2119 // This function tries to dispatch the pending spdy transactions on
2120 // the connection entry sent in as an argument. It will do so on the
2121 // active spdy connection either in that same entry or in the
2122 // redirected 'preferred' entry for the same coalescing hash key if
2123 // coalescing is enabled.
2124
2125 void
2126 nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
2127 {
2128 nsHttpConnection *conn = GetSpdyPreferredConn(ent);
2129 if (!conn || !conn->CanDirectlyActivate())
2130 return;
2131
2132 nsTArray<nsHttpTransaction*> leftovers;
2133 uint32_t index;
2134
2135 // Dispatch all the transactions we can
2136 for (index = 0;
2137 index < ent->mPendingQ.Length() && conn->CanDirectlyActivate();
2138 ++index) {
2139 nsHttpTransaction *trans = ent->mPendingQ[index];
2140
2141 if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
2142 trans->Caps() & NS_HTTP_DISALLOW_SPDY) {
2143 leftovers.AppendElement(trans);
2144 continue;
2145 }
2146
2147 nsresult rv = DispatchTransaction(ent, trans, conn);
2148 if (NS_FAILED(rv)) {
2149 // this cannot happen, but if due to some bug it does then
2150 // close the transaction
2151 MOZ_ASSERT(false, "Dispatch SPDY Transaction");
2152 LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
2153 trans));
2154 trans->Close(rv);
2155 }
2156 NS_RELEASE(trans);
2157 }
2158
2159 // Slurp up the rest of the pending queue into our leftovers bucket (we
2160 // might have some left if conn->CanDirectlyActivate returned false)
2161 for (; index < ent->mPendingQ.Length(); ++index) {
2162 nsHttpTransaction *trans = ent->mPendingQ[index];
2163 leftovers.AppendElement(trans);
2164 }
2165
2166 // Put the leftovers back in the pending queue and get rid of the
2167 // transactions we dispatched
2168 leftovers.SwapElements(ent->mPendingQ);
2169 leftovers.Clear();
2170 }
2171
2172 PLDHashOperator
2173 nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key,
2174 nsAutoPtr<nsConnectionEntry> &ent,
2175 void *closure)
2176 {
2177 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
2178 self->ProcessSpdyPendingQ(ent);
2179 return PL_DHASH_NEXT;
2180 }
2181
2182 void
2183 nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, void *)
2184 {
2185 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2186 LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
2187 mCT.Enumerate(ProcessSpdyPendingQCB, this);
2188 }
2189
2190 nsHttpConnection *
2191 nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
2192 {
2193 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2194 MOZ_ASSERT(ent);
2195
2196 nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent);
2197
2198 // this entry is spdy-enabled if it is involved in a redirect
2199 if (preferred)
2200 // all new connections for this entry will use spdy too
2201 ent->mUsingSpdy = true;
2202 else
2203 preferred = ent;
2204
2205 nsHttpConnection *conn = nullptr;
2206
2207 if (preferred->mUsingSpdy) {
2208 for (uint32_t index = 0;
2209 index < preferred->mActiveConns.Length();
2210 ++index) {
2211 if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
2212 conn = preferred->mActiveConns[index];
2213 break;
2214 }
2215 }
2216 }
2217
2218 return conn;
2219 }
2220
2221 //-----------------------------------------------------------------------------
2222
2223 void
2224 nsHttpConnectionMgr::OnMsgShutdown(int32_t, void *param)
2225 {
2226 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2227 LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
2228
2229 mCT.Enumerate(ShutdownPassCB, this);
2230
2231 if (mTimeoutTick) {
2232 mTimeoutTick->Cancel();
2233 mTimeoutTick = nullptr;
2234 mTimeoutTickArmed = false;
2235 }
2236 if (mTimer) {
2237 mTimer->Cancel();
2238 mTimer = nullptr;
2239 }
2240
2241 // signal shutdown complete
2242 nsRefPtr<nsIRunnable> runnable =
2243 new nsConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
2244 0, param);
2245 NS_DispatchToMainThread(runnable);
2246 }
2247
2248 void
2249 nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, void *param)
2250 {
2251 MOZ_ASSERT(NS_IsMainThread());
2252 LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
2253
2254 bool *shutdown = static_cast<bool*>(param);
2255 *shutdown = true;
2256 }
2257
2258 void
2259 nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, void *param)
2260 {
2261 LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
2262
2263 nsHttpTransaction *trans = (nsHttpTransaction *) param;
2264 trans->SetPriority(priority);
2265 nsresult rv = ProcessNewTransaction(trans);
2266 if (NS_FAILED(rv))
2267 trans->Close(rv); // for whatever its worth
2268 NS_RELEASE(trans);
2269 }
2270
2271 void
2272 nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, void *param)
2273 {
2274 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2275 LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
2276
2277 nsHttpTransaction *trans = (nsHttpTransaction *) param;
2278 trans->SetPriority(priority);
2279
2280 nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
2281 nullptr, trans);
2282
2283 if (ent) {
2284 int32_t index = ent->mPendingQ.IndexOf(trans);
2285 if (index >= 0) {
2286 ent->mPendingQ.RemoveElementAt(index);
2287 InsertTransactionSorted(ent->mPendingQ, trans);
2288 }
2289 }
2290
2291 NS_RELEASE(trans);
2292 }
2293
2294 void
2295 nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, void *param)
2296 {
2297 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2298 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
2299
2300 nsresult closeCode = static_cast<nsresult>(reason);
2301 nsHttpTransaction *trans = (nsHttpTransaction *) param;
2302 //
2303 // if the transaction owns a connection and the transaction is not done,
2304 // then ask the connection to close the transaction. otherwise, close the
2305 // transaction directly (removing it from the pending queue first).
2306 //
2307 nsAHttpConnection *conn = trans->Connection();
2308 if (conn && !trans->IsDone()) {
2309 conn->CloseTransaction(trans, closeCode);
2310 } else {
2311 nsConnectionEntry *ent =
2312 LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
2313
2314 if (ent) {
2315 int32_t index = ent->mPendingQ.IndexOf(trans);
2316 if (index >= 0) {
2317 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
2318 " found in pending queue\n", trans));
2319 ent->mPendingQ.RemoveElementAt(index);
2320 nsHttpTransaction *temp = trans;
2321 NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument!
2322 }
2323 }
2324 trans->Close(closeCode);
2325
2326 // Cancel is a pretty strong signal that things might be hanging
2327 // so we want to cancel any null transactions related to this connection
2328 // entry. They are just optimizations, but they aren't hooked up to
2329 // anything that might get canceled from the rest of gecko, so best
2330 // to assume that's what was meant by the cancel we did receive if
2331 // it only applied to something in the queue.
2332 for (uint32_t index = 0;
2333 ent && (index < ent->mActiveConns.Length());
2334 ++index) {
2335 nsHttpConnection *activeConn = ent->mActiveConns[index];
2336 nsAHttpTransaction *liveTransaction = activeConn->Transaction();
2337 if (liveTransaction && liveTransaction->IsNullTransaction()) {
2338 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
2339 "also canceling Null Transaction %p on conn %p\n",
2340 trans, liveTransaction, activeConn));
2341 activeConn->CloseTransaction(liveTransaction, closeCode);
2342 }
2343 }
2344 }
2345 NS_RELEASE(trans);
2346 }
2347
2348 void
2349 nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, void *param)
2350 {
2351 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2352 nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
2353
2354 if (!ci) {
2355 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
2356 // Try and dispatch everything
2357 mCT.Enumerate(ProcessAllTransactionsCB, this);
2358 return;
2359 }
2360
2361 LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
2362 ci->HashKey().get()));
2363
2364 // start by processing the queue identified by the given connection info.
2365 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
2366 if (!(ent && ProcessPendingQForEntry(ent, false))) {
2367 // if we reach here, it means that we couldn't dispatch a transaction
2368 // for the specified connection info. walk the connection table...
2369 mCT.Enumerate(ProcessOneTransactionCB, this);
2370 }
2371
2372 NS_RELEASE(ci);
2373 }
2374
2375 void
2376 nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, void *)
2377 {
2378 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2379 LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
2380
2381 // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
2382 mTimeOfNextWakeUp = UINT64_MAX;
2383
2384 // check canreuse() for all idle connections plus any active connections on
2385 // connection entries that are using spdy.
2386 if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
2387 mCT.Enumerate(PruneDeadConnectionsCB, this);
2388 }
2389
2390 void
2391 nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, void *param)
2392 {
2393 LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
2394 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2395
2396 nsRefPtr<nsHttpConnectionInfo> ci =
2397 dont_AddRef(static_cast<nsHttpConnectionInfo *>(param));
2398
2399 mCT.Enumerate(ClosePersistentConnectionsCB, this);
2400 if (ci)
2401 ResetIPFamilyPreference(ci);
2402 }
2403
2404 void
2405 nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, void *param)
2406 {
2407 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2408 LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
2409
2410 nsHttpConnection *conn = (nsHttpConnection *) param;
2411
2412 //
2413 // 1) remove the connection from the active list
2414 // 2) if keep-alive, add connection to idle list
2415 // 3) post event to process the pending transaction queue
2416 //
2417
2418 nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
2419 conn, nullptr);
2420 nsHttpConnectionInfo *ci = nullptr;
2421
2422 if (!ent) {
2423 // this should never happen
2424 LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection ent == null\n"));
2425 MOZ_ASSERT(false, "no connection entry");
2426 NS_ADDREF(ci = conn->ConnectionInfo());
2427 }
2428 else {
2429 NS_ADDREF(ci = ent->mConnInfo);
2430
2431 // If the connection is in the active list, remove that entry
2432 // and the reference held by the mActiveConns list.
2433 // This is never the final reference on conn as the event context
2434 // is also holding one that is released at the end of this function.
2435
2436 if (ent->mUsingSpdy) {
2437 // Spdy connections aren't reused in the traditional HTTP way in
2438 // the idleconns list, they are actively multplexed as active
2439 // conns. Even when they have 0 transactions on them they are
2440 // considered active connections. So when one is reclaimed it
2441 // is really complete and is meant to be shut down and not
2442 // reused.
2443 conn->DontReuse();
2444 }
2445
2446 if (ent->mActiveConns.RemoveElement(conn)) {
2447 if (conn == ent->mYellowConnection)
2448 ent->OnYellowComplete();
2449 nsHttpConnection *temp = conn;
2450 NS_RELEASE(temp);
2451 DecrementActiveConnCount(conn);
2452 ConditionallyStopTimeoutTick();
2453 }
2454
2455 if (conn->CanReuse()) {
2456 LOG((" adding connection to idle list\n"));
2457 // Keep The idle connection list sorted with the connections that
2458 // have moved the largest data pipelines at the front because these
2459 // connections have the largest cwnds on the server.
2460
2461 // The linear search is ok here because the number of idleconns
2462 // in a single entry is generally limited to a small number (i.e. 6)
2463
2464 uint32_t idx;
2465 for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
2466 nsHttpConnection *idleConn = ent->mIdleConns[idx];
2467 if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
2468 break;
2469 }
2470
2471 NS_ADDREF(conn);
2472 ent->mIdleConns.InsertElementAt(idx, conn);
2473 mNumIdleConns++;
2474 conn->BeginIdleMonitoring();
2475
2476 // If the added connection was first idle connection or has shortest
2477 // time to live among the watched connections, pruning dead
2478 // connections needs to be done when it can't be reused anymore.
2479 uint32_t timeToLive = conn->TimeToLive();
2480 if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
2481 PruneDeadConnectionsAfter(timeToLive);
2482 }
2483 else {
2484 LOG((" connection cannot be reused; closing connection\n"));
2485 conn->Close(NS_ERROR_ABORT);
2486 }
2487 }
2488
2489 OnMsgProcessPendingQ(0, ci); // releases |ci|
2490 NS_RELEASE(conn);
2491 }
2492
2493 void
2494 nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, void *param)
2495 {
2496 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2497 nsCompleteUpgradeData *data = (nsCompleteUpgradeData *) param;
2498 LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
2499 "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
2500 data->mUpgradeListener.get()));
2501
2502 nsCOMPtr<nsISocketTransport> socketTransport;
2503 nsCOMPtr<nsIAsyncInputStream> socketIn;
2504 nsCOMPtr<nsIAsyncOutputStream> socketOut;
2505
2506 nsresult rv;
2507 rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
2508 getter_AddRefs(socketIn),
2509 getter_AddRefs(socketOut));
2510
2511 if (NS_SUCCEEDED(rv))
2512 data->mUpgradeListener->OnTransportAvailable(socketTransport,
2513 socketIn,
2514 socketOut);
2515 delete data;
2516 }
2517
2518 void
2519 nsHttpConnectionMgr::OnMsgUpdateParam(int32_t, void *param)
2520 {
2521 uint16_t name = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16;
2522 uint16_t value = NS_PTR_TO_INT32(param) & 0x0000FFFF;
2523
2524 switch (name) {
2525 case MAX_CONNECTIONS:
2526 mMaxConns = value;
2527 break;
2528 case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
2529 mMaxPersistConnsPerHost = value;
2530 break;
2531 case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
2532 mMaxPersistConnsPerProxy = value;
2533 break;
2534 case MAX_REQUEST_DELAY:
2535 mMaxRequestDelay = value;
2536 break;
2537 case MAX_PIPELINED_REQUESTS:
2538 mMaxPipelinedRequests = value;
2539 break;
2540 case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
2541 mMaxOptimisticPipelinedRequests = value;
2542 break;
2543 default:
2544 NS_NOTREACHED("unexpected parameter name");
2545 }
2546 }
2547
2548 // nsHttpConnectionMgr::nsConnectionEntry
2549 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
2550 {
2551 if (mSpdyPreferred)
2552 gHttpHandler->ConnMgr()->RemoveSpdyPreferredEnt(mCoalescingKey);
2553
2554 NS_RELEASE(mConnInfo);
2555 }
2556
2557 void
2558 nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, void *param)
2559 {
2560 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2561 nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param;
2562
2563 PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
2564 delete fb;
2565 }
2566
2567 // Read Timeout Tick handlers
2568
2569 void
2570 nsHttpConnectionMgr::ActivateTimeoutTick()
2571 {
2572 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2573 LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() "
2574 "this=%p mTimeoutTick=%p\n"));
2575
2576 // The timer tick should be enabled if it is not already pending.
2577 // Upon running the tick will rearm itself if there are active
2578 // connections available.
2579
2580 if (mTimeoutTick && mTimeoutTickArmed) {
2581 // make sure we get one iteration on a quick tick
2582 if (mTimeoutTickNext > 1) {
2583 mTimeoutTickNext = 1;
2584 mTimeoutTick->SetDelay(1000);
2585 }
2586 return;
2587 }
2588
2589 if (!mTimeoutTick) {
2590 mTimeoutTick = do_CreateInstance(NS_TIMER_CONTRACTID);
2591 if (!mTimeoutTick) {
2592 NS_WARNING("failed to create timer for http timeout management");
2593 return;
2594 }
2595 mTimeoutTick->SetTarget(mSocketThreadTarget);
2596 }
2597
2598 MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
2599 mTimeoutTickArmed = true;
2600 mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
2601 }
2602
2603 void
2604 nsHttpConnectionMgr::TimeoutTick()
2605 {
2606 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2607 MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
2608
2609 LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
2610 // The next tick will be between 1 second and 1 hr
2611 // Set it to the max value here, and the TimeoutTickCB()s can
2612 // reduce it to their local needs.
2613 mTimeoutTickNext = 3600; // 1hr
2614 mCT.Enumerate(TimeoutTickCB, this);
2615 if (mTimeoutTick) {
2616 mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
2617 mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
2618 }
2619 }
2620
2621 PLDHashOperator
2622 nsHttpConnectionMgr::TimeoutTickCB(const nsACString &key,
2623 nsAutoPtr<nsConnectionEntry> &ent,
2624 void *closure)
2625 {
2626 nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
2627
2628 LOG(("nsHttpConnectionMgr::TimeoutTickCB() this=%p host=%s "
2629 "idle=%d active=%d half-len=%d pending=%d\n",
2630 self, ent->mConnInfo->Host(), ent->mIdleConns.Length(),
2631 ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
2632 ent->mPendingQ.Length()));
2633
2634 // first call the tick handler for each active connection
2635 PRIntervalTime now = PR_IntervalNow();
2636 for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
2637 uint32_t connNextTimeout = ent->mActiveConns[index]->ReadTimeoutTick(now);
2638 self->mTimeoutTickNext = std::min(self->mTimeoutTickNext, connNextTimeout);
2639 }
2640
2641 // now check for any stalled half open sockets
2642 if (ent->mHalfOpens.Length()) {
2643 TimeStamp now = TimeStamp::Now();
2644 double maxConnectTime = gHttpHandler->ConnectTimeout(); /* in milliseconds */
2645
2646 for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
2647 index--;
2648
2649 nsHalfOpenSocket *half = ent->mHalfOpens[index];
2650 double delta = half->Duration(now);
2651 // If the socket has timed out, close it so the waiting transaction
2652 // will get the proper signal
2653 if (delta > maxConnectTime) {
2654 LOG(("Force timeout of half open to %s after %.2fms.\n",
2655 ent->mConnInfo->HashKey().get(), delta));
2656 if (half->SocketTransport())
2657 half->SocketTransport()->Close(NS_ERROR_ABORT);
2658 if (half->BackupTransport())
2659 half->BackupTransport()->Close(NS_ERROR_ABORT);
2660 }
2661
2662 // If this half open hangs around for 5 seconds after we've closed() it
2663 // then just abandon the socket.
2664 if (delta > maxConnectTime + 5000) {
2665 LOG(("Abandon half open to %s after %.2fms.\n",
2666 ent->mConnInfo->HashKey().get(), delta));
2667 half->Abandon();
2668 }
2669 }
2670 }
2671 if (ent->mHalfOpens.Length()) {
2672 self->mTimeoutTickNext = 1;
2673 }
2674 return PL_DHASH_NEXT;
2675 }
2676
2677 //-----------------------------------------------------------------------------
2678 // nsHttpConnectionMgr::nsConnectionHandle
2679
2680 nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle()
2681 {
2682 if (mConn) {
2683 gHttpHandler->ReclaimConnection(mConn);
2684 NS_RELEASE(mConn);
2685 }
2686 }
2687
2688 NS_IMPL_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle)
2689
2690 nsHttpConnectionMgr::nsConnectionEntry *
2691 nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *ci)
2692 {
2693 nsConnectionEntry *ent = mCT.Get(ci->HashKey());
2694 if (ent)
2695 return ent;
2696
2697 nsHttpConnectionInfo *clone = ci->Clone();
2698 ent = new nsConnectionEntry(clone);
2699 mCT.Put(ci->HashKey(), ent);
2700 return ent;
2701 }
2702
2703 nsresult
2704 nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
2705 nsHttpRequestHead *req,
2706 nsHttpResponseHead *resp,
2707 bool *reset)
2708 {
2709 return mConn->OnHeadersAvailable(trans, req, resp, reset);
2710 }
2711
2712 void
2713 nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
2714 {
2715 mConn->CloseTransaction(trans, reason);
2716 }
2717
2718 nsresult
2719 nsHttpConnectionMgr::
2720 nsConnectionHandle::TakeTransport(nsISocketTransport **aTransport,
2721 nsIAsyncInputStream **aInputStream,
2722 nsIAsyncOutputStream **aOutputStream)
2723 {
2724 return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
2725 }
2726
2727 void
2728 nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param)
2729 {
2730 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2731
2732 nsRefPtr<SpeculativeConnectArgs> args =
2733 dont_AddRef(static_cast<SpeculativeConnectArgs *>(param));
2734
2735 LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
2736 args->mTrans->ConnectionInfo()->HashKey().get()));
2737
2738 nsConnectionEntry *ent =
2739 GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo());
2740
2741 // If spdy has previously made a preferred entry for this host via
2742 // the ip pooling rules. If so, connect to the preferred host instead of
2743 // the one directly passed in here.
2744 nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
2745 if (preferredEntry)
2746 ent = preferredEntry;
2747
2748 uint32_t parallelSpeculativeConnectLimit =
2749 gHttpHandler->ParallelSpeculativeConnectLimit();
2750 bool ignorePossibleSpdyConnections = false;
2751 bool ignoreIdle = false;
2752
2753 if (args->mOverridesOK) {
2754 parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
2755 ignorePossibleSpdyConnections = args->mIgnorePossibleSpdyConnections;
2756 ignoreIdle = args->mIgnoreIdle;
2757 }
2758
2759 if (ent->SupportsPipelining()) {
2760 /* Only speculative connect if we're not pipelining and have no other pending
2761 * unconnected half-opens.. */
2762 if (ent->UnconnectedHalfOpens() == 0 && ent->mIdleConns.Length() == 0
2763 && !RestrictConnections(ent) && !HasPipelines(ent)
2764 && !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
2765 #ifdef WTF_DEBUG
2766 fprintf(stderr, "WTF: Creating speculative connection because we have no pipelines\n");
2767 #endif
2768 CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true);
2769 }
2770 } else if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
2771 ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
2772 !ent->mIdleConns.Length()) &&
2773 !RestrictConnections(ent, ignorePossibleSpdyConnections) &&
2774 !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
2775 #ifdef WTF_DEBUG
2776 fprintf(stderr, "WTF: Creating speculative connection because we can't pipeline\n");
2777 #endif
2778 CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true);
2779 } else {
2780 LOG((" Transport not created due to existing connection count\n"));
2781 }
2782 }
2783
2784 bool
2785 nsHttpConnectionMgr::HasPipelines(nsConnectionEntry *ent)
2786 {
2787 uint32_t activeCount = ent->mActiveConns.Length();
2788
2789 if (!ent->SupportsPipelining()) {
2790 return false;
2791 }
2792
2793 for (uint32_t i = 0; i < activeCount; ++i) {
2794 nsHttpConnection *conn = ent->mActiveConns[i];
2795 if (!conn->SupportsPipelining())
2796 continue;
2797
2798 nsAHttpTransaction *activeTrans = conn->Transaction();
2799
2800 if (activeTrans && !activeTrans->IsDone() &&
2801 !NS_FAILED(activeTrans->Status()))
2802 return true;
2803 }
2804 return false;
2805 }
2806
2807 bool
2808 nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
2809 {
2810 return mConn->IsPersistent();
2811 }
2812
2813 bool
2814 nsHttpConnectionMgr::nsConnectionHandle::IsReused()
2815 {
2816 return mConn->IsReused();
2817 }
2818
2819 void
2820 nsHttpConnectionMgr::nsConnectionHandle::DontReuse()
2821 {
2822 mConn->DontReuse();
2823 }
2824
2825 nsresult
2826 nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
2827 {
2828 return mConn->PushBack(buf, bufLen);
2829 }
2830
2831
2832 //////////////////////// nsHalfOpenSocket
2833
2834
2835 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
2836 nsIOutputStreamCallback,
2837 nsITransportEventSink,
2838 nsIInterfaceRequestor,
2839 nsITimerCallback)
2840
2841 nsHttpConnectionMgr::
2842 nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
2843 nsAHttpTransaction *trans,
2844 uint32_t caps)
2845 : mEnt(ent),
2846 mTransaction(trans),
2847 mCaps(caps),
2848 mSpeculative(false),
2849 mHasConnected(false)
2850 {
2851 MOZ_ASSERT(ent && trans, "constructor with null arguments");
2852 LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s]\n",
2853 this, trans, ent->mConnInfo->Host()));
2854 }
2855
2856 nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
2857 {
2858 MOZ_ASSERT(!mStreamOut);
2859 MOZ_ASSERT(!mBackupStreamOut);
2860 MOZ_ASSERT(!mSynTimer);
2861 LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
2862
2863 if (mEnt)
2864 mEnt->RemoveHalfOpen(this);
2865 }
2866
2867 nsresult
2868 nsHttpConnectionMgr::
2869 nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
2870 nsIAsyncInputStream **instream,
2871 nsIAsyncOutputStream **outstream,
2872 bool isBackup)
2873 {
2874 nsresult rv;
2875
2876 const char* types[1];
2877 types[0] = (mEnt->mConnInfo->UsingSSL()) ?
2878 "ssl" : gHttpHandler->DefaultSocketType();
2879 uint32_t typeCount = (types[0] != nullptr);
2880
2881 nsCOMPtr<nsISocketTransport> socketTransport;
2882 nsCOMPtr<nsISocketTransportService> sts;
2883
2884 sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
2885 NS_ENSURE_SUCCESS(rv, rv);
2886
2887 rv = sts->CreateTransport(types, typeCount,
2888 nsDependentCString(mEnt->mConnInfo->Host()),
2889 mEnt->mConnInfo->Port(),
2890 mEnt->mConnInfo->ProxyInfo(),
2891 getter_AddRefs(socketTransport));
2892 NS_ENSURE_SUCCESS(rv, rv);
2893
2894 uint32_t tmpFlags = 0;
2895 if (mCaps & NS_HTTP_REFRESH_DNS)
2896 tmpFlags = nsISocketTransport::BYPASS_CACHE;
2897
2898 if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
2899 tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
2900
2901 if (mEnt->mConnInfo->GetPrivate())
2902 tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
2903
2904 // For backup connections, we disable IPv6. That's because some users have
2905 // broken IPv6 connectivity (leading to very long timeouts), and disabling
2906 // IPv6 on the backup connection gives them a much better user experience
2907 // with dual-stack hosts, though they still pay the 250ms delay for each new
2908 // connection. This strategy is also known as "happy eyeballs".
2909 if (mEnt->mPreferIPv6) {
2910 tmpFlags |= nsISocketTransport::DISABLE_IPV4;
2911 }
2912 else if (mEnt->mPreferIPv4 ||
2913 (isBackup && gHttpHandler->FastFallbackToIPv4())) {
2914 tmpFlags |= nsISocketTransport::DISABLE_IPV6;
2915 }
2916
2917 if (IsSpeculative()) {
2918 tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
2919 }
2920
2921 socketTransport->SetConnectionFlags(tmpFlags);
2922
2923 socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
2924
2925 rv = socketTransport->SetEventSink(this, nullptr);
2926 NS_ENSURE_SUCCESS(rv, rv);
2927
2928 rv = socketTransport->SetSecurityCallbacks(this);
2929 NS_ENSURE_SUCCESS(rv, rv);
2930
2931 nsCOMPtr<nsIOutputStream> sout;
2932 rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
2933 0, 0,
2934 getter_AddRefs(sout));
2935 NS_ENSURE_SUCCESS(rv, rv);
2936
2937 nsCOMPtr<nsIInputStream> sin;
2938 rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
2939 0, 0,
2940 getter_AddRefs(sin));
2941 NS_ENSURE_SUCCESS(rv, rv);
2942
2943 socketTransport.forget(transport);
2944 CallQueryInterface(sin, instream);
2945 CallQueryInterface(sout, outstream);
2946
2947 rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
2948 if (NS_SUCCEEDED(rv))
2949 gHttpHandler->ConnMgr()->StartedConnect();
2950
2951 return rv;
2952 }
2953
2954 nsresult
2955 nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
2956 {
2957 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2958
2959 nsresult rv;
2960
2961 mPrimarySynStarted = TimeStamp::Now();
2962 rv = SetupStreams(getter_AddRefs(mSocketTransport),
2963 getter_AddRefs(mStreamIn),
2964 getter_AddRefs(mStreamOut),
2965 false);
2966 LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
2967 this, mEnt->mConnInfo->Host(), rv));
2968 if (NS_FAILED(rv)) {
2969 if (mStreamOut)
2970 mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
2971 mStreamOut = nullptr;
2972 mStreamIn = nullptr;
2973 mSocketTransport = nullptr;
2974 }
2975 return rv;
2976 }
2977
2978 nsresult
2979 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
2980 {
2981 mBackupSynStarted = TimeStamp::Now();
2982 nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
2983 getter_AddRefs(mBackupStreamIn),
2984 getter_AddRefs(mBackupStreamOut),
2985 true);
2986 LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
2987 this, mEnt->mConnInfo->Host(), rv));
2988 if (NS_FAILED(rv)) {
2989 if (mBackupStreamOut)
2990 mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
2991 mBackupStreamOut = nullptr;
2992 mBackupStreamIn = nullptr;
2993 mBackupTransport = nullptr;
2994 }
2995 return rv;
2996 }
2997
2998 void
2999 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
3000 {
3001 uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
3002 MOZ_ASSERT(!mSynTimer, "timer already initd");
3003
3004 if (timeout && !mTransaction->IsDone()) {
3005 // Setup the timer that will establish a backup socket
3006 // if we do not get a writable event on the main one.
3007 // We do this because a lost SYN takes a very long time
3008 // to repair at the TCP level.
3009 //
3010 // Failure to setup the timer is something we can live with,
3011 // so don't return an error in that case.
3012 nsresult rv;
3013 mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
3014 if (NS_SUCCEEDED(rv)) {
3015 mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
3016 LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
3017 }
3018 }
3019 else if (timeout) {
3020 LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p],"
3021 " transaction already done!", this));
3022 }
3023 }
3024
3025 void
3026 nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
3027 {
3028 // If the syntimer is still armed, we can cancel it because no backup
3029 // socket should be formed at this point
3030 if (!mSynTimer)
3031 return;
3032
3033 LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
3034 mSynTimer->Cancel();
3035 mSynTimer = nullptr;
3036 }
3037
3038 void
3039 nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
3040 {
3041 LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s]",
3042 this, mEnt->mConnInfo->Host()));
3043
3044 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3045
3046 nsRefPtr<nsHalfOpenSocket> deleteProtector(this);
3047
3048 // Tell socket (and backup socket) to forget the half open socket.
3049 if (mSocketTransport) {
3050 mSocketTransport->SetEventSink(nullptr, nullptr);
3051 mSocketTransport->SetSecurityCallbacks(nullptr);
3052 mSocketTransport = nullptr;
3053 }
3054 if (mBackupTransport) {
3055 mBackupTransport->SetEventSink(nullptr, nullptr);
3056 mBackupTransport->SetSecurityCallbacks(nullptr);
3057 mBackupTransport = nullptr;
3058 }
3059
3060 // Tell output stream (and backup) to forget the half open socket.
3061 if (mStreamOut) {
3062 gHttpHandler->ConnMgr()->RecvdConnect();
3063 mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
3064 mStreamOut = nullptr;
3065 }
3066 if (mBackupStreamOut) {
3067 gHttpHandler->ConnMgr()->RecvdConnect();
3068 mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
3069 mBackupStreamOut = nullptr;
3070 }
3071
3072 // Lose references to input stream (and backup).
3073 mStreamIn = mBackupStreamIn = nullptr;
3074
3075 // Stop the timer - we don't want any new backups.
3076 CancelBackupTimer();
3077
3078 // Remove the half open from the connection entry.
3079 if (mEnt)
3080 mEnt->RemoveHalfOpen(this);
3081 mEnt = nullptr;
3082 }
3083
3084 double
3085 nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
3086 {
3087 if (mPrimarySynStarted.IsNull())
3088 return 0;
3089
3090 return (epoch - mPrimarySynStarted).ToMilliseconds();
3091 }
3092
3093
3094 NS_IMETHODIMP // method for nsITimerCallback
3095 nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
3096 {
3097 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3098 MOZ_ASSERT(timer == mSynTimer, "wrong timer");
3099
3100 SetupBackupStreams();
3101
3102 mSynTimer = nullptr;
3103 return NS_OK;
3104 }
3105
3106 // method for nsIAsyncOutputStreamCallback
3107 NS_IMETHODIMP
3108 nsHttpConnectionMgr::
3109 nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
3110 {
3111 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3112 MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
3113 "stream mismatch");
3114 LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
3115 this, mEnt->mConnInfo->Host(),
3116 out == mStreamOut ? "primary" : "backup"));
3117 int32_t index;
3118 nsresult rv;
3119
3120 gHttpHandler->ConnMgr()->RecvdConnect();
3121
3122 CancelBackupTimer();
3123
3124 // assign the new socket to the http connection
3125 nsRefPtr<nsHttpConnection> conn = new nsHttpConnection();
3126 LOG(("nsHalfOpenSocket::OnOutputStreamReady "
3127 "Created new nshttpconnection %p\n", conn.get()));
3128
3129 // Some capabilities are needed before a transaciton actually gets
3130 // scheduled (e.g. how to negotiate false start)
3131 conn->SetTransactionCaps(mTransaction->Caps());
3132
3133 NetAddr peeraddr;
3134 nsCOMPtr<nsIInterfaceRequestor> callbacks;
3135 mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
3136 if (out == mStreamOut) {
3137 TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
3138 rv = conn->Init(mEnt->mConnInfo,
3139 gHttpHandler->ConnMgr()->mMaxRequestDelay,
3140 mSocketTransport, mStreamIn, mStreamOut,
3141 callbacks,
3142 PR_MillisecondsToInterval(
3143 static_cast<uint32_t>(rtt.ToMilliseconds())));
3144
3145 if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
3146 mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
3147
3148 // The nsHttpConnection object now owns these streams and sockets
3149 mStreamOut = nullptr;
3150 mStreamIn = nullptr;
3151 mSocketTransport = nullptr;
3152 }
3153 else {
3154 TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
3155 rv = conn->Init(mEnt->mConnInfo,
3156 gHttpHandler->ConnMgr()->mMaxRequestDelay,
3157 mBackupTransport, mBackupStreamIn, mBackupStreamOut,
3158 callbacks,
3159 PR_MillisecondsToInterval(
3160 static_cast<uint32_t>(rtt.ToMilliseconds())));
3161
3162 if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr)))
3163 mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
3164
3165 // The nsHttpConnection object now owns these streams and sockets
3166 mBackupStreamOut = nullptr;
3167 mBackupStreamIn = nullptr;
3168 mBackupTransport = nullptr;
3169 }
3170
3171 if (NS_FAILED(rv)) {
3172 LOG(("nsHalfOpenSocket::OnOutputStreamReady "
3173 "conn->init (%p) failed %x\n", conn.get(), rv));
3174 return rv;
3175 }
3176
3177 // This half-open socket has created a connection. This flag excludes it
3178 // from counter of actual connections used for checking limits.
3179 mHasConnected = true;
3180
3181 // if this is still in the pending list, remove it and dispatch it
3182 index = mEnt->mPendingQ.IndexOf(mTransaction);
3183 if (index != -1) {
3184 MOZ_ASSERT(!mSpeculative,
3185 "Speculative Half Open found mTranscation");
3186 nsRefPtr<nsHttpTransaction> temp = dont_AddRef(mEnt->mPendingQ[index]);
3187 mEnt->mPendingQ.RemoveElementAt(index);
3188 gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
3189 #ifdef WTF_DEBUG
3190 fprintf(stderr, "WTF: Speculative half-opened connection is now ready for %s (pipelines %d)\n",
3191 mEnt->mConnInfo->Host(), mEnt->SupportsPipelining());
3192 #endif
3193 rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
3194 }
3195 else {
3196 // this transaction was dispatched off the pending q before all the
3197 // sockets established themselves.
3198
3199 // After about 1 second allow for the possibility of restarting a
3200 // transaction due to server close. Keep at sub 1 second as that is the
3201 // minimum granularity we can expect a server to be timing out with.
3202 conn->SetIsReusedAfter(950);
3203
3204 // if we are using ssl and no other transactions are waiting right now,
3205 // then form a null transaction to drive the SSL handshake to
3206 // completion. Afterwards the connection will be 100% ready for the next
3207 // transaction to use it. Make an exception for SSL over HTTP proxy as the
3208 // NullHttpTransaction does not know how to drive CONNECT.
3209 if (mEnt->mConnInfo->UsingSSL() && !mEnt->mPendingQ.Length() &&
3210 !mEnt->mConnInfo->UsingHttpProxy()) {
3211 LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will "
3212 "be used to finish SSL handshake on conn %p\n", conn.get()));
3213 nsRefPtr<NullHttpTransaction> trans =
3214 new NullHttpTransaction(mEnt->mConnInfo,
3215 callbacks,
3216 mCaps & ~NS_HTTP_ALLOW_PIPELINING);
3217
3218 gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
3219 conn->Classify(nsAHttpTransaction::CLASS_SOLO);
3220 rv = gHttpHandler->ConnMgr()->
3221 DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
3222 }
3223 else {
3224 // otherwise just put this in the persistent connection pool
3225 LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
3226 "returning conn %p to pool\n", conn.get()));
3227 nsRefPtr<nsHttpConnection> copy(conn);
3228 // forget() to effectively addref because onmsg*() will drop a ref
3229 gHttpHandler->ConnMgr()->OnMsgReclaimConnection(
3230 0, conn.forget().take());
3231 }
3232 }
3233
3234 return rv;
3235 }
3236
3237 // method for nsITransportEventSink
3238 NS_IMETHODIMP
3239 nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
3240 nsresult status,
3241 uint64_t progress,
3242 uint64_t progressMax)
3243 {
3244 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3245
3246 if (mTransaction)
3247 mTransaction->OnTransportStatus(trans, status, progress);
3248
3249 if (trans != mSocketTransport)
3250 return NS_OK;
3251
3252 // if we are doing spdy coalescing and haven't recorded the ip address
3253 // for this entry before then make the hash key if our dns lookup
3254 // just completed. We can't do coalescing if using a proxy because the
3255 // ip addresses are not available to the client.
3256
3257 if (status == NS_NET_STATUS_CONNECTED_TO &&
3258 gHttpHandler->IsSpdyEnabled() &&
3259 gHttpHandler->CoalesceSpdy() &&
3260 mEnt && mEnt->mConnInfo && mEnt->mConnInfo->UsingSSL() &&
3261 !mEnt->mConnInfo->UsingProxy() &&
3262 mEnt->mCoalescingKey.IsEmpty()) {
3263
3264 NetAddr addr;
3265 nsresult rv = mSocketTransport->GetPeerAddr(&addr);
3266 if (NS_SUCCEEDED(rv)) {
3267 mEnt->mCoalescingKey.SetCapacity(kIPv6CStrBufSize + 26);
3268 NetAddrToString(&addr, mEnt->mCoalescingKey.BeginWriting(), kIPv6CStrBufSize);
3269 mEnt->mCoalescingKey.SetLength(
3270 strlen(mEnt->mCoalescingKey.BeginReading()));
3271
3272 if (mEnt->mConnInfo->GetAnonymous())
3273 mEnt->mCoalescingKey.AppendLiteral("~A:");
3274 else
3275 mEnt->mCoalescingKey.AppendLiteral("~.:");
3276 mEnt->mCoalescingKey.AppendInt(mEnt->mConnInfo->Port());
3277
3278 LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
3279 "STATUS_CONNECTED_TO Established New Coalescing Key for host "
3280 "%s [%s]", mEnt->mConnInfo->Host(),
3281 mEnt->mCoalescingKey.get()));
3282
3283 gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
3284 }
3285 }
3286
3287 switch (status) {
3288 case NS_NET_STATUS_CONNECTING_TO:
3289 // Passed DNS resolution, now trying to connect, start the backup timer
3290 // only prevent creating another backup transport.
3291 // We also check for mEnt presence to not instantiate the timer after
3292 // this half open socket has already been abandoned. It may happen
3293 // when we get this notification right between main-thread calls to
3294 // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
3295 // where the first abandones all half open socket instances and only
3296 // after that the second stops the socket thread.
3297 if (mEnt && !mBackupTransport && !mSynTimer)
3298 SetupBackupTimer();
3299 break;
3300
3301 case NS_NET_STATUS_CONNECTED_TO:
3302 // TCP connection's up, now transfer or SSL negotiantion starts,
3303 // no need for backup socket
3304 CancelBackupTimer();
3305 break;
3306
3307 default:
3308 break;
3309 }
3310
3311 return NS_OK;
3312 }
3313
3314 // method for nsIInterfaceRequestor
3315 NS_IMETHODIMP
3316 nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
3317 void **result)
3318 {
3319 if (mTransaction) {
3320 nsCOMPtr<nsIInterfaceRequestor> callbacks;
3321 mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
3322 if (callbacks)
3323 return callbacks->GetInterface(iid, result);
3324 }
3325 return NS_ERROR_NO_INTERFACE;
3326 }
3327
3328
3329 nsHttpConnection *
3330 nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection()
3331 {
3332 // return our connection object to the caller and clear it internally
3333 // do not drop our reference - the caller now owns it.
3334
3335 MOZ_ASSERT(mConn);
3336 nsHttpConnection *conn = mConn;
3337 mConn = nullptr;
3338 return conn;
3339 }
3340
3341 uint32_t
3342 nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason)
3343 {
3344 // no pipeline to cancel
3345 return 0;
3346 }
3347
3348 nsAHttpTransaction::Classifier
3349 nsHttpConnectionMgr::nsConnectionHandle::Classification()
3350 {
3351 if (mConn)
3352 return mConn->Classification();
3353
3354 LOG(("nsConnectionHandle::Classification this=%p "
3355 "has null mConn using CLASS_SOLO default", this));
3356 return nsAHttpTransaction::CLASS_SOLO;
3357 }
3358
3359 // nsConnectionEntry
3360
3361 nsHttpConnectionMgr::
3362 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
3363 : mConnInfo(ci)
3364 , mPipelineState(PS_YELLOW)
3365 , mYellowGoodEvents(0)
3366 , mYellowBadEvents(0)
3367 , mYellowConnection(nullptr)
3368 , mGreenDepth(kPipelineOpen)
3369 , mPipeliningPenalty(0)
3370 , mSpdyCWND(0)
3371 , mUsingSpdy(false)
3372 , mTestedSpdy(false)
3373 , mSpdyPreferred(false)
3374 , mPreferIPv4(false)
3375 , mPreferIPv6(false)
3376 {
3377 NS_ADDREF(mConnInfo);
3378
3379 // Randomize the pipeline depth (3..12)
3380 mGreenDepth = gHttpHandler->GetMaxOptimisticPipelinedRequests()
3381 + rand() % (gHttpHandler->GetMaxPipelinedRequests()
3382 - gHttpHandler->GetMaxOptimisticPipelinedRequests());
3383
3384 if (gHttpHandler->GetPipelineAggressive()) {
3385 mPipelineState = PS_GREEN;
3386 }
3387
3388 mInitialGreenDepth = mGreenDepth;
3389 memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
3390 }
3391
3392 bool
3393 nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
3394 {
3395 return mPipelineState != nsHttpConnectionMgr::PS_RED;
3396 }
3397
3398 nsHttpConnectionMgr::PipeliningState
3399 nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
3400 {
3401 return mPipelineState;
3402 }
3403
3404 void
3405 nsHttpConnectionMgr::
3406 nsConnectionEntry::OnPipelineFeedbackInfo(
3407 nsHttpConnectionMgr::PipelineFeedbackInfoType info,
3408 nsHttpConnection *conn,
3409 uint32_t data)
3410 {
3411 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3412
3413 if (mPipelineState == PS_YELLOW) {
3414 if (info & kPipelineInfoTypeBad)
3415 mYellowBadEvents++;
3416 else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
3417 mYellowGoodEvents++;
3418 }
3419
3420 if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
3421 int32_t depth = data;
3422 LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
3423 depth, mConnInfo->Host()));
3424
3425 // Don't set this. We want to keep our initial random value..
3426 //if (depth >= 3)
3427 // mGreenDepth = kPipelineUnlimited;
3428 }
3429
3430 nsAHttpTransaction::Classifier classification;
3431 if (conn)
3432 classification = conn->Classification();
3433 else if (info == BadInsufficientFraming ||
3434 info == BadUnexpectedLarge)
3435 classification = (nsAHttpTransaction::Classifier) data;
3436 else
3437 classification = nsAHttpTransaction::CLASS_SOLO;
3438
3439 if (gHttpHandler->GetPipelineAggressive() &&
3440 info & kPipelineInfoTypeBad &&
3441 info != BadExplicitClose &&
3442 info != RedVersionTooLow &&
3443 info != RedBannedServer &&
3444 info != RedCorruptedContent &&
3445 info != BadInsufficientFraming) {
3446 LOG(("minor negative feedback ignored "
3447 "because of pipeline aggressive mode"));
3448 }
3449 else if (info & kPipelineInfoTypeBad) {
3450 if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
3451 LOG(("transition to red from %d. Host = %s.\n",
3452 mPipelineState, mConnInfo->Host()));
3453 mPipelineState = PS_RED;
3454 mPipeliningPenalty = 0;
3455 #ifdef WTF_TEST
3456 fprintf(stderr, "WTF-bad: Red pipeline status disabled host %s\n",
3457 mConnInfo->Host());
3458 #endif
3459
3460 }
3461
3462 if (mLastCreditTime.IsNull())
3463 mLastCreditTime = TimeStamp::Now();
3464
3465 // Red* events impact the host globally via mPipeliningPenalty, while
3466 // Bad* events impact the per class penalty.
3467
3468 // The individual penalties should be < 16bit-signed-maxint - 25000
3469 // (approx 7500). Penalties are paid-off either when something promising
3470 // happens (a successful transaction, or promising headers) or when
3471 // time goes by at a rate of 1 penalty point every 16 seconds.
3472
3473 switch (info) {
3474 case RedVersionTooLow:
3475 mPipeliningPenalty += 1000;
3476 break;
3477 case RedBannedServer:
3478 mPipeliningPenalty += 7000;
3479 break;
3480 case RedCorruptedContent:
3481 mPipeliningPenalty += 7000;
3482 break;
3483 case RedCanceledPipeline:
3484 mPipeliningPenalty += 60;
3485 break;
3486 case BadExplicitClose:
3487 mPipeliningClassPenalty[classification] += 250;
3488 break;
3489 case BadSlowReadMinor:
3490 mPipeliningClassPenalty[classification] += 5;
3491 break;
3492 case BadSlowReadMajor:
3493 mPipeliningClassPenalty[classification] += 25;
3494 break;
3495 case BadInsufficientFraming:
3496 mPipeliningClassPenalty[classification] += 7000;
3497 break;
3498 case BadUnexpectedLarge:
3499 mPipeliningClassPenalty[classification] += 120;
3500 break;
3501
3502 default:
3503 MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
3504 }
3505
3506 const int16_t kPenalty = 25000;
3507 mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
3508 mPipeliningClassPenalty[classification] =
3509 std::min(mPipeliningClassPenalty[classification], kPenalty);
3510
3511 LOG(("Assessing red penalty to %s class %d for event %d. "
3512 "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(),
3513 classification, info, mPipeliningPenalty, classification,
3514 mPipeliningClassPenalty[classification]));
3515 }
3516 else {
3517 // hand out credits for neutral and good events such as
3518 // "headers look ok" events
3519
3520 mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
3521 mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
3522 }
3523
3524 if (mPipelineState == PS_RED && !mPipeliningPenalty)
3525 {
3526 LOG(("transition %s to yellow\n", mConnInfo->Host()));
3527 mPipelineState = PS_YELLOW;
3528 mYellowConnection = nullptr;
3529 }
3530 }
3531
3532 void
3533 nsHttpConnectionMgr::
3534 nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
3535 {
3536 MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
3537 "yellow connection already set or state is not yellow");
3538 mYellowConnection = conn;
3539 mYellowGoodEvents = mYellowBadEvents = 0;
3540 }
3541
3542 void
3543 nsHttpConnectionMgr::
3544 nsConnectionEntry::OnYellowComplete()
3545 {
3546 if (mPipelineState == PS_YELLOW) {
3547 if (mYellowGoodEvents && !mYellowBadEvents) {
3548 LOG(("transition %s to green\n", mConnInfo->Host()));
3549 mPipelineState = PS_GREEN;
3550 mGreenDepth = mInitialGreenDepth;
3551 }
3552 else {
3553 // The purpose of the yellow state is to witness at least
3554 // one successful pipelined transaction without seeing any
3555 // kind of negative feedback before opening the flood gates.
3556 // If we haven't confirmed that, then transfer back to red.
3557 LOG(("transition %s to red from yellow return\n",
3558 mConnInfo->Host()));
3559 mPipelineState = PS_RED;
3560 }
3561 }
3562
3563 mYellowConnection = nullptr;
3564 }
3565
3566 void
3567 nsHttpConnectionMgr::
3568 nsConnectionEntry::CreditPenalty()
3569 {
3570 if (mLastCreditTime.IsNull())
3571 return;
3572
3573 // Decrease penalty values by 1 for every 16 seconds
3574 // (i.e 3.7 per minute, or 1000 every 4h20m)
3575
3576 TimeStamp now = TimeStamp::Now();
3577 TimeDuration elapsedTime = now - mLastCreditTime;
3578 uint32_t creditsEarned =
3579 static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;
3580
3581 bool failed = false;
3582 if (creditsEarned > 0) {
3583 mPipeliningPenalty =
3584 std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
3585 if (mPipeliningPenalty > 0)
3586 failed = true;
3587
3588 for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
3589 mPipeliningClassPenalty[i] =
3590 std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
3591 failed = failed || (mPipeliningClassPenalty[i] > 0);
3592 }
3593
3594 // update last credit mark to reflect elapsed time
3595 mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
3596 }
3597 else {
3598 failed = true; /* just assume this */
3599 }
3600
3601 // If we are no longer red then clear the credit counter - you only
3602 // get credits for time spent in the red state
3603 if (!failed)
3604 mLastCreditTime = TimeStamp(); /* reset to null timestamp */
3605
3606 if (mPipelineState == PS_RED && !mPipeliningPenalty)
3607 {
3608 LOG(("transition %s to yellow based on time credit\n",
3609 mConnInfo->Host()));
3610 mPipelineState = PS_YELLOW;
3611 mYellowConnection = nullptr;
3612 }
3613 }
3614
3615 uint32_t
3616 nsHttpConnectionMgr::
3617 nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
3618 {
3619 // Still subject to configuration limit no matter return value
3620
3621 if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
3622 return 0;
3623
3624 if (mPipelineState == PS_YELLOW)
3625 return kPipelineRestricted;
3626
3627 return mGreenDepth;
3628 }
3629
3630 PLDHashOperator
3631 nsHttpConnectionMgr::ReadConnectionEntry(const nsACString &key,
3632 nsAutoPtr<nsConnectionEntry> &ent,
3633 void *aArg)
3634 {
3635 if (ent->mConnInfo->GetPrivate())
3636 return PL_DHASH_NEXT;
3637
3638 nsTArray<HttpRetParams> *args = static_cast<nsTArray<HttpRetParams> *> (aArg);
3639 HttpRetParams data;
3640 data.host = ent->mConnInfo->Host();
3641 data.port = ent->mConnInfo->Port();
3642 for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
3643 HttpConnInfo info;
3644 info.ttl = ent->mActiveConns[i]->TimeToLive();
3645 info.rtt = ent->mActiveConns[i]->Rtt();
3646 if (ent->mActiveConns[i]->UsingSpdy())
3647 info.SetHTTP2ProtocolVersion(ent->mActiveConns[i]->GetSpdyVersion());
3648 else
3649 info.SetHTTP1ProtocolVersion(ent->mActiveConns[i]->GetLastHttpResponseVersion());
3650
3651 data.active.AppendElement(info);
3652 }
3653 for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
3654 HttpConnInfo info;
3655 info.ttl = ent->mIdleConns[i]->TimeToLive();
3656 info.rtt = ent->mIdleConns[i]->Rtt();
3657 info.SetHTTP1ProtocolVersion(ent->mIdleConns[i]->GetLastHttpResponseVersion());
3658 data.idle.AppendElement(info);
3659 }
3660 for(uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
3661 HalfOpenSockets hSocket;
3662 hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
3663 data.halfOpens.AppendElement(hSocket);
3664 }
3665 data.spdy = ent->mUsingSpdy;
3666 data.ssl = ent->mConnInfo->UsingSSL();
3667 args->AppendElement(data);
3668 return PL_DHASH_NEXT;
3669 }
3670
3671 bool
3672 nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
3673 {
3674 mCT.Enumerate(ReadConnectionEntry, aArg);
3675 return true;
3676 }
3677
3678 void
3679 nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
3680 {
3681 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3682 nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr);
3683 if (ent)
3684 ent->ResetIPFamilyPreference();
3685 }
3686
3687 uint32_t
3688 nsHttpConnectionMgr::
3689 nsConnectionEntry::UnconnectedHalfOpens()
3690 {
3691 uint32_t unconnectedHalfOpens = 0;
3692 for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
3693 if (!mHalfOpens[i]->HasConnected())
3694 ++unconnectedHalfOpens;
3695 }
3696 return unconnectedHalfOpens;
3697 }
3698
3699 void
3700 nsHttpConnectionMgr::
3701 nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
3702 {
3703 // A failure to create the transport object at all
3704 // will result in it not being present in the halfopen table
3705 // so ignore failures of RemoveElement()
3706 mHalfOpens.RemoveElement(halfOpen);
3707 gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
3708
3709 if (!UnconnectedHalfOpens())
3710 // perhaps this reverted RestrictConnections()
3711 // use the PostEvent version of processpendingq to avoid
3712 // altering the pending q vector from an arbitrary stack
3713 gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
3714 }
3715
3716 void
3717 nsHttpConnectionMgr::
3718 nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
3719 {
3720 if (family == PR_AF_INET && !mPreferIPv6)
3721 mPreferIPv4 = true;
3722
3723 if (family == PR_AF_INET6 && !mPreferIPv4)
3724 mPreferIPv6 = true;
3725 }
3726
3727 void
3728 nsHttpConnectionMgr::
3729 nsConnectionEntry::ResetIPFamilyPreference()
3730 {
3731 mPreferIPv4 = false;
3732 mPreferIPv6 = false;
3733 }
3734
3735 } // namespace mozilla::net
3736 } // namespace mozilla

mercurial