Skip to content

Commit 4310f2c

Browse files
authored
Better way to detect "Failed to start the HttpConnection before stop() was called." (#40018)
1 parent 130849a commit 4310f2c

File tree

4 files changed

+40
-12
lines changed

4 files changed

+40
-12
lines changed

src/SignalR/clients/ts/signalr/src/HttpConnection.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
import { DefaultHttpClient } from "./DefaultHttpClient";
5-
import { AggregateErrors, DisabledTransportError, FailedToNegotiateWithServerError, FailedToStartTransportError, HttpError, UnsupportedTransportError } from "./Errors";
5+
import { AggregateErrors, DisabledTransportError, FailedToNegotiateWithServerError, FailedToStartTransportError, HttpError, UnsupportedTransportError, AbortError } from "./Errors";
66
import { HeaderNames } from "./HeaderNames";
77
import { HttpClient } from "./HttpClient";
88
import { IConnection } from "./IConnection";
@@ -146,12 +146,12 @@ export class HttpConnection implements IConnection {
146146
// We cannot await stopPromise inside startInternal since stopInternal awaits the startInternalPromise.
147147
await this._stopPromise;
148148

149-
return Promise.reject(new Error(message));
149+
return Promise.reject(new AbortError(message));
150150
} else if (this._connectionState as any !== ConnectionState.Connected) {
151151
// stop() was called and transitioned the client into the Disconnecting state.
152152
const message = "HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";
153153
this._logger.log(LogLevel.Error, message);
154-
return Promise.reject(new Error(message));
154+
return Promise.reject(new AbortError(message));
155155
}
156156

157157
this._connectionStarted = true;
@@ -247,7 +247,7 @@ export class HttpConnection implements IConnection {
247247
negotiateResponse = await this._getNegotiationResponse(url);
248248
// the user tries to stop the connection when it is being started
249249
if (this._connectionState === ConnectionState.Disconnecting || this._connectionState === ConnectionState.Disconnected) {
250-
throw new Error("The connection was stopped during negotiation.");
250+
throw new AbortError("The connection was stopped during negotiation.");
251251
}
252252

253253
if (negotiateResponse.error) {
@@ -401,7 +401,7 @@ export class HttpConnection implements IConnection {
401401
if (this._connectionState !== ConnectionState.Connecting) {
402402
const message = "Failed to select transport before stop() was called.";
403403
this._logger.log(LogLevel.Debug, message);
404-
return Promise.reject(new Error(message));
404+
return Promise.reject(new AbortError(message));
405405
}
406406
}
407407
}

src/SignalR/clients/ts/signalr/src/HubConnection.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { HandshakeProtocol, HandshakeRequestMessage, HandshakeResponseMessage } from "./HandshakeProtocol";
55
import { IConnection } from "./IConnection";
6+
import { AbortError } from "./Errors";
67
import { CancelInvocationMessage, CompletionMessage, IHubProtocol, InvocationMessage, MessageType, StreamInvocationMessage, StreamItemMessage } from "./IHubProtocol";
78
import { ILogger, LogLevel } from "./ILogger";
89
import { IRetryPolicy } from "./IRetryPolicy";
@@ -296,7 +297,7 @@ export class HubConnection {
296297

297298
this._cleanupTimeout();
298299
this._cleanupPingTimer();
299-
this._stopDuringStartError = error || new Error("The connection was stopped before the hub handshake could complete.");
300+
this._stopDuringStartError = error || new AbortError("The connection was stopped before the hub handshake could complete.");
300301

301302
// HttpConnection.stop() should not complete until after either HttpConnection.start() fails
302303
// or the onclose callback is invoked. The onclose callback will transition the HubConnection
@@ -698,7 +699,7 @@ export class HubConnection {
698699
this._logger.log(LogLevel.Debug, `HubConnection.connectionClosed(${error}) called while in state ${this._connectionState}.`);
699700

700701
// Triggering this.handshakeRejecter is insufficient because it could already be resolved without the continuation having run yet.
701-
this._stopDuringStartError = this._stopDuringStartError || error || new Error("The underlying connection was closed before the hub handshake could complete.");
702+
this._stopDuringStartError = this._stopDuringStartError || error || new AbortError("The underlying connection was closed before the hub handshake could complete.");
702703

703704
// If the handshake is in progress, start will be waiting for the handshake promise, so we complete it.
704705
// If it has already completed, this should just noop.

src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions";
77
import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport";
88
import { getUserAgentHeader } from "../src/Utils";
99

10-
import { HttpError } from "../src/Errors";
10+
import { AbortError, HttpError } from "../src/Errors";
1111
import { ILogger, LogLevel } from "../src/ILogger";
1212
import { NullLogger } from "../src/Loggers";
1313
import { EventSourceConstructor, WebSocketConstructor } from "../src/Polyfills";
@@ -161,9 +161,12 @@ describe("HttpConnection", () => {
161161

162162
await stopPromise;
163163

164-
await expect(startPromise)
165-
.rejects
166-
.toThrow("The connection was stopped during negotiation.");
164+
try {
165+
await startPromise
166+
} catch (e) {
167+
expect(e).toBeInstanceOf(AbortError);
168+
expect((e as AbortError).message).toBe("The connection was stopped during negotiation.");
169+
}
167170
},
168171
"Failed to start the connection: Error: The connection was stopped during negotiation.");
169172
});

src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
import { AbortError } from "../src/Errors";
45
import { HubConnection, HubConnectionState } from "../src/HubConnection";
56
import { IConnection } from "../src/IConnection";
67
import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol";
@@ -391,7 +392,10 @@ describe("HubConnection", () => {
391392
await connection.stop();
392393
try {
393394
await startPromise;
394-
} catch { }
395+
} catch (e) {
396+
expect(e).toBeInstanceOf(AbortError);
397+
expect((e as AbortError).message).toBe("The underlying connection was closed before the hub handshake could complete.");
398+
}
395399
} finally {
396400
await hubConnection.stop();
397401
}
@@ -802,6 +806,26 @@ describe("HubConnection", () => {
802806
"Server returned handshake error: Error!");
803807
});
804808

809+
it("connection stopped before handshake completes",async () => {
810+
await VerifyLogger.run(async (logger) => {
811+
const connection = new TestConnection(false);
812+
const hubConnection = createHubConnection(connection, logger);
813+
814+
let startCompleted = false;
815+
const startPromise = hubConnection.start().then(() => startCompleted = true);
816+
expect(startCompleted).toBe(false);
817+
818+
await hubConnection.stop()
819+
820+
try {
821+
await startPromise
822+
} catch (e) {
823+
expect(e).toBeInstanceOf(AbortError);
824+
expect((e as AbortError).message).toBe("The connection was stopped before the hub handshake could complete.");
825+
}
826+
}, "The connection was stopped before the hub handshake could complete.");
827+
});
828+
805829
it("stop on close message", async () => {
806830
await VerifyLogger.run(async (logger) => {
807831
const connection = new TestConnection();

0 commit comments

Comments
 (0)