|
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 |