diff --git a/src/SignalR/clients/ts/signalr/src/DefaultReconnectPolicy.ts b/src/SignalR/clients/ts/signalr/src/DefaultReconnectPolicy.ts index 1a5ae81cde..34658c5cbc 100644 --- a/src/SignalR/clients/ts/signalr/src/DefaultReconnectPolicy.ts +++ b/src/SignalR/clients/ts/signalr/src/DefaultReconnectPolicy.ts @@ -1,20 +1,20 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -import { IReconnectPolicy } from "./IReconnectPolicy"; +import { IRetryPolicy, RetryContext } from "./IRetryPolicy"; // 0, 2, 10, 30 second delays before reconnect attempts. const DEFAULT_RETRY_DELAYS_IN_MILLISECONDS = [0, 2000, 10000, 30000, null]; /** @private */ -export class DefaultReconnectPolicy implements IReconnectPolicy { +export class DefaultReconnectPolicy implements IRetryPolicy { private readonly retryDelays: Array; constructor(retryDelays?: number[]) { this.retryDelays = retryDelays !== undefined ? [...retryDelays, null] : DEFAULT_RETRY_DELAYS_IN_MILLISECONDS; } - public nextRetryDelayInMilliseconds(previousRetryCount: number): number | null { - return this.retryDelays[previousRetryCount]; + public nextRetryDelayInMilliseconds(retryContext: RetryContext): number | null { + return this.retryDelays[retryContext.previousRetryCount]; } } diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts index 1247c35797..f604fb09b2 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts @@ -5,7 +5,7 @@ import { HandshakeProtocol, HandshakeRequestMessage, HandshakeResponseMessage } import { IConnection } from "./IConnection"; import { CancelInvocationMessage, CompletionMessage, IHubProtocol, InvocationMessage, MessageType, StreamInvocationMessage, StreamItemMessage } from "./IHubProtocol"; import { ILogger, LogLevel } from "./ILogger"; -import { IReconnectPolicy } from "./IReconnectPolicy"; +import { IRetryPolicy } from "./IRetryPolicy"; import { IStreamResult } from "./Stream"; import { Subject } from "./Subject"; import { Arg } from "./Utils"; @@ -32,7 +32,7 @@ export class HubConnection { private readonly cachedPingMessage: string | ArrayBuffer; private readonly connection: IConnection; private readonly logger: ILogger; - private readonly reconnectPolicy?: IReconnectPolicy; + private readonly reconnectPolicy?: IRetryPolicy; private protocol: IHubProtocol; private handshakeProtocol: HandshakeProtocol; private callbacks: { [invocationId: string]: (invocationEvent: StreamItemMessage | CompletionMessage | null, error?: Error) => void }; @@ -81,11 +81,11 @@ export class HubConnection { // create method that can be used by HubConnectionBuilder. An "internal" constructor would just // be stripped away and the '.d.ts' file would have no constructor, which is interpreted as a // public parameter-less constructor. - public static create(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IReconnectPolicy): HubConnection { + public static create(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IRetryPolicy): HubConnection { return new HubConnection(connection, logger, protocol, reconnectPolicy); } - private constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IReconnectPolicy) { + private constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IRetryPolicy) { Arg.isRequired(connection, "connection"); Arg.isRequired(logger, "logger"); Arg.isRequired(protocol, "protocol"); @@ -666,11 +666,12 @@ export class HubConnection { private async reconnect(error?: Error) { const reconnectStartTime = Date.now(); let previousReconnectAttempts = 0; + let retryError = error !== undefined ? error : new Error("Attempting to reconnect due to a unknown error."); - let nextRetryDelay = this.getNextRetryDelay(previousReconnectAttempts++, 0); + let nextRetryDelay = this.getNextRetryDelay(previousReconnectAttempts++, 0, retryError); if (nextRetryDelay === null) { - this.logger.log(LogLevel.Debug, "Connection not reconnecting because the IReconnectPolicy returned null on the first reconnect attempt."); + this.logger.log(LogLevel.Debug, "Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."); this.completeClose(error); return; } @@ -732,9 +733,10 @@ export class HubConnection { this.logger.log(LogLevel.Debug, "Connection left the reconnecting state during reconnect attempt. Done reconnecting."); return; } - } - nextRetryDelay = this.getNextRetryDelay(previousReconnectAttempts++, Date.now() - reconnectStartTime); + retryError = e instanceof Error ? e : new Error(e.toString()); + nextRetryDelay = this.getNextRetryDelay(previousReconnectAttempts++, Date.now() - reconnectStartTime, retryError); + } } this.logger.log(LogLevel.Information, `Reconnect retries have been exhausted after ${Date.now() - reconnectStartTime} ms and ${previousReconnectAttempts} failed attempts. Connection disconnecting.`); @@ -742,11 +744,15 @@ export class HubConnection { this.completeClose(); } - private getNextRetryDelay(previousRetryCount: number, elapsedMilliseconds: number) { + private getNextRetryDelay(previousRetryCount: number, elapsedMilliseconds: number, retryReason: Error) { try { - return this.reconnectPolicy!.nextRetryDelayInMilliseconds(previousRetryCount, elapsedMilliseconds); + return this.reconnectPolicy!.nextRetryDelayInMilliseconds({ + elapsedMilliseconds, + previousRetryCount, + retryReason, + }); } catch (e) { - this.logger.log(LogLevel.Error, `IReconnectPolicy.nextRetryDelayInMilliseconds(${previousRetryCount}, ${elapsedMilliseconds}) threw error '${e}'.`); + this.logger.log(LogLevel.Error, `IRetryPolicy.nextRetryDelayInMilliseconds(${previousRetryCount}, ${elapsedMilliseconds}) threw error '${e}'.`); return null; } } diff --git a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts index c9ae4e15d9..06a7426791 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -7,7 +7,7 @@ import { HubConnection } from "./HubConnection"; import { IHttpConnectionOptions } from "./IHttpConnectionOptions"; import { IHubProtocol } from "./IHubProtocol"; import { ILogger, LogLevel } from "./ILogger"; -import { IReconnectPolicy } from "./IReconnectPolicy"; +import { IRetryPolicy } from "./IRetryPolicy"; import { HttpTransportType } from "./ITransport"; import { JsonHubProtocol } from "./JsonHubProtocol"; import { NullLogger } from "./Loggers"; @@ -51,7 +51,7 @@ export class HubConnectionBuilder { /** If defined, this indicates the client should automatically attempt to reconnect if the connection is lost. */ /** @internal */ - public reconnectPolicy?: IReconnectPolicy; + public reconnectPolicy?: IRetryPolicy; /** Configures console logging for the {@link @aspnet/signalr.HubConnection}. * @@ -164,10 +164,10 @@ export class HubConnectionBuilder { /** Configures the {@link @aspnet/signalr.HubConnection} to automatically attempt to reconnect if the connection is lost. * - * @param {IReconnectPolicy} reconnectPolicy An {@link @aspnet/signalR.IReconnectPolicy} that controls the timing and number of reconnect attempts. + * @param {IRetryPolicy} reconnectPolicy An {@link @aspnet/signalR.IRetryPolicy} that controls the timing and number of reconnect attempts. */ - public withAutomaticReconnect(reconnectPolicy: IReconnectPolicy): HubConnectionBuilder; - public withAutomaticReconnect(retryDelaysOrReconnectPolicy?: number[] | IReconnectPolicy): HubConnectionBuilder { + public withAutomaticReconnect(reconnectPolicy: IRetryPolicy): HubConnectionBuilder; + public withAutomaticReconnect(retryDelaysOrReconnectPolicy?: number[] | IRetryPolicy): HubConnectionBuilder { if (this.reconnectPolicy) { throw new Error("A reconnectPolicy has already been set."); } diff --git a/src/SignalR/clients/ts/signalr/src/IReconnectPolicy.ts b/src/SignalR/clients/ts/signalr/src/IReconnectPolicy.ts deleted file mode 100644 index 7f316aa2ca..0000000000 --- a/src/SignalR/clients/ts/signalr/src/IReconnectPolicy.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -/** An abstraction that controls when the client attempts to reconnect and how many times it does so. */ -export interface IReconnectPolicy { - /** Called after the transport loses the connection. - * - * @param {number} previousRetryCount The number of consecutive failed reconnect attempts so far. - * - * @param {number} elapsedMilliseconds The amount of time in milliseconds spent reconnecting so far. - * - * @returns {number | null} The amount of time in milliseconds to wait before the next reconnect attempt. `null` tells the client to stop retrying and close. - */ - nextRetryDelayInMilliseconds(previousRetryCount: number, elapsedMilliseconds: number): number | null; -} diff --git a/src/SignalR/clients/ts/signalr/src/IRetryPolicy.ts b/src/SignalR/clients/ts/signalr/src/IRetryPolicy.ts new file mode 100644 index 0000000000..fb615a0033 --- /dev/null +++ b/src/SignalR/clients/ts/signalr/src/IRetryPolicy.ts @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +/** An abstraction that controls when the client attempts to reconnect and how many times it does so. */ +export interface IRetryPolicy { + /** Called after the transport loses the connection. + * + * @param {RetryContext} retryContext Details related to the retry event to help determine how long to wait for the next retry. + * + * @returns {number | null} The amount of time in milliseconds to wait before the next retry. `null` tells the client to stop retrying. + */ + nextRetryDelayInMilliseconds(retryContext: RetryContext): number | null; +} + +export interface RetryContext { + /** + * The number of consecutive failed tries so far. + */ + readonly previousRetryCount: number; + + /** + * The amount of time in milliseconds spent retrying so far. + */ + readonly elapsedMilliseconds: number; + + /** + * The error that forced the upcoming retry. + */ + readonly retryReason: Error; +} diff --git a/src/SignalR/clients/ts/signalr/src/index.ts b/src/SignalR/clients/ts/signalr/src/index.ts index 98cb0106d6..dc2086c661 100644 --- a/src/SignalR/clients/ts/signalr/src/index.ts +++ b/src/SignalR/clients/ts/signalr/src/index.ts @@ -21,4 +21,4 @@ export { IStreamSubscriber, IStreamResult, ISubscription } from "./Stream"; export { NullLogger } from "./Loggers"; export { JsonHubProtocol } from "./JsonHubProtocol"; export { Subject } from "./Subject"; -export { IReconnectPolicy } from "./IReconnectPolicy"; +export { IRetryPolicy, RetryContext } from "./IRetryPolicy"; diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts index 9b1d3b34e7..542a5217d9 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts @@ -3,6 +3,7 @@ import { DefaultReconnectPolicy } from "../src/DefaultReconnectPolicy"; import { HubConnection, HubConnectionState } from "../src/HubConnection"; +import { RetryContext } from "../src/IRetryPolicy"; import { JsonHubProtocol } from "../src/JsonHubProtocol"; import { VerifyLogger } from "./Common"; @@ -46,15 +47,17 @@ describe("auto reconnect", () => { let lastRetryCount = -1; let lastElapsedMs = -1; + let retryReason = null; let onreconnectingCount = 0; let onreconnectedCount = 0; let closeCount = 0; const connection = new TestConnection(); const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { - nextRetryDelayInMilliseconds(previousRetryCount: number, elapsedMilliseconds: number) { - lastRetryCount = previousRetryCount; - lastElapsedMs = elapsedMilliseconds; + nextRetryDelayInMilliseconds(retryContext: RetryContext) { + lastRetryCount = retryContext.previousRetryCount; + lastElapsedMs = retryContext.elapsedMilliseconds; + retryReason = retryContext.retryReason; nextRetryDelayCalledPromise.resolve(); return 0; }, @@ -81,8 +84,11 @@ describe("auto reconnect", () => { return promise; }; + const oncloseError = new Error("Connection lost"); + const continueRetryingError = new Error("Reconnect attempt failed"); + // Typically this would be called by the transport - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError); await nextRetryDelayCalledPromise; nextRetryDelayCalledPromise = new PromiseSource(); @@ -90,17 +96,19 @@ describe("auto reconnect", () => { expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(0); expect(lastElapsedMs).toBe(0); + expect(retryReason).toBe(oncloseError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); // Make sure the the Promise is "handled" immediately upon rejection or else this test fails. continueRetryingPromise.catch(() => { }); - continueRetryingPromise.reject(new Error("Reconnect attempt failed")); + continueRetryingPromise.reject(continueRetryingError); await nextRetryDelayCalledPromise; expect(lastRetryCount).toBe(1); expect(lastElapsedMs).toBeGreaterThanOrEqual(0); + expect(retryReason).toBe(continueRetryingError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); @@ -133,18 +141,20 @@ describe("auto reconnect", () => { let lastRetryCount = -1; let lastElapsedMs = -1; + let retryReason = null; let onreconnectingCount = 0; let onreconnectedCount = 0; let closeCount = 0; const connection = new TestConnection(); const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { - nextRetryDelayInMilliseconds(previousRetryCount: number, elapsedMilliseconds: number) { - lastRetryCount = previousRetryCount; - lastElapsedMs = elapsedMilliseconds; + nextRetryDelayInMilliseconds(retryContext: RetryContext) { + lastRetryCount = retryContext.previousRetryCount; + lastElapsedMs = retryContext.elapsedMilliseconds; + retryReason = retryContext.retryReason; nextRetryDelayCalledPromise.resolve(); - return previousRetryCount === 0 ? 0 : null; + return retryContext.previousRetryCount === 0 ? 0 : null; }, }); @@ -163,12 +173,15 @@ describe("auto reconnect", () => { await hubConnection.start(); + const oncloseError = new Error("Connection lost"); + const startError = new Error("Reconnect attempt failed"); + connection.start = () => { - return Promise.reject("Reconnect attempt failed"); + throw startError; }; // Typically this would be called by the transport - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError); await nextRetryDelayCalledPromise; nextRetryDelayCalledPromise = new PromiseSource(); @@ -176,6 +189,7 @@ describe("auto reconnect", () => { expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(0); expect(lastElapsedMs).toBe(0); + expect(retryReason).toBe(oncloseError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); @@ -185,6 +199,7 @@ describe("auto reconnect", () => { expect(hubConnection.state).toBe(HubConnectionState.Disconnected); expect(lastRetryCount).toBe(1); expect(lastElapsedMs).toBeGreaterThanOrEqual(0); + expect(retryReason).toBe(startError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(1); @@ -198,15 +213,17 @@ describe("auto reconnect", () => { let lastRetryCount = -1; let lastElapsedMs = -1; + let retryReason = null; let onreconnectingCount = 0; let onreconnectedCount = 0; let closeCount = 0; const connection = new TestConnection(); const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { - nextRetryDelayInMilliseconds(previousRetryCount: number, elapsedMilliseconds: number) { - lastRetryCount = previousRetryCount; - lastElapsedMs = elapsedMilliseconds; + nextRetryDelayInMilliseconds(retryContext: RetryContext) { + lastRetryCount = retryContext.previousRetryCount; + lastElapsedMs = retryContext.elapsedMilliseconds; + retryReason = retryContext.retryReason; nextRetryDelayCalledPromise.resolve(); return 0; }, @@ -227,8 +244,11 @@ describe("auto reconnect", () => { await hubConnection.start(); + const oncloseError = new Error("Connection lost 1"); + const oncloseError2 = new Error("Connection lost 2"); + // Typically this would be called by the transport - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError); await nextRetryDelayCalledPromise; nextRetryDelayCalledPromise = new PromiseSource(); @@ -236,6 +256,7 @@ describe("auto reconnect", () => { expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(0); expect(lastElapsedMs).toBe(0); + expect(retryReason).toBe(oncloseError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); @@ -250,13 +271,14 @@ describe("auto reconnect", () => { expect(onreconnectedCount).toBe(1); expect(closeCount).toBe(0); - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError2); await nextRetryDelayCalledPromise; expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(0); expect(lastElapsedMs).toBe(0); + expect(retryReason).toBe(oncloseError2); expect(onreconnectingCount).toBe(2); expect(onreconnectedCount).toBe(1); expect(closeCount).toBe(0); @@ -362,6 +384,7 @@ describe("auto reconnect", () => { let nextRetryDelayCalledPromise = new PromiseSource(); let lastRetryCount = 0; + let retryReason = null; let onreconnectingCount = 0; let onreconnectedCount = 0; let closeCount = 0; @@ -369,8 +392,9 @@ describe("auto reconnect", () => { // Disable autoHandshake in TestConnection const connection = new TestConnection(false); const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { - nextRetryDelayInMilliseconds(previousRetryCount: number) { - lastRetryCount = previousRetryCount; + nextRetryDelayInMilliseconds(retryContext: RetryContext) { + lastRetryCount = retryContext.previousRetryCount; + retryReason = retryContext.retryReason; nextRetryDelayCalledPromise.resolve(); return 0; }, @@ -400,14 +424,18 @@ describe("auto reconnect", () => { return Promise.resolve(); }; + const oncloseError = new Error("Connection lost 1"); + const oncloseError2 = new Error("Connection lost 2"); + // Typically this would be called by the transport - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError); await nextRetryDelayCalledPromise; nextRetryDelayCalledPromise = new PromiseSource(); expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(0); + expect(retryReason).toBe(oncloseError); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); @@ -416,12 +444,13 @@ describe("auto reconnect", () => { replacedStartCalledPromise = new PromiseSource(); // Fail underlying connection during reconnect during handshake - connection.onclose!(new Error("Connection lost")); + connection.onclose!(oncloseError2); await nextRetryDelayCalledPromise; expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); expect(lastRetryCount).toBe(1); + expect(retryReason).toBe(oncloseError2); expect(onreconnectingCount).toBe(1); expect(onreconnectedCount).toBe(0); expect(closeCount).toBe(0); @@ -461,8 +490,8 @@ describe("auto reconnect", () => { // Disable autoHandshake in TestConnection const connection = new TestConnection(false); const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { - nextRetryDelayInMilliseconds(previousRetryCount: number) { - lastRetryCount = previousRetryCount; + nextRetryDelayInMilliseconds(retryContext: RetryContext) { + lastRetryCount = retryContext.previousRetryCount; nextRetryDelayCalledPromise.resolve(); return 0; }, diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts index c0a331e558..4aaa70a0d8 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts @@ -322,7 +322,13 @@ describe("HubConnectionBuilder", () => { let retryCount = 0; for (const delay of DEFAULT_RETRY_DELAYS_IN_MILLISECONDS) { - expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryCount++, 0)).toBe(delay); + const retryContext = { + previousRetryCount: retryCount++, + elapsedMilliseconds: 0, + retryReason: new Error(), + }; + + expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContext)).toBe(delay); } }); @@ -333,23 +339,47 @@ describe("HubConnectionBuilder", () => { let retryCount = 0; for (const delay of customRetryDelays) { - expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryCount++, 0)).toBe(delay); + const retryContext = { + previousRetryCount: retryCount++, + elapsedMilliseconds: 0, + retryReason: new Error(), + }; + + expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContext)).toBe(delay); } - expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryCount, 0)).toBe(null); + const retryContextFinal = { + previousRetryCount: retryCount++, + elapsedMilliseconds: 0, + retryReason: new Error(), + }; + + expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContextFinal)).toBe(null); }); - it("withAutomaticReconnect uses a custom IReconnectPolicy when provided", () => { + it("withAutomaticReconnect uses a custom IRetryPolicy when provided", () => { const customRetryDelays = [127, 0, 0, 1]; const builder = new HubConnectionBuilder() .withAutomaticReconnect(new DefaultReconnectPolicy(customRetryDelays)); let retryCount = 0; for (const delay of customRetryDelays) { - expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryCount++, 0)).toBe(delay); + const retryContext = { + previousRetryCount: retryCount++, + elapsedMilliseconds: 0, + retryReason: new Error(), + }; + + expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContext)).toBe(delay); } - expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryCount, 0)).toBe(null); + const retryContextFinal = { + previousRetryCount: retryCount++, + elapsedMilliseconds: 0, + retryReason: new Error(), + }; + + expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContextFinal)).toBe(null); }); });