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