mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
fix(client): clear the scheduled retry when a connect attempt opens a transport
An explicit connect() during reconnect backoff opened a new transport but left the scheduled retry armed. When it fired on the healthy connection it disposed the transport and reconnected, dropping requests in flight. This is why owned-subscriptions-old-daemon could still fail after #4858 added the explicit reconnect. attemptConnect now owns the rule ensureConnected already applied; the new unit test fails on the old client.
This commit is contained in:
@@ -989,6 +989,49 @@ test("ensureConnected reconnects immediately without leaving the scheduled retry
|
||||
}
|
||||
});
|
||||
|
||||
test("connect during a scheduled retry reconnects immediately without leaving the retry armed", async () => {
|
||||
useHeartbeatClock();
|
||||
try {
|
||||
const first = createMockTransport();
|
||||
const second = createMockTransport();
|
||||
const third = createMockTransport();
|
||||
const transports = [first, second, third];
|
||||
let transportIndex = 0;
|
||||
const client = new DaemonClient({
|
||||
url: "ws://test",
|
||||
clientId: "clsk_explicit_reconnect",
|
||||
reconnect: { enabled: true, baseDelayMs: 1_500, maxDelayMs: 1_500 },
|
||||
transportFactory: () => {
|
||||
const transport = transports[transportIndex];
|
||||
if (!transport) throw new Error("unexpected extra reconnect");
|
||||
transportIndex += 1;
|
||||
return transport.transport;
|
||||
},
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const initialConnect = client.connect();
|
||||
first.triggerOpen();
|
||||
await initialConnect;
|
||||
first.triggerClose({ code: 1001, reason: "daemon restarted" });
|
||||
expect(client.getConnectionState().status).toBe("disconnected");
|
||||
|
||||
const reconnect = client.connect();
|
||||
expect(client.getConnectionState().status).toBe("connecting");
|
||||
expect(transportIndex).toBe(2);
|
||||
|
||||
second.triggerOpen();
|
||||
await reconnect;
|
||||
expect(client.getConnectionState().status).toBe("connected");
|
||||
await vi.advanceTimersByTimeAsync(1_500);
|
||||
|
||||
expect(client.getConnectionState().status).toBe("connected");
|
||||
expect(transportIndex).toBe(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
test("disabling reconnect cancels a pending retry until explicitly resumed", async () => {
|
||||
useHeartbeatClock();
|
||||
try {
|
||||
|
||||
@@ -1255,6 +1255,12 @@ export class DaemonClient {
|
||||
if (this.connectionState.status === "connecting") {
|
||||
return;
|
||||
}
|
||||
// This attempt supersedes any retry the last disconnect scheduled. Left
|
||||
// armed, that retry would tear down the connection this attempt opens.
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
const password = normalizePassword(this.config.password);
|
||||
@@ -1464,10 +1470,6 @@ export class DaemonClient {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
if (this.connectPromise) {
|
||||
this.attemptConnect();
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user