From 7934736faedf4e7ddec1d95a0a6561a3bb3bd8ac Mon Sep 17 00:00:00 2001 From: George Payne Date: Tue, 9 Feb 2021 17:07:38 +0100 Subject: [PATCH 1/3] Keep alives - add new keepAlive parameter - allow keepAlive to be passed via connectionString --- src/Client/index.ts | 28 ++++++++++++++++++- src/Client/parseConnectionString.ts | 5 +++- .../parseConnectionString.test.ts.snap | 2 ++ .../connection/connectionStringMockups.ts | 7 +++-- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Client/index.ts b/src/Client/index.ts index f90c771d..3e62c768 100644 --- a/src/Client/index.ts +++ b/src/Client/index.ts @@ -21,6 +21,15 @@ import { discoverEndpoint } from "./discovery"; import { parseConnectionString } from "./parseConnectionString"; interface ClientOptions { + /** + * The optional amount of time to wait after which a keepalive ping is sent on the transport. (in ms) + * @default 10_000 + */ + keepAlive?: number | false; + /** + * Whether or not to immediately throw an exception when an append fails. + * @default true + */ throwOnAppendFailure?: boolean; } @@ -73,6 +82,7 @@ export class Client { #connectionSettings: ConnectionTypeOptions; #channelCredentials: ChannelCredentials; #insecure: boolean; + #keepAlive: number | false; #defaultCredentials?: Credentials; #channel?: Promise; @@ -120,6 +130,8 @@ export class Client { discoveryInterval: options.discoveryInterval, gossipTimeout: options.gossipTimeout, maxDiscoverAttempts: options.maxDiscoverAttempts, + throwOnAppendFailure: options.throwOnAppendFailure, + keepAlive: options.keepAlive, }, channelCredentials, options.defaultCredentials @@ -134,6 +146,8 @@ export class Client { discoveryInterval: options.discoveryInterval, gossipTimeout: options.gossipTimeout, maxDiscoverAttempts: options.maxDiscoverAttempts, + throwOnAppendFailure: options.throwOnAppendFailure, + keepAlive: options.keepAlive, }, channelCredentials, options.defaultCredentials @@ -143,6 +157,8 @@ export class Client { return new Client( { endpoint: options.hosts[0], + throwOnAppendFailure: options.throwOnAppendFailure, + keepAlive: options.keepAlive, }, channelCredentials, options.defaultCredentials @@ -167,12 +183,14 @@ export class Client { constructor( { throwOnAppendFailure = true, + keepAlive = 10_000, ...connectionSettings }: ConnectionTypeOptions, channelCredentials: ChannelCredentialOptions = { insecure: false }, defaultUserCredentials?: Credentials ) { this.#throwOnAppendFailure = throwOnAppendFailure; + this.#keepAlive = keepAlive; this.#connectionSettings = connectionSettings; this.#insecure = !!channelCredentials.insecure; this.#defaultCredentials = defaultUserCredentials; @@ -248,7 +266,15 @@ export class Client { uri ); - return new Channel(uri, this.#channelCredentials, {}); + return new Channel( + uri, + this.#channelCredentials, + this.#keepAlive && this.#keepAlive > 0 + ? { + "grpc.keepalive_time_ms": this.#keepAlive, + } + : {} + ); }; private resolveUri = async (): Promise => { diff --git a/src/Client/parseConnectionString.ts b/src/Client/parseConnectionString.ts index 60d4a73d..e0e6ac13 100644 --- a/src/Client/parseConnectionString.ts +++ b/src/Client/parseConnectionString.ts @@ -12,6 +12,7 @@ const lowerToKey: Record = { tls: "tls", tlsverifycert: "tlsVerifyCert", throwonappendfailure: "throwOnAppendFailure", + keepalive: "keepAlive", }; export interface ConnectionOptions { @@ -23,6 +24,7 @@ export interface ConnectionOptions { tls: boolean; tlsVerifyCert: boolean; throwOnAppendFailure: boolean; + keepAlive?: number; defaultCredentials?: Credentials; hosts: EndPoint[]; } @@ -252,7 +254,8 @@ const verifyKeyValuePair = ( } case "maxDiscoverAttempts": case "discoveryInterval": - case "gossipTimeout": { + case "gossipTimeout": + case "keepAlive": { const parsedValue = parseInt(value); if (Number.isNaN(parsedValue)) { diff --git a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap index ac297d16..186f44df 100644 --- a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap +++ b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap @@ -8,6 +8,8 @@ exports[`connection string parser Should throw on invalid strings esdb://host1;h exports[`connection string parser Should throw on invalid strings esdb://localhost/&tlsVerifyCert=false 1`] = `"Unexpected \\"&\\" at position 17, expected ?key=value."`; +exports[`connection string parser Should throw on invalid strings esdb://localhost?keepAlive=please 1`] = `"Unexpected \\"please\\" at position 27, expected Integer."`; + exports[`connection string parser Should throw on invalid strings esdb://localhost?throwOnAppendFailure=sometimes 1`] = `"Unexpected \\"sometimes\\" at position 38, expected true or false."`; exports[`connection string parser Should throw on invalid strings esdb://localhost?tlsVerifyCert=false&nodePreference=any 1`] = `"Unexpected \\"any\\" at position 52, expected follower or leader or random."`; diff --git a/src/__test__/connection/connectionStringMockups.ts b/src/__test__/connection/connectionStringMockups.ts index 36682ce3..caeeffd5 100644 --- a/src/__test__/connection/connectionStringMockups.ts +++ b/src/__test__/connection/connectionStringMockups.ts @@ -415,7 +415,7 @@ export const valid: Array< }, ], [ - "esdb://host?maxDiscoverAttempts=200&discoveryInterval=1000&gossipTimeout=1&nodePreference=leader&tls=false&tlsVerifyCert=false&throwOnAppendFailure=false", + "esdb://host?maxDiscoverAttempts=200&discoveryInterval=1000&gossipTimeout=1&nodePreference=leader&tls=false&tlsVerifyCert=false&throwOnAppendFailure=false&keepAlive=10000", { dnsDiscover: false, maxDiscoverAttempts: 200, @@ -425,6 +425,7 @@ export const valid: Array< tls: false, tlsVerifyCert: false, throwOnAppendFailure: false, + keepAlive: 10000, hosts: [ { address: "host", @@ -434,7 +435,7 @@ export const valid: Array< }, ], [ - "esdb://host?MaxDiscoverAttempts=200&discoveryinterval=1000&GOSSIPTIMEOUT=1&nOdEpReFeReNcE=leader&TLS=false&TlsVerifyCert=false&THROWOnAppendFailure=false", + "esdb://host?MaxDiscoverAttempts=200&discoveryinterval=1000&GOSSIPTIMEOUT=1&nOdEpReFeReNcE=leader&TLS=false&TlsVerifyCert=false&THROWOnAppendFailure=false&KEEPALIVE=200", { dnsDiscover: false, maxDiscoverAttempts: 200, @@ -444,6 +445,7 @@ export const valid: Array< tls: false, tlsVerifyCert: false, throwOnAppendFailure: false, + keepAlive: 200, hosts: [ { address: "host", @@ -467,6 +469,7 @@ export const invalid: string[] = [ "esdb://localhost?tlsVerifyCert=false&nodePreference=any", "esdb://localhost?tlsVerifyCert=if you feel like it", "esdb://localhost?throwOnAppendFailure=sometimes", + "esdb://localhost?keepAlive=please", ]; export const warning: Array< From c37dcd52eb4cca59de32b63dad559508076047bf Mon Sep 17 00:00:00 2001 From: George Payne Date: Thu, 11 Feb 2021 10:53:38 +0100 Subject: [PATCH 2/3] Update keepalive settings to agreed naming / behaviour --- src/Client/index.ts | 59 ++++++++++----- src/Client/parseConnectionString.ts | 12 +++- .../parseConnectionString.test.ts.snap | 2 +- .../connection/connectionStringMockups.ts | 71 +++++++++++++++++-- 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/src/Client/index.ts b/src/Client/index.ts index 3e62c768..8e66a300 100644 --- a/src/Client/index.ts +++ b/src/Client/index.ts @@ -22,10 +22,17 @@ import { parseConnectionString } from "./parseConnectionString"; interface ClientOptions { /** - * The optional amount of time to wait after which a keepalive ping is sent on the transport. (in ms) + * The amount of time (in milliseconds) to wait after which a keepalive ping is sent on the transport. + * Use -1 to disable. * @default 10_000 */ - keepAlive?: number | false; + keepAliveInterval?: number; + /** + * The amount of time (in milliseconds) the sender of the keepalive ping waits for an acknowledgement. + * If it does not receive an acknowledgment within this time, it will close the connection. + * @default 10_000 + */ + keepAliveTimeout?: number; /** * Whether or not to immediately throw an exception when an append fails. * @default true @@ -82,7 +89,9 @@ export class Client { #connectionSettings: ConnectionTypeOptions; #channelCredentials: ChannelCredentials; #insecure: boolean; - #keepAlive: number | false; + #keepAliveInterval: number; + #keepAliveTimeout: number; + #defaultCredentials?: Credentials; #channel?: Promise; @@ -131,7 +140,8 @@ export class Client { gossipTimeout: options.gossipTimeout, maxDiscoverAttempts: options.maxDiscoverAttempts, throwOnAppendFailure: options.throwOnAppendFailure, - keepAlive: options.keepAlive, + keepAliveInterval: options.keepAliveInterval, + keepAliveTimeout: options.keepAliveTimeout, }, channelCredentials, options.defaultCredentials @@ -147,7 +157,8 @@ export class Client { gossipTimeout: options.gossipTimeout, maxDiscoverAttempts: options.maxDiscoverAttempts, throwOnAppendFailure: options.throwOnAppendFailure, - keepAlive: options.keepAlive, + keepAliveInterval: options.keepAliveInterval, + keepAliveTimeout: options.keepAliveTimeout, }, channelCredentials, options.defaultCredentials @@ -158,7 +169,8 @@ export class Client { { endpoint: options.hosts[0], throwOnAppendFailure: options.throwOnAppendFailure, - keepAlive: options.keepAlive, + keepAliveInterval: options.keepAliveInterval, + keepAliveTimeout: options.keepAliveTimeout, }, channelCredentials, options.defaultCredentials @@ -183,14 +195,28 @@ export class Client { constructor( { throwOnAppendFailure = true, - keepAlive = 10_000, + keepAliveInterval = 10_000, + keepAliveTimeout = 10_000, ...connectionSettings }: ConnectionTypeOptions, channelCredentials: ChannelCredentialOptions = { insecure: false }, defaultUserCredentials?: Credentials ) { + if (keepAliveInterval < -1) { + throw new Error( + `Invalid keepAliveInterval "${keepAliveInterval}". Please provide a positive integer, or -1 to disable.` + ); + } + + if (keepAliveTimeout < -1) { + throw new Error( + `Invalid keepAliveTimeout "${keepAliveTimeout}". Please provide a positive integer, or -1 to disable.` + ); + } + this.#throwOnAppendFailure = throwOnAppendFailure; - this.#keepAlive = keepAlive; + this.#keepAliveInterval = keepAliveInterval; + this.#keepAliveTimeout = keepAliveTimeout; this.#connectionSettings = connectionSettings; this.#insecure = !!channelCredentials.insecure; this.#defaultCredentials = defaultUserCredentials; @@ -266,15 +292,14 @@ export class Client { uri ); - return new Channel( - uri, - this.#channelCredentials, - this.#keepAlive && this.#keepAlive > 0 - ? { - "grpc.keepalive_time_ms": this.#keepAlive, - } - : {} - ); + return new Channel(uri, this.#channelCredentials, { + "grpc.keepalive_time_ms": + this.#keepAliveInterval < 0 + ? Number.MAX_VALUE + : this.#keepAliveInterval, + "grpc.keepalive_timeout_ms": + this.#keepAliveTimeout < 0 ? Number.MAX_VALUE : this.#keepAliveTimeout, + }); }; private resolveUri = async (): Promise => { diff --git a/src/Client/parseConnectionString.ts b/src/Client/parseConnectionString.ts index e0e6ac13..1ffc9301 100644 --- a/src/Client/parseConnectionString.ts +++ b/src/Client/parseConnectionString.ts @@ -12,7 +12,8 @@ const lowerToKey: Record = { tls: "tls", tlsverifycert: "tlsVerifyCert", throwonappendfailure: "throwOnAppendFailure", - keepalive: "keepAlive", + keepaliveinterval: "keepAliveInterval", + keepalivetimeout: "keepAliveTimeout", }; export interface ConnectionOptions { @@ -24,7 +25,9 @@ export interface ConnectionOptions { tls: boolean; tlsVerifyCert: boolean; throwOnAppendFailure: boolean; - keepAlive?: number; + keepAliveInterval: number; + keepAliveTimeout: number; + defaultCredentials?: Credentials; hosts: EndPoint[]; } @@ -37,6 +40,8 @@ const defaultConnectionOptions: ConnectionOptions = { nodePreference: "random", tls: true, tlsVerifyCert: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, throwOnAppendFailure: true, hosts: [], }; @@ -255,7 +260,8 @@ const verifyKeyValuePair = ( case "maxDiscoverAttempts": case "discoveryInterval": case "gossipTimeout": - case "keepAlive": { + case "keepAliveInterval": + case "keepAliveTimeout": { const parsedValue = parseInt(value); if (Number.isNaN(parsedValue)) { diff --git a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap index 186f44df..79a3b4d7 100644 --- a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap +++ b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap @@ -8,7 +8,7 @@ exports[`connection string parser Should throw on invalid strings esdb://host1;h exports[`connection string parser Should throw on invalid strings esdb://localhost/&tlsVerifyCert=false 1`] = `"Unexpected \\"&\\" at position 17, expected ?key=value."`; -exports[`connection string parser Should throw on invalid strings esdb://localhost?keepAlive=please 1`] = `"Unexpected \\"please\\" at position 27, expected Integer."`; +exports[`connection string parser Should throw on invalid strings esdb://localhost?keepAliveTimeout=please 1`] = `"Unexpected \\"please\\" at position 34, expected Integer."`; exports[`connection string parser Should throw on invalid strings esdb://localhost?throwOnAppendFailure=sometimes 1`] = `"Unexpected \\"sometimes\\" at position 38, expected true or false."`; diff --git a/src/__test__/connection/connectionStringMockups.ts b/src/__test__/connection/connectionStringMockups.ts index caeeffd5..637da498 100644 --- a/src/__test__/connection/connectionStringMockups.ts +++ b/src/__test__/connection/connectionStringMockups.ts @@ -14,6 +14,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "localhost", @@ -33,6 +35,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "localhost", @@ -52,6 +56,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -75,6 +81,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -98,6 +106,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -121,6 +131,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -144,6 +156,8 @@ export const valid: Array< tls: false, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -167,6 +181,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "host1", @@ -194,6 +210,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "host1", @@ -221,6 +239,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "bubaqp2rh41uf5akmj0g-0.mesdb.eventstore.cloud", @@ -248,6 +268,8 @@ export const valid: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -279,6 +301,8 @@ export const valid: Array< tls: false, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "host1", @@ -306,6 +330,8 @@ export const valid: Array< tls: false, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "127.0.0.1", @@ -325,6 +351,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "host1", @@ -352,6 +380,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -364,6 +394,27 @@ export const valid: Array< ], }, ], + [ + "esdb://host?keepAliveInterval=-1&keepAliveTimeout=-1", + { + dnsDiscover: false, + maxDiscoverAttempts: 3, + discoveryInterval: 500, + gossipTimeout: 3000, + nodePreference: "random", + tls: true, + tlsVerifyCert: true, + throwOnAppendFailure: true, + keepAliveInterval: -1, + keepAliveTimeout: -1, + hosts: [ + { + address: "host", + port: 2113, + }, + ], + }, + ], [ "esdb://my%3Agreat%40username:UyeXx8%24%5EPsOo4jG88FlCauR1Coz25q@host?nodePreference=follower&tlsVerifyCert=false", { @@ -375,6 +426,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "my:great@username", password: "UyeXx8$^PsOo4jG88FlCauR1Coz25q", @@ -398,6 +451,8 @@ export const valid: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, defaultCredentials: { username: "user", password: "pass", @@ -415,7 +470,7 @@ export const valid: Array< }, ], [ - "esdb://host?maxDiscoverAttempts=200&discoveryInterval=1000&gossipTimeout=1&nodePreference=leader&tls=false&tlsVerifyCert=false&throwOnAppendFailure=false&keepAlive=10000", + "esdb://host?maxDiscoverAttempts=200&discoveryInterval=1000&gossipTimeout=1&nodePreference=leader&tls=false&tlsVerifyCert=false&throwOnAppendFailure=false&keepAliveInterval=10", { dnsDiscover: false, maxDiscoverAttempts: 200, @@ -425,7 +480,8 @@ export const valid: Array< tls: false, tlsVerifyCert: false, throwOnAppendFailure: false, - keepAlive: 10000, + keepAliveInterval: 10, + keepAliveTimeout: 10_000, hosts: [ { address: "host", @@ -435,7 +491,7 @@ export const valid: Array< }, ], [ - "esdb://host?MaxDiscoverAttempts=200&discoveryinterval=1000&GOSSIPTIMEOUT=1&nOdEpReFeReNcE=leader&TLS=false&TlsVerifyCert=false&THROWOnAppendFailure=false&KEEPALIVE=200", + "esdb://host?MaxDiscoverAttempts=200&discoveryinterval=1000&GOSSIPTIMEOUT=1&nOdEpReFeReNcE=leader&TLS=false&TlsVerifyCert=false&THROWOnAppendFailure=false&KEEPALIVEinterval=200", { dnsDiscover: false, maxDiscoverAttempts: 200, @@ -445,7 +501,8 @@ export const valid: Array< tls: false, tlsVerifyCert: false, throwOnAppendFailure: false, - keepAlive: 200, + keepAliveInterval: 200, + keepAliveTimeout: 10_000, hosts: [ { address: "host", @@ -469,7 +526,7 @@ export const invalid: string[] = [ "esdb://localhost?tlsVerifyCert=false&nodePreference=any", "esdb://localhost?tlsVerifyCert=if you feel like it", "esdb://localhost?throwOnAppendFailure=sometimes", - "esdb://localhost?keepAlive=please", + "esdb://localhost?keepAliveTimeout=please", ]; export const warning: Array< @@ -486,6 +543,8 @@ export const warning: Array< tls: true, tlsVerifyCert: false, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "localhost", @@ -505,6 +564,8 @@ export const warning: Array< tls: true, tlsVerifyCert: true, throwOnAppendFailure: true, + keepAliveInterval: 10_000, + keepAliveTimeout: 10_000, hosts: [ { address: "localhost", From 63a53f07f722b213d42e975d579930a486943a73 Mon Sep 17 00:00:00 2001 From: George Payne Date: Mon, 15 Feb 2021 13:28:40 +0100 Subject: [PATCH 3/3] Add warning for interval value less than 10_000ms - add test cases for keepAlive various settings - add invalid test case for keepAliveInterval --- src/Client/index.ts | 8 +- .../__snapshots__/keepAlive.test.ts.snap | 89 +++++++++ .../parseConnectionString.test.ts.snap | 2 + .../connection/connectionStringMockups.ts | 1 + src/__test__/connection/keepAlive.test.ts | 173 ++++++++++++++++++ 5 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 src/__test__/connection/__snapshots__/keepAlive.test.ts.snap create mode 100644 src/__test__/connection/keepAlive.test.ts diff --git a/src/Client/index.ts b/src/Client/index.ts index 8e66a300..5bf30363 100644 --- a/src/Client/index.ts +++ b/src/Client/index.ts @@ -106,7 +106,7 @@ export class Client { */ static connectionString( connectionString: TemplateStringsArray | string, - ...parts: string[] + ...parts: Array ): Client { const string: string = Array.isArray(connectionString) ? connectionString.reduce( @@ -214,6 +214,12 @@ export class Client { ); } + if (keepAliveInterval > -1 && keepAliveInterval < 10_000) { + console.warn( + `Specified KeepAliveInterval of ${keepAliveInterval} is less than recommended 10_000 ms.` + ); + } + this.#throwOnAppendFailure = throwOnAppendFailure; this.#keepAliveInterval = keepAliveInterval; this.#keepAliveTimeout = keepAliveTimeout; diff --git a/src/__test__/connection/__snapshots__/keepAlive.test.ts.snap b/src/__test__/connection/__snapshots__/keepAlive.test.ts.snap new file mode 100644 index 00000000..a4bbaa53 --- /dev/null +++ b/src/__test__/connection/__snapshots__/keepAlive.test.ts.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`keepAlive settings should throw if < -1 keepAliveInterval connectionString 1`] = `"Invalid keepAliveInterval \\"-2\\". Please provide a positive integer, or -1 to disable."`; + +exports[`keepAlive settings should throw if < -1 keepAliveInterval constructor 1`] = `"Invalid keepAliveInterval \\"-2\\". Please provide a positive integer, or -1 to disable."`; + +exports[`keepAlive settings should throw if < -1 keepAliveTimeout connectionString 1`] = `"Invalid keepAliveTimeout \\"-100\\". Please provide a positive integer, or -1 to disable."`; + +exports[`keepAlive settings should throw if < -1 keepAliveTimeout constructor 1`] = `"Invalid keepAliveTimeout \\"-100\\". Please provide a positive integer, or -1 to disable."`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) connectionString 1`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 0 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) connectionString 2`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 1 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) connectionString 3`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 10 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) connectionString 4`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 1000 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) connectionString 5`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 9999 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) constructor 1`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 0 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) constructor 2`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 1 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) constructor 3`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 10 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) constructor 4`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 1000 is less than recommended 10_000 ms.", + ], +] +`; + +exports[`keepAlive settings should warn if keepAliveInterval is less than 10_000ms (but more than -1) constructor 5`] = ` +Array [ + Array [ + "Specified KeepAliveInterval of 9999 is less than recommended 10_000 ms.", + ], +] +`; diff --git a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap index 79a3b4d7..32f29830 100644 --- a/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap +++ b/src/__test__/connection/__snapshots__/parseConnectionString.test.ts.snap @@ -8,6 +8,8 @@ exports[`connection string parser Should throw on invalid strings esdb://host1;h exports[`connection string parser Should throw on invalid strings esdb://localhost/&tlsVerifyCert=false 1`] = `"Unexpected \\"&\\" at position 17, expected ?key=value."`; +exports[`connection string parser Should throw on invalid strings esdb://localhost?keepAliveInterval=XXIV 1`] = `"Unexpected \\"XXIV\\" at position 35, expected Integer."`; + exports[`connection string parser Should throw on invalid strings esdb://localhost?keepAliveTimeout=please 1`] = `"Unexpected \\"please\\" at position 34, expected Integer."`; exports[`connection string parser Should throw on invalid strings esdb://localhost?throwOnAppendFailure=sometimes 1`] = `"Unexpected \\"sometimes\\" at position 38, expected true or false."`; diff --git a/src/__test__/connection/connectionStringMockups.ts b/src/__test__/connection/connectionStringMockups.ts index 637da498..6b885073 100644 --- a/src/__test__/connection/connectionStringMockups.ts +++ b/src/__test__/connection/connectionStringMockups.ts @@ -527,6 +527,7 @@ export const invalid: string[] = [ "esdb://localhost?tlsVerifyCert=if you feel like it", "esdb://localhost?throwOnAppendFailure=sometimes", "esdb://localhost?keepAliveTimeout=please", + "esdb://localhost?keepAliveInterval=XXIV", ]; export const warning: Array< diff --git a/src/__test__/connection/keepAlive.test.ts b/src/__test__/connection/keepAlive.test.ts new file mode 100644 index 00000000..18913572 --- /dev/null +++ b/src/__test__/connection/keepAlive.test.ts @@ -0,0 +1,173 @@ +import { Channel } from "@grpc/grpc-js"; + +import { EventStoreDBClient } from "../.."; + +jest.mock("@grpc/grpc-js/build/src/channel.js"); +const ChannelMock = Channel as jest.Mock; + +describe("keepAlive settings", () => { + const endpoint = "host:1234"; + + beforeEach(() => { + ChannelMock.mockClear(); + }); + + describe("should both default to 10_000", () => { + describe.each([ + [ + "keepAliveInterval should default to 10_000", + "esdb://host", + {}, + { + "grpc.keepalive_time_ms": 10000, + }, + ], + [ + "keepAliveTimeout should default to 10_000", + "esdb://host", + {}, + { + "grpc.keepalive_timeout_ms": 10000, + }, + ], + [ + "keepAliveInterval should be settable (leaving timeout as default)", + "esdb://host?keepAliveInterval=123456", + { keepAliveInterval: 123456 }, + { + "grpc.keepalive_time_ms": 123456, + "grpc.keepalive_timeout_ms": 10000, + }, + ], + [ + "keepAliveTimeout should be settable (leaving interval as default)", + "esdb://host?keepAliveTimeout=246810", + { keepAliveTimeout: 246810 }, + { + "grpc.keepalive_time_ms": 10000, + "grpc.keepalive_timeout_ms": 246810, + }, + ], + [ + "both should be settable", + "esdb://host?keepAliveInterval=9987654&keepAliveTimeout=124816", + { keepAliveInterval: 9987654, keepAliveTimeout: 124816 }, + { + "grpc.keepalive_time_ms": 9987654, + "grpc.keepalive_timeout_ms": 124816, + }, + ], + [ + "-1 should disable (max_int) keepAliveInterval (leaving timeout as default)", + "esdb://host?keepAliveInterval=-1", + { keepAliveInterval: -1 }, + { + "grpc.keepalive_time_ms": Number.MAX_VALUE, + "grpc.keepalive_timeout_ms": 10000, + }, + ], + [ + "-1 should disable (max_int) keepAliveTimeout (leaving interval as default)", + "esdb://host?keepAliveTimeout=-1", + { keepAliveTimeout: -1 }, + { + "grpc.keepalive_time_ms": 10000, + "grpc.keepalive_timeout_ms": Number.MAX_VALUE, + }, + ], + [ + "-1 should disable (max_int) both", + "esdb://host?keepAliveTimeout=-1&keepAliveInterval=-1", + { keepAliveTimeout: -1, keepAliveInterval: -1 }, + { + "grpc.keepalive_time_ms": Number.MAX_VALUE, + "grpc.keepalive_timeout_ms": Number.MAX_VALUE, + }, + ], + [ + "0 is fine (but a terrible choice)", + "esdb://host?keepAliveTimeout=0&keepAliveInterval=0", + { keepAliveTimeout: 0, keepAliveInterval: 0 }, + { + "grpc.keepalive_time_ms": 0, + "grpc.keepalive_timeout_ms": 0, + }, + ], + ])("%s", (_, connectionString, constructorOptions, expected) => { + test.each([ + [ + "connectionString", + () => EventStoreDBClient.connectionString(connectionString), + ], + [ + "constructor", + () => + new EventStoreDBClient({ + endpoint, + ...constructorOptions, + }), + ], + ])("%s", async (_, createClient) => { + const warnSpy = jest.spyOn(console, "warn").mockImplementation(); + const client = createClient(); + + try { + await client.restartSubsystem(); + } catch (_) { + // We're not actually connecting to anything, just triggering channel creation + } + + expect(ChannelMock.mock.calls).toHaveLength(1); + const [[, , options]] = ChannelMock.mock.calls; + expect(options).toMatchObject(expected); + warnSpy.mockRestore(); + }); + }); + }); + + describe("should throw if < -1", () => { + describe.each` + option | value + ${"keepAliveTimeout"} | ${-100} + ${"keepAliveInterval"} | ${-2} + `("$option", ({ option, value }) => { + test.each([ + [ + "connectionString", + (option: string, value: number) => + EventStoreDBClient.connectionString`esdb://host?${option}=${value}`, + ], + [ + "constructor", + (option: string, value: number) => + new EventStoreDBClient({ endpoint, [option]: value }), + ], + ])("%s", (_, testCase) => { + expect(() => testCase(option, value)).toThrowErrorMatchingSnapshot(); + }); + }); + }); + + describe("should warn if keepAliveInterval is less than 10_000ms (but more than -1)", () => { + test.each([ + [ + "connectionString", + (value: number) => + EventStoreDBClient.connectionString`esdb://host?keepAliveInterval=${value}`, + ], + [ + "constructor", + (value: number) => + new EventStoreDBClient({ endpoint, keepAliveInterval: value }), + ], + ])("%s", (_, testCase) => { + for (const keepAliveInterval of [0, 1, 10, 1000, 9999]) { + const warnSpy = jest.spyOn(console, "warn").mockImplementation(); + testCase(keepAliveInterval); + expect(warnSpy).toHaveBeenCalled(); + expect(warnSpy.mock.calls).toMatchSnapshot(); + warnSpy.mockRestore(); + } + }); + }); +});