From 2826c103a2f5786575157c6aa9cbd270746b57ca Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Thu, 22 Apr 2021 23:13:28 +0200 Subject: [PATCH] fix(client): Shouldn't reconnect if all subscriptions complete while waiting for retry Closes #163 --- src/client.ts | 14 ++++++++-- src/tests/client.ts | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 97ca0c77..33a384a3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -351,6 +351,13 @@ export function createClient(options: ClientOptions): Client { (async () => { if (retrying) { await retryWait(retries); + + // subscriptions might complete while waiting for retry + if (!locks) { + connecting = undefined; + return denied({ code: 1000, reason: 'All Subscriptions Gone' }); + } + retries++; } @@ -431,9 +438,8 @@ export function createClient(options: ClientOptions): Client { Promise.race([ // wait for released.then(() => { - // decrement the subscription locks - if (!--locks) { - // and if no more are present, complete the connection + if (!locks) { + // and if no more locks are present, complete the connection const complete = () => socket.close(1000, 'Normal Closure'); if (isFinite(keepAlive) && keepAlive > 0) { // if the keepalive is set, allow for the specified calmdown time and @@ -518,6 +524,7 @@ export function createClient(options: ClientOptions): Client { let completed = false, releaser = () => { // for handling completions before connect + locks--; completed = true; }; @@ -566,6 +573,7 @@ export function createClient(options: ClientOptions): Client { ); releaser = () => { + locks--; if (!completed && socket.readyState === WebSocketImpl.OPEN) // if not completed already and socket is open, send complete message to server on release socket.send( diff --git a/src/tests/client.ts b/src/tests/client.ts index 9ad90b14..5704fe05 100644 --- a/src/tests/client.ts +++ b/src/tests/client.ts @@ -1074,6 +1074,74 @@ describe('reconnecting', () => { // and all connections are gone expect(server.getClients().length).toBe(0); }); + + it('should not reconnect if the subscription completes while waiting for a retry', async () => { + const { url, ...server } = await startTServer(); + + let retryAttempt = () => { + /**/ + }; + const waitForRetryAttempt = () => + new Promise((resolve) => (retryAttempt = resolve)); + let retry = () => { + /**/ + }; + const client = createClient({ + url, + retryAttempts: 2, + retryWait: () => { + retryAttempt(); + return new Promise((resolve) => (retry = resolve)); + }, + }); + + // case 1 + + // subscribe and wait for operation + let sub = tsubscribe(client, { + query: 'subscription { ping }', + }); + await server.waitForOperation(); + + // close client then wait for retry attempt + await server.waitForClient((client) => { + client.close(); + }); + await waitForRetryAttempt(); + + // complete subscription while waiting + sub.dispose(); + + retry(); + + await server.waitForClient(() => { + fail("Client shouldn't have reconnected"); + }, 20); + + // case 2 + + // subscribe but close connection immediately (dont wait for operation) + sub = tsubscribe(client, { + query: 'subscription { ping }', + }); + retry(); // this still counts as a retry, so retry + await server.waitForOperation(); + + // close client then wait for retry attempt + await server.waitForClient((client) => { + client.close(); + }); + await waitForRetryAttempt(); + + // complete subscription while waiting + sub.dispose(); + + retry(); + + await server.waitForClient(() => { + fail("Client shouldn't have reconnected"); + }, 20); + }); }); describe('events', () => {