Skip to content

Commit 25beb06

Browse files
committed
Bug 1816258 - Websocket opening takes ages after repeated failures to same address r=necko-reviewers,valentin
Although our implementation follows the intention of rfc6455#section-4.1, we can handle the described situation more gracefully by prioritizing new WS connections to paths that have _not_ previously failed. Differential Revision: https://phabricator.services.mozilla.com/D193386
1 parent 306e1d4 commit 25beb06

File tree

2 files changed

+69
-30
lines changed

2 files changed

+69
-30
lines changed

netwerk/protocol/websocket/WebSocketChannel.cpp

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@
7575
using namespace mozilla;
7676
using namespace mozilla::net;
7777

78-
namespace mozilla {
79-
namespace net {
78+
namespace mozilla::net {
8079

8180
NS_IMPL_ISUPPORTS(WebSocketChannel, nsIWebSocketChannel, nsIHttpUpgradeListener,
8281
nsIRequestObserver, nsIStreamListener, nsIProtocolHandler,
@@ -121,11 +120,11 @@ const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
121120
const uint32_t kWSReconnectMaxDelay = 60 * 1000;
122121

123122
// hold record of failed connections, and calculates needed delay for reconnects
124-
// to same host/port.
123+
// to same host/path/port.
125124
class FailDelay {
126125
public:
127-
FailDelay(nsCString address, int32_t port)
128-
: mAddress(std::move(address)), mPort(port) {
126+
FailDelay(nsCString address, nsCString path, int32_t port)
127+
: mAddress(std::move(address)), mPath(std::move(path)), mPort(port) {
129128
mLastFailure = TimeStamp::Now();
130129
mNextDelay = kWSReconnectInitialBaseDelay +
131130
(rand() % kWSReconnectInitialRandomDelay);
@@ -139,9 +138,10 @@ class FailDelay {
139138
mNextDelay = static_cast<uint32_t>(
140139
std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
141140
LOG(
142-
("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to "
141+
("WebSocket: FailedAgain: host=%s, path=%s, port=%d: incremented delay "
142+
"to "
143143
"%" PRIu32,
144-
mAddress.get(), mPort, mNextDelay));
144+
mAddress.get(), mPath.get(), mPort, mNextDelay));
145145
}
146146

147147
// returns 0 if there is no need to delay (i.e. delay interval is over)
@@ -160,6 +160,7 @@ class FailDelay {
160160
}
161161

162162
nsCString mAddress; // IP address (or hostname if using proxy)
163+
nsCString mPath;
163164
int32_t mPort;
164165

165166
private:
@@ -191,16 +192,16 @@ class FailDelayManager {
191192

192193
~FailDelayManager() { MOZ_COUNT_DTOR(FailDelayManager); }
193194

194-
void Add(nsCString& address, int32_t port) {
195+
void Add(nsCString& address, nsCString& path, int32_t port) {
195196
if (mDelaysDisabled) return;
196197

197-
UniquePtr<FailDelay> record(new FailDelay(address, port));
198+
UniquePtr<FailDelay> record(new FailDelay(address, path, port));
198199
mEntries.AppendElement(std::move(record));
199200
}
200201

201202
// Element returned may not be valid after next main thread event: don't keep
202203
// pointer to it around
203-
FailDelay* Lookup(nsCString& address, int32_t port,
204+
FailDelay* Lookup(nsCString& address, nsCString& path, int32_t port,
204205
uint32_t* outIndex = nullptr) {
205206
if (mDelaysDisabled) return nullptr;
206207

@@ -211,7 +212,8 @@ class FailDelayManager {
211212
// indexing simpler
212213
for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
213214
FailDelay* fail = mEntries[i].get();
214-
if (fail->mAddress.Equals(address) && fail->mPort == port) {
215+
if (fail->mAddress.Equals(address) && fail->mPath.Equals(path) &&
216+
fail->mPort == port) {
215217
if (outIndex) *outIndex = i;
216218
result = fail;
217219
// break here: removing more entries would mess up *outIndex.
@@ -230,7 +232,7 @@ class FailDelayManager {
230232
void DelayOrBegin(WebSocketChannel* ws) {
231233
if (!mDelaysDisabled) {
232234
uint32_t failIndex = 0;
233-
FailDelay* fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
235+
FailDelay* fail = Lookup(ws->mAddress, ws->mPath, ws->mPort, &failIndex);
234236

235237
if (fail) {
236238
TimeStamp rightNow = TimeStamp::Now();
@@ -266,13 +268,14 @@ class FailDelayManager {
266268

267269
// Remove() also deletes all expired entries as it iterates: better for
268270
// battery life than using a periodic timer.
269-
void Remove(nsCString& address, int32_t port) {
271+
void Remove(nsCString& address, nsCString& path, int32_t port) {
270272
TimeStamp rightNow = TimeStamp::Now();
271273

272274
// iterate from end, to make deletion indexing easier
273275
for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
274276
FailDelay* entry = mEntries[i].get();
275-
if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
277+
if ((entry->mAddress.Equals(address) && entry->mPath.Equals(path) &&
278+
entry->mPort == port) ||
276279
entry->IsExpired(rightNow)) {
277280
mEntries.RemoveElementAt(i);
278281
}
@@ -321,14 +324,29 @@ class nsWSAdmissionManager {
321324

322325
// If there is already another WS channel connecting to this IP address,
323326
// defer BeginOpen and mark as waiting in queue.
324-
bool found = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
327+
bool hostFound = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
328+
329+
uint32_t failIndex = 0;
330+
FailDelay* fail = sManager->mFailures.Lookup(ws->mAddress, ws->mPath,
331+
ws->mPort, &failIndex);
332+
bool existingFail = fail != nullptr;
325333

326334
// Always add ourselves to queue, even if we'll connect immediately
327335
UniquePtr<nsOpenConn> newdata(
328-
new nsOpenConn(ws->mAddress, ws->mOriginSuffix, ws));
329-
sManager->mQueue.AppendElement(std::move(newdata));
336+
new nsOpenConn(ws->mAddress, ws->mOriginSuffix, existingFail, ws));
330337

331-
if (found) {
338+
// If a connection has not previously failed then prioritize it over
339+
// connections that have
340+
if (existingFail) {
341+
sManager->mQueue.AppendElement(std::move(newdata));
342+
} else {
343+
uint32_t insertionIndex = sManager->IndexOfFirstFailure();
344+
MOZ_ASSERT(insertionIndex <= sManager->mQueue.Length(),
345+
"Insertion index outside bounds");
346+
sManager->mQueue.InsertElementAt(insertionIndex, std::move(newdata));
347+
}
348+
349+
if (hostFound) {
332350
LOG(
333351
("Websocket: some other channel is connecting, changing state to "
334352
"CONNECTING_QUEUED"));
@@ -357,7 +375,8 @@ class nsWSAdmissionManager {
357375
sManager->RemoveFromQueue(aChannel);
358376

359377
// Connection succeeded, so stop keeping track of any previous failures
360-
sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
378+
sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPath,
379+
aChannel->mPort);
361380

362381
// Check for queued connections to same host.
363382
// Note: still need to check for failures, since next websocket with same
@@ -378,24 +397,27 @@ class nsWSAdmissionManager {
378397

379398
if (NS_FAILED(aReason)) {
380399
// Have we seen this failure before?
381-
FailDelay* knownFailure =
382-
sManager->mFailures.Lookup(aChannel->mAddress, aChannel->mPort);
400+
FailDelay* knownFailure = sManager->mFailures.Lookup(
401+
aChannel->mAddress, aChannel->mPath, aChannel->mPort);
383402
if (knownFailure) {
384403
if (aReason == NS_ERROR_NOT_CONNECTED) {
385404
// Don't count close() before connection as a network error
386405
LOG(
387-
("Websocket close() before connection to %s, %d completed"
406+
("Websocket close() before connection to %s, %s, %d completed"
388407
" [this=%p]",
389-
aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
408+
aChannel->mAddress.get(), aChannel->mPath.get(),
409+
(int)aChannel->mPort, aChannel));
390410
} else {
391411
// repeated failure to connect: increase delay for next connection
392412
knownFailure->FailedAgain();
393413
}
394414
} else {
395415
// new connection failure: record it.
396-
LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
397-
aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
398-
sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
416+
LOG(("WebSocket: connection to %s, %s, %d failed: [this=%p]",
417+
aChannel->mAddress.get(), aChannel->mPath.get(),
418+
(int)aChannel->mPort, aChannel));
419+
sManager->mFailures.Add(aChannel->mAddress, aChannel->mPath,
420+
aChannel->mPort);
399421
}
400422
}
401423

@@ -480,15 +502,19 @@ class nsWSAdmissionManager {
480502

481503
class nsOpenConn {
482504
public:
483-
nsOpenConn(nsCString& addr, nsCString& originSuffix,
505+
nsOpenConn(nsCString& addr, nsCString& originSuffix, bool failed,
484506
WebSocketChannel* channel)
485-
: mAddress(addr), mOriginSuffix(originSuffix), mChannel(channel) {
507+
: mAddress(addr),
508+
mOriginSuffix(originSuffix),
509+
mFailed(failed),
510+
mChannel(channel) {
486511
MOZ_COUNT_CTOR(nsOpenConn);
487512
}
488513
MOZ_COUNTED_DTOR(nsOpenConn)
489514

490515
nsCString mAddress;
491516
nsCString mOriginSuffix;
517+
bool mFailed = false;
492518
RefPtr<WebSocketChannel> mChannel;
493519
};
494520

@@ -535,6 +561,15 @@ class nsWSAdmissionManager {
535561
return -1;
536562
}
537563

564+
// Returns the index of the first entry that failed, or else the last entry if
565+
// none found
566+
uint32_t IndexOfFirstFailure() {
567+
for (uint32_t i = 0; i < mQueue.Length(); i++) {
568+
if (mQueue[i]->mFailed) return i;
569+
}
570+
return mQueue.Length();
571+
}
572+
538573
// SessionCount might be decremented from the main or the socket
539574
// thread, so manage it with atomic counters
540575
Atomic<int32_t> mSessionCount;
@@ -2853,6 +2888,10 @@ nsresult WebSocketChannel::DoAdmissionDNS() {
28532888
rv = mURI->GetHost(hostName);
28542889
NS_ENSURE_SUCCESS(rv, rv);
28552890
mAddress = hostName;
2891+
nsCString path;
2892+
rv = mURI->GetFilePath(path);
2893+
NS_ENSURE_SUCCESS(rv, rv);
2894+
mPath = path;
28562895
rv = mURI->GetPort(&mPort);
28572896
NS_ENSURE_SUCCESS(rv, rv);
28582897
if (mPort == -1) mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
@@ -4292,7 +4331,6 @@ nsresult WebSocketChannel::OnDataReceived(uint8_t* aData, uint32_t aCount) {
42924331
return ProcessInput(aData, aCount);
42934332
}
42944333

4295-
} // namespace net
4296-
} // namespace mozilla
4334+
} // namespace mozilla::net
42974335

42984336
#undef CLOSE_GOING_AWAY

netwerk/protocol/websocket/WebSocketChannel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class WebSocketChannel : public BaseWebSocketChannel,
229229
// then to IP address (unless we're leaving DNS resolution to a proxy server)
230230
// MainThread only
231231
nsCString mAddress;
232+
nsCString mPath;
232233
int32_t mPort; // WS server port
233234
// Secondary key for the connection queue. Used by nsWSAdmissionManager.
234235
nsCString mOriginSuffix; // MainThread only

0 commit comments

Comments
 (0)