/* vim:set ts=4 sw=2 sts=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first #include"HttpLog.h"
// Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED()
// release our reference to the STS to prevent further events // from being posted. this is how we indicate that we are // shutting down.
mIsShuttingDown = true;
mSocketThreadTarget = nullptr;
if (NS_FAILED(rv)) {
NS_WARNING("unable to post SHUTDOWN message"); return rv;
}
}
// wait for shutdown event to complete
SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns,
[&, shutdownWrapper]() { return shutdownWrapper->mBool; });
// failure to create a timer is not a fatal error, but idle connections // will not be cleaned up until we try to use them. if (mTimer) {
mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
} else {
NS_WARNING("failed to create: timer for pruning the dead connections!");
}
}
void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { // Leave the timer in place if there are connections that potentially // need management if (mNumIdleConns ||
(mNumActiveConns && StaticPrefs::network_http_http2_enabled())) { return;
}
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
mTimeOfNextWakeUp = UINT64_MAX; if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
int32_t priority) {
LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority)); // Make sure a transaction is not in a pending queue.
CheckTransInPendingQueue(trans->AsHttpTransaction()); return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
trans->AsHttpTransaction());
}
class NewTransactionData : public ARefBase { public:
NewTransactionData(nsHttpTransaction* trans, int32_t priority,
nsHttpTransaction* transWithStickyConn)
: mTrans(trans),
mPriority(priority),
mTransWithStickyConn(transWithStickyConn) {}
nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
HttpTransactionShell* trans, int32_t priority,
HttpTransactionShell* transWithStickyConn) {
LOG(
("nsHttpConnectionMgr::AddTransactionWithStickyConn " "[trans=%p %d transWithStickyConn=%p]\n",
trans, priority, transWithStickyConn)); // Make sure a transaction is not in a pending queue.
CheckTransInPendingQueue(trans->AsHttpTransaction());
RefPtr<NewTransactionData> data = new NewTransactionData(trans->AsHttpTransaction(), priority,
transWithStickyConn->AsHttpTransaction()); return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
data);
}
// // Called after a timeout. Check for active connections that have had no // traffic since they were "marked" and nuke them.
nsresult nsHttpConnectionMgr::PruneNoTraffic() {
LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
mPruningNoTraffic = true; return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
}
// Hosts that are Local IP Literals should not be speculatively // connected - Bug 853423. if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
LOG(
("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 " "address [%s]",
ci->Origin())); return NS_OK;
}
RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
// Wrap up the callbacks and the target to ensure they're released on the // target thread properly.
nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
NS_NewInterfaceRequestorAggregation(callbacks, nullptr,
getter_AddRefs(wrappedCallbacks));
// A structure used to marshall 6 pointers across the various necessary // threads to complete an HTTP upgrade. class nsCompleteUpgradeData : public ARefBase { public:
nsCompleteUpgradeData(nsHttpTransaction* aTrans,
nsIHttpUpgradeListener* aListener, bool aJsWrapped)
: mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {}
nsresult nsHttpConnectionMgr::CompleteUpgrade(
HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) { // test if aUpgradeListener is a wrapped JsObject
nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener);
bool wrapped = !!wrapper;
RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData(
aTrans->AsHttpTransaction(), aUpgradeListener, wrapped); return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
}
nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket(
EventTokenBucket* aBucket) { // Call From main thread when a new EventTokenBucket has been made in order // to post the new value to the socket thread. return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0,
aBucket);
}
nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key); if (!listOfWeakConns) { return nullptr;
}
uint32_t listLen = listOfWeakConns->Length(); for (uint32_t j = 0; j < listLen;) {
RefPtr<HttpConnectionBase> potentialMatch =
do_QueryReferent(listOfWeakConns->ElementAt(j)); if (!potentialMatch) { // This is a connection that needs to be removed from the list
LOG(
("FindCoalescableConnectionByHashKey() found old conn %p that has " "null weak ptr - removing\n",
listOfWeakConns->ElementAt(j).get())); if (j != listLen - 1) {
listOfWeakConns->Elements()[j] =
listOfWeakConns->Elements()[listLen - 1];
}
listOfWeakConns->RemoveLastElement();
MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
listLen--; continue; // without adjusting iterator
}
if (aNoHttp3 && potentialMatch->UsingHttp3()) {
j++; continue;
} if (aNoHttp2 && potentialMatch->UsingSpdy()) {
j++; continue;
} bool couldJoin; if (justKidding) {
couldJoin =
potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
} else {
couldJoin =
potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
} if (couldJoin) {
LOG(
("FindCoalescableConnectionByHashKey() found match conn=%p key=%s " "newCI=%s matchedCI=%s join ok\n",
potentialMatch.get(), key.get(), ci->HashKey().get(),
potentialMatch->ConnectionInfo()->HashKey().get())); return potentialMatch.get();
}
LOG(
("FindCoalescableConnectionByHashKey() found match conn=%p key=%s " "newCI=%s matchedCI=%s join failed\n",
potentialMatch.get(), key.get(), ci->HashKey().get(),
potentialMatch->ConnectionInfo()->HashKey().get()));
++j; // bypassed by continue when weakptr fails
}
if (!listLen) { // shrunk to 0 while iterating
LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
mCoalescingHash.Remove(key);
} return nullptr;
}
if (ci->GetWebTransport()) {
LOG(("Don't coalesce a WebTransport conn ")); return nullptr;
} // First try and look it up by origin frame
nsCString newKey;
BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
ent, newKey, justKidding, aNoHttp2, aNoHttp3); if (conn) {
LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
ci->HashKey().get(), conn, newKey.get())); return conn;
}
// now check for DNS based keys // deleted conns (null weak pointers) are removed from list
uint32_t keyLen = ent->mCoalescingKeys.Length(); for (uint32_t i = 0; i < keyLen; ++i) {
conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i],
justKidding, aNoHttp2, aNoHttp3);
auto usableEntry = [&](HttpConnectionBase* conn) { // This is allowed by the spec, but other browsers don't coalesce // so agressively, which surprises developers. See bug 1420777. if (StaticPrefs::network_http_http2_aggressive_coalescing()) { returntrue;
}
// Make sure that the connection's IP address is one that is in // the set of IP addresses in the entry's DNS response.
NetAddr addr;
nsresult rv = conn->GetPeerAddr(&addr); if (NS_FAILED(rv)) { // Err on the side of not coalescing returnfalse;
} // We don't care about remote port when matching entries.
addr.inet.port = 0; return ent->mAddresses.Contains(addr);
};
if (conn) {
LOG(("Found connection with matching hash")); if (usableEntry(conn)) {
LOG(("> coalescing")); return conn;
} else {
LOG(("> not coalescing as remote address not present in DNS records"));
}
}
}
LOG(("FindCoalescableConnection(%s) no matching conn\n",
ci->HashKey().get())); return nullptr;
}
void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(newConn);
MOZ_ASSERT(newConn->ConnectionInfo());
MOZ_ASSERT(ent);
MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn, aNoHttp3)); if (newConn->ConnectionInfo()->GetWebTransport()) {
LOG(("Don't coalesce a WebTransport conn %p", newConn)); // TODO: implement this properly in bug 1815735. return;
}
HttpConnectionBase* existingConn =
FindCoalescableConnection(ent, true, false, false); if (existingConn) { // Prefer http3 connection, but allow an HTTP/2 connection if it is used for // WebSocket. if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn); if (connTCP && !connTCP->IsForWebSocket()) {
LOG(
("UpdateCoalescingForNewConn() found existing active H2 conn that " "could have served newConn, but new connection is H3, therefore " "close the H2 conncetion"));
existingConn->SetCloseReason(
ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
existingConn->DontReuse();
}
} elseif (existingConn->UsingHttp3() && newConn->UsingSpdy()) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(newConn); if (connTCP && !connTCP->IsForWebSocket() && !aNoHttp3) {
LOG(
("UpdateCoalescingForNewConn() found existing active H3 conn that " "could have served H2 newConn graceful close of newConn=%p to " "migrate to existingConn %p\n",
newConn, existingConn));
newConn->SetCloseReason(
ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
newConn->DontReuse(); return;
}
} else {
LOG(
("UpdateCoalescingForNewConn() found existing active conn that could " "have served newConn " "graceful close of newConn=%p to migrate to existingConn %p\n",
newConn, existingConn));
newConn->SetCloseReason(
ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
newConn->DontReuse(); return;
}
}
// This connection might go into the mCoalescingHash for new transactions to // be coalesced onto if it can accept new transactions if (!newConn->CanDirectlyActivate()) { return;
}
uint32_t keyLen = ent->mCoalescingKeys.Length(); for (uint32_t i = 0; i < keyLen; ++i) {
LOG(( "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
newConn, newConn->ConnectionInfo()->HashKey().get(),
ent->mCoalescingKeys[i].get()));
mCoalescingHash
.LookupOrInsertWith(
ent->mCoalescingKeys[i],
[] {
LOG(("UpdateCoalescingForNewConn() need new list element\n")); return MakeUnique<nsTArray<nsWeakPtr>>(1);
})
->AppendElement(do_GetWeakReference( static_cast<nsISupportsWeakReference*>(newConn)));
}
// this is a new connection that can be coalesced onto. hooray! // if there are other connection to this entry (e.g. // some could still be handshaking, shutting down, etc..) then close // them down after any transactions that are on them are complete. // This probably happened due to the parallel connection algorithm // that is used only before the host is known to speak h2.
ent->MakeAllDontReuseExcept(newConn);
}
// This function lets a connection, after completing the NPN phase, // report whether or not it is using spdy through the usingSpdy // argument. It would not be necessary if NPN were driven out of // the connection manager. The connection entry associated with the // connection is then updated to indicate whether or not we want to use // spdy with that host and update the coalescing hash // entries used for de-sharding hostsnames. void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn, bool usingSpdy, bool disallowHttp3) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); if (!conn->ConnectionInfo()) { return;
}
ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey()); if (!ent || !usingSpdy) { return;
}
// if !considerAll iterate the pending list until one is dispatched // successfully. Keep iterating afterwards only until a transaction fails to // dispatch. if considerAll == true then try and dispatch all items. for (uint32_t i = 0; i < pendingQ.Length();) {
pendingTransInfo = pendingQ[i];
rv = TryDispatchTransaction(ent, alreadyDnsAndConnectSocketOrWaitingForTLS,
pendingTransInfo); if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) { if (NS_SUCCEEDED(rv)) {
LOG((" dispatching pending transaction...\n"));
} else {
LOG(
(" removing pending transaction based on " "TryDispatchTransaction returning hard error %" PRIx32 "\n", static_cast<uint32_t>(rv)));
} if (pendingQ.RemoveElement(pendingTransInfo)) { // pendingTransInfo is now potentially destroyed
dispatchedSuccessfully = true; continue; // dont ++i as we just made the array shorter
}
LOG((" transaction not found in pending queue\n"));
}
if (dispatchedSuccessfully && !considerAll) break;
// No need to try dispatching if we reach the active connection limit. if (!availableConnections) { return;
}
// Only have to get transactions from the queue whose window id is 0. if (!gHttpHandler->ActiveTabPriority()) {
ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections); return;
}
if (!maxFocusedWindowConnections) {
maxFocusedWindowConnections = 1;
}
// Only need to dispatch transactions for either focused or // non-focused window because considerAll is false. if (!considerAll) {
ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
maxFocusedWindowConnections);
if (pendingQ.IsEmpty()) {
ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ,
availableConnections);
} return;
}
if (maxNonFocusedWindowConnections) {
ent->AppendPendingQForNonFocusedWindows(
mCurrentBrowserId, remainingPendingQ, maxNonFocusedWindowConnections);
}
// If the slots for either focused or non-focused window are not filled up // to the availability, try to use the remaining available connections // for the other slot (with preference for the focused window). if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
ent->AppendPendingQForFocusedWindow(
mCurrentBrowserId, pendingQ,
maxNonFocusedWindowConnections - remainingPendingQ.Length());
} elseif (pendingQ.Length() < maxFocusedWindowConnections) {
ent->AppendPendingQForNonFocusedWindows(
mCurrentBrowserId, remainingPendingQ,
maxFocusedWindowConnections - pendingQ.Length());
}
// Append elements in |remainingPendingQ| to |pendingQ|. The order in // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
pendingQ.AppendElements(std::move(remainingPendingQ));
}
// The only case that |pendingQ| is empty is when there is no // connection available for dispatching. if (pendingQ.IsEmpty()) { return dispatchedSuccessfully;
}
// Put the leftovers into connection entry, in the same order as they // were before to keep the natural ordering. for (constauto& transactionInfo : Reversed(pendingQ)) {
ent->InsertTransaction(transactionInfo, true);
}
// Only remove empty pendingQ when considerAll is true. if (considerAll) {
ent->RemoveEmptyPendingQ();
}
return dispatchedSuccessfully;
}
bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
ConnectionEntry* ent = mCT.GetWeak(ci->HashKey()); if (ent) return ProcessPendingQForEntry(ent, false); returnfalse;
}
// we're at the active connection limit if any one of the following conditions // is true: // (1) at max-connections // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy // (3) keep-alive disabled and at max-connections-per-server bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry* ent,
uint32_t caps) {
nsHttpConnectionInfo* ci = ent->mConnInfo;
uint32_t totalCount = ent->TotalActiveConnections();
if (ci->IsHttp3()) { if (ci->GetWebTransport()) { // TODO: implement this properly in bug 1815735. returnfalse;
} return totalCount > 0;
}
if (caps & NS_HTTP_URGENT_START) { if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
LOG(( "The number of total connections are greater than or equal to sum of " "max urgent-start queue length and the number of max persistent " "connections.\n")); returntrue;
} returnfalse;
}
// update maxconns if potentially limited by the max socket count // this requires a dynamic reduction in the max socket count to a point // lower than the max-connections pref.
uint32_t maxSocketCount = gHttpHandler->MaxSocketCount(); if (mMaxConns > maxSocketCount) {
mMaxConns = maxSocketCount;
LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
mMaxConns));
}
// If there are more active connections than the global limit, then we're // done. Purging idle connections won't get us below it. if (mNumActiveConns >= mMaxConns) {
LOG((" num active conns == max conns\n")); returntrue;
}
bool result = (totalCount >= maxPersistConns);
LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false")); return result;
}
// returns NS_OK if a connection was started // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to // ephemeral limits // returns other NS_ERROR on hard failure conditions
nsresult nsHttpConnectionMgr::MakeNewConnection(
ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent,
pendingTransInfo->Transaction()));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (ent->FindConnToClaim(pendingTransInfo)) { return NS_OK;
}
nsHttpTransaction* trans = pendingTransInfo->Transaction();
// If this host is trying to negotiate a SPDY session right now, // don't create any new connections until the result of the // negotiation is known. if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && ent->RestrictConnections()) {
LOG(
("nsHttpConnectionMgr::MakeNewConnection [ci = %s] " "Not Available Due to RestrictConnections()\n",
ent->mConnInfo->HashKey().get())); return NS_ERROR_NOT_AVAILABLE;
}
// We need to make a new connection. If that is going to exceed the // global connection limit then try and free up some room by closing // an idle connection to another host. We know it won't select "ent" // because we have already determined there are no idle connections // to our destination
if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) { // If the global number of connections is preventing the opening of new // connections to a host without idle connections, then close them // regardless of their TTL. auto iter = mCT.ConstIter(); while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) {
RefPtr<ConnectionEntry> entry = iter.Data();
entry->CloseIdleConnections((mNumIdleConns + mNumActiveConns + 1) -
mMaxConns);
iter.Next();
}
}
if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns &&
StaticPrefs::network_http_http2_enabled()) { // If the global number of connections is preventing the opening of new // connections to a host without idle connections, then close any spdy // ASAP. for (const RefPtr<ConnectionEntry>& entry : mCT.Values()) { while (entry->MakeFirstActiveSpdyConnDontReuse()) { // Stop on <= (particularly =) because this dontreuse // causes async close. if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) { goto outerLoopEnd;
}
}
}
outerLoopEnd:;
}
if (AtActiveConnectionLimit(ent, trans->Caps())) { return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = ent->CreateDnsAndConnectSocket(
trans, trans->Caps(), false, false,
trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart, true,
pendingTransInfo); if (NS_FAILED(rv)) { /* hard failure */
LOG(
("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] " "CreateDnsAndConnectSocket() hard failure.\n",
ent->mConnInfo->HashKey().get(), trans));
trans->Close(rv); if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE; return rv;
}
return NS_OK;
}
// returns OK if a connection is found for the transaction // and the transaction is started. // returns ERROR_NOT_AVAILABLE if no connection can be found and it // should be queued until circumstances change // returns other ERROR when transaction has a hard failure and should // not remain in the pending queue
nsresult nsHttpConnectionMgr::TryDispatchTransaction(
ConnectionEntry* ent, bool onlyReusedConnection,
PendingTransactionInfo* pendingTransInfo) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsHttpTransaction* trans = pendingTransInfo->Transaction();
// 0 - If this should use spdy then dispatch it post haste. // 1 - If there is connection pressure then see if we can pipeline this on // a connection of a matching type instead of using a new conn // 2 - If there is an idle connection, use it! // 3 - if class == reval or script and there is an open conn of that type // then pipeline onto shortest pipeline of that class if limits allow // 4 - If we aren't up against our connection limit, // then open a new one // 5 - Try a pipeline if we haven't already - this will be unusual because // it implies a low connection pressure situation where // MakeNewConnection() failed.. that is possible, but unlikely, due to // global limits // 6 - no connection is available - queue it
// step 0 // look for existing spdy connection - that's always best because it is // essentially pipelining without head of line blocking
RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
ent,
(!StaticPrefs::network_http_http2_enabled() ||
(caps & NS_HTTP_DISALLOW_SPDY)),
(!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3))); if (conn) {
LOG(("TryingDispatchTransaction: an active h2 connection exists"));
WebSocketSupport wsSupp = conn->GetWebSocketSupport(); if (trans->IsWebsocketUpgrade()) {
LOG(("TryingDispatchTransaction: this is a websocket upgrade")); if (wsSupp == WebSocketSupport::NO_SUPPORT) {
LOG(( "TryingDispatchTransaction: no support for websockets over Http2")); // This is a websocket transaction and we already have a h2 connection // that do not support websockets, we should disable h2 for this // transaction.
trans->DisableSpdy();
caps &= NS_HTTP_DISALLOW_SPDY;
trans->MakeSticky();
} elseif (wsSupp == WebSocketSupport::SUPPORTED) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
LOG(("TryingDispatchTransaction: websockets over Http2"));
// No limit for number of websockets, dispatch transaction to the tunnel
RefPtr<nsHttpConnection> connToTunnel;
connTCP->CreateTunnelStream(trans, getter_AddRefs(connToTunnel), true);
ent->InsertIntoH2WebsocketConns(connToTunnel);
trans->SetConnection(nullptr);
connToTunnel->SetInSpdyTunnel(); // tells conn it is already in tunnel
trans->SetIsHttp2Websocket(true);
nsresult rv = DispatchTransaction(ent, trans, connToTunnel); // need to undo NonSticky bypass for transaction reset to continue // for correct websocket upgrade handling
trans->MakeSticky(); return rv;
} else { // if we aren't sure that websockets are supported yet or we are // already at the connection limit then we queue the transaction
LOG(("TryingDispatchTransaction: unsure if websockets over Http2")); return NS_ERROR_NOT_AVAILABLE;
}
} else { if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
(caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) ||
!conn->IsExperienced()) {
LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
trans->RemoveDispatchedAsBlocking(); /* just in case */
nsresult rv = DispatchTransaction(ent, trans, conn);
NS_ENSURE_SUCCESS(rv, rv); return NS_OK;
}
unusedSpdyPersistentConnection = conn;
}
}
// If this is not a blocking transaction and the request context for it is // currently processing one or more blocking transactions then we // need to just leave it in the queue until those are complete unless it is // explicitly marked as unblocked. if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) { if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
nsIRequestContext* requestContext = trans->RequestContext(); if (requestContext) {
uint32_t blockers = 0; if (NS_SUCCEEDED(
requestContext->GetBlockingTransactionCount(&blockers)) &&
blockers) { // need to wait for blockers to clear
LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
requestContext, trans, blockers)); return NS_ERROR_NOT_AVAILABLE;
}
}
}
} else { // Mark the transaction and its load group as blocking right now to prevent // other transactions from being reordered in the queue due to slow syns.
trans->DispatchedAsBlocking();
}
// step 1 // If connection pressure, then we want to favor pipelining of any kind // h1 pipelining has been removed
// Subject most transactions at high parallelism to rate pacing. // It will only be actually submitted to the // token bucket once, and if possible it is granted admission synchronously. // It is important to leave a transaction in the pending queue when blocked by // pacing so it can be found on cancel if necessary. // Transactions that cause blocking or bypass it (e.g. js/css) are not rate // limited. if (gHttpHandler->UseRequestTokenBucket()) { // submit even whitelisted transactions to the token bucket though they will // not be slowed by it bool runNow = trans->TryToRunPacedRequest(); if (!runNow) { if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <=
gHttpHandler->RequestTokenBucketMinParallelism()) {
runNow = true; // white list it
} elseif (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
runNow = true; // white list it
}
} if (!runNow) {
LOG((" blocked due to rate pacing trans=%p\n", trans)); return NS_ERROR_NOT_AVAILABLE;
}
}
// step 3 // consider pipelining scripts and revalidations // h1 pipelining has been removed
// Don't dispatch if this transaction is waiting for HTTPS RR. // This usually happens when the pref "network.dns.force_waiting_https_rr" is // true or when echConfig is enabled. if (trans->WaitingForHTTPSRR()) { return NS_ERROR_NOT_AVAILABLE;
}
// step 4 if (!onlyReusedConnection) {
nsresult rv = MakeNewConnection(ent, pendingTransInfo); if (NS_SUCCEEDED(rv)) { // this function returns NOT_AVAILABLE for asynchronous connects
LOG((" dispatched step 4 (async new conn) trans=%p\n", trans)); return NS_ERROR_NOT_AVAILABLE;
}
if (rv != NS_ERROR_NOT_AVAILABLE) { // not available return codes should try next step as they are // not hard errors. Other codes should stop now
LOG((" failed step 4 (%" PRIx32 ") trans=%p\n", static_cast<uint32_t>(rv), trans)); return rv;
}
// repeat step 2 when there are only idle connections and all are urgent, // don't respect urgency so that non-urgent transaction will be allowed // to dispatch on an urgent-start-only marked connection to avoid // dispatch deadlocks if (!(trans->GetClassOfService().Flags() &
nsIClassOfService::UrgentStart) &&
idleConnsAllUrgent &&
ent->ActiveConnsLength() < MaxPersistConnections(ent)) {
rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false); if (NS_SUCCEEDED(rv)) {
LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans)); return NS_OK;
}
}
}
// step 5 // previously pipelined anything here if allowed but h1 pipelining has been // removed
// step 6 if (unusedSpdyPersistentConnection) { // to avoid deadlocks, we need to throw away this perfectly valid SPDY // connection to make room for a new one that can service a no KEEPALIVE // request
unusedSpdyPersistentConnection->DontReuse();
}
LOG((" not dispatched (queued) trans=%p\n", trans)); return NS_ERROR_NOT_AVAILABLE; /* queue it */
}
if (conn) { // This will update the class of the connection to be the class of // the transaction dispatched on it.
ent->InsertIntoActiveConns(conn);
nsresult rv = DispatchTransaction(ent, trans, conn);
NS_ENSURE_SUCCESS(rv, rv);
// It is possible for a rate-paced transaction to be dispatched independent // of the token bucket when the amount of parallelization has changed or // when a muxed connection (e.g. h2) becomes available.
trans->CancelPacing(NS_OK);
TimeStamp now = TimeStamp::Now();
TimeDuration elapsed = now - trans->GetPendingTime(); auto recordPendingTimeForHTTPSRR = [&](nsCString& aKey) {
uint32_t stage = trans->HTTPSSVCReceivedStage(); if (HTTPS_RR_IS_USED(stage)) {
glean::networking::transaction_wait_time_https_rr.AccumulateRawDuration(
elapsed);
// Use this method for dispatching nsAHttpTransction's. It can only safely be // used upon first use of a connection when NPN has not negotiated SPDY vs // HTTP/1 yet as multiplexing onto an existing SPDY session requires a // concrete nsHttpTransaction
nsresult nsHttpConnectionMgr::DispatchAbstractTransaction(
ConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps,
HttpConnectionBase* conn, int32_t priority) {
MOZ_ASSERT(ent);
nsresult rv;
MOZ_ASSERT(!conn->UsingSpdy(), "Spdy Must Not Use DispatchAbstractTransaction");
LOG(
("nsHttpConnectionMgr::DispatchAbstractTransaction " "[ci=%s trans=%p caps=%x conn=%p]\n",
ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
RefPtr<nsAHttpTransaction> transaction(aTrans);
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
// give the transaction the indirect reference to the connection.
transaction->SetConnection(handle);
// sever back references to connection, and do so without triggering // a call to ReclaimConnection ;-)
transaction->SetConnection(nullptr);
handle->Reset(); // destroy the connection
}
nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// since "adds" and "cancels" are processed asynchronously and because // various events might trigger an "add" directly on the socket thread, // we must take care to avoid dispatching a transaction that has already // been canceled (see bug 190001). if (NS_FAILED(trans->Status())) {
LOG((" transaction was canceled... dropping event!\n")); return NS_OK;
}
// Make sure a transaction is not in a pending queue.
CheckTransInPendingQueue(trans);
if (gHttpHandler->EchConfigEnabled(ci->IsHttp3())) {
ent->MaybeUpdateEchConfig(ci);
}
ReportProxyTelemetry(ent);
// Check if the transaction already has a sticky reference to a connection. // If so, then we can just use it directly by transferring its reference // to the new connection variable instead of searching for a new one
if (!ent->IsInActiveConns(conn)) {
LOG(
("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " "sticky connection=%p needs to go on the active list\n",
trans, conn.get()));
// make sure it isn't on the idle list - we expect this to be an // unknown fresh connection
MOZ_ASSERT(!ent->IsInIdleConnections(conn));
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.