From ee414476aa839e364bce6b26ab47859be1b99307 Mon Sep 17 00:00:00 2001 From: Daria Pardue Date: Wed, 25 May 2022 13:08:55 -0400 Subject: [PATCH] feat(NODE-3750): make maxConnecting configurable (#3261) --- src/cmap/connection_pool.ts | 6 +- src/connection_string.ts | 10 +++ src/mongo_client.ts | 3 + ...kout-custom-maxConnecting-is-enforced.json | 81 +++++++++++++++++++ ...ckout-custom-maxConnecting-is-enforced.yml | 50 ++++++++++++ .../uri-options/connection-pool-options.json | 23 +++++- .../uri-options/connection-pool-options.yml | 24 +++++- test/tools/cmap_spec_runner.ts | 1 + test/tools/uri_spec_runner.ts | 1 + test/unit/mongo_client.test.js | 2 + 10 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.json create mode 100644 test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.yml diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index d34edd38a5..6ef29184c2 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -67,6 +67,8 @@ export interface ConnectionPoolOptions extends Omit { closed: boolean; - options: Readonly; + options: Readonly; /** @internal */ [kLogger]: Logger; /** @internal */ @@ -199,7 +201,7 @@ export class ConnectionPool extends TypedEventEmitter { connectionType: Connection, maxPoolSize: options.maxPoolSize ?? 100, minPoolSize: options.minPoolSize ?? 0, - maxConnecting: 2, + maxConnecting: options.maxConnecting ?? 2, maxIdleTimeMS: options.maxIdleTimeMS ?? 0, waitQueueTimeoutMS: options.waitQueueTimeoutMS ?? 0, autoEncrypter: options.autoEncrypter, diff --git a/src/connection_string.ts b/src/connection_string.ts index 261ebacb8d..5b3d494a77 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -856,6 +856,16 @@ export const OPTIONS = { return new Logger('MongoClient', { loggerLevel: value as LoggerLevel }); } }, + maxConnecting: { + default: 2, + transform({ name, values: [value] }): number { + const maxConnecting = getUint(name, value); + if (maxConnecting === 0) { + throw new MongoInvalidArgumentError('maxConnecting must be > 0 if specified'); + } + return maxConnecting; + } + }, maxIdleTimeMS: { default: 0, type: 'uint' diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 2b04f01e63..068142e2cf 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -141,6 +141,8 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC maxPoolSize?: number; /** The minimum number of connections in the connection pool. */ minPoolSize?: number; + /** The maximum number of connections that may be in the process of being established concurrently by the connection pool. */ + maxConnecting?: number; /** The maximum number of milliseconds that a connection can remain idle in the pool before being removed and closed. */ maxIdleTimeMS?: number; /** The maximum time in milliseconds that a thread can wait for a connection to become available. */ @@ -652,6 +654,7 @@ export interface MongoOptions | 'keepAliveInitialDelay' | 'localThresholdMS' | 'logger' + | 'maxConnecting' | 'maxIdleTimeMS' | 'maxPoolSize' | 'minPoolSize' diff --git a/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.json b/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.json new file mode 100644 index 0000000000..6620f82fd9 --- /dev/null +++ b/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "style": "integration", + "description": "custom maxConnecting is enforced", + "runOn": [ + { + "minServerVersion": "4.4.0" + } + ], + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "closeConnection": false, + "blockConnection": true, + "blockTimeMS": 500 + } + }, + "poolOptions": { + "maxConnecting": 1, + "maxPoolSize": 2, + "waitQueueTimeoutMS": 5000 + }, + "operations": [ + { + "name": "ready" + }, + { + "name": "start", + "target": "thread1" + }, + { + "name": "start", + "target": "thread2" + }, + { + "name": "checkOut", + "thread": "thread1" + }, + { + "name": "waitForEvent", + "event": "ConnectionCreated", + "count": 1 + }, + { + "name": "checkOut", + "thread": "thread2" + }, + { + "name": "waitForEvent", + "event": "ConnectionReady", + "count": 2 + } + ], + "events": [ + { + "type": "ConnectionCreated" + }, + { + "type": "ConnectionReady" + }, + { + "type": "ConnectionCreated" + }, + { + "type": "ConnectionReady" + } + ], + "ignore": [ + "ConnectionCheckOutStarted", + "ConnectionCheckedIn", + "ConnectionCheckedOut", + "ConnectionClosed", + "ConnectionPoolCreated", + "ConnectionPoolReady" + ] +} diff --git a/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.yml b/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.yml new file mode 100644 index 0000000000..dc8852696e --- /dev/null +++ b/test/spec/connection-monitoring-and-pooling/pool-checkout-custom-maxConnecting-is-enforced.yml @@ -0,0 +1,50 @@ +version: 1 +style: integration +description: custom maxConnecting is enforced +runOn: + - + minServerVersion: "4.4.0" +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 500 +poolOptions: + maxConnecting: 1 + # gives opportunity for the checkout in thread2 to establish a new connection, which it must not do until thread1 establishes one + maxPoolSize: 2 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # thread1 exists to consume the single permit to open a connection, + # so that thread2 would be blocked acquiring a permit, which results in ordering its ConnectionCreated event after + # the ConnectionReady event from thread1. + - name: start + target: thread1 + - name: start + target: thread2 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 1 + - name: checkOut + thread: thread2 + - name: waitForEvent + event: ConnectionReady + count: 2 +events: + - type: ConnectionCreated + - type: ConnectionReady + - type: ConnectionCreated + - type: ConnectionReady +ignore: + - ConnectionCheckOutStarted + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionClosed + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/test/spec/uri-options/connection-pool-options.json b/test/spec/uri-options/connection-pool-options.json index aae16190ba..118b2f6783 100644 --- a/test/spec/uri-options/connection-pool-options.json +++ b/test/spec/uri-options/connection-pool-options.json @@ -2,7 +2,7 @@ "tests": [ { "description": "Valid connection pool options are parsed correctly", - "uri": "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3", + "uri": "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3&maxConnecting=1", "valid": true, "warning": false, "hosts": null, @@ -10,7 +10,8 @@ "options": { "maxIdleTimeMS": 50000, "maxPoolSize": 5, - "minPoolSize": 3 + "minPoolSize": 3, + "maxConnecting": 1 } }, { @@ -52,6 +53,24 @@ "options": { "minPoolSize": 0 } + }, + { + "description": "maxConnecting=0 causes a warning", + "uri": "mongodb://example.com/?maxConnecting=0", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "maxConnecting<0 causes a warning", + "uri": "mongodb://example.com/?maxConnecting=-1", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": {} } ] } diff --git a/test/spec/uri-options/connection-pool-options.yml b/test/spec/uri-options/connection-pool-options.yml index 3d98b87563..65fbb7367c 100644 --- a/test/spec/uri-options/connection-pool-options.yml +++ b/test/spec/uri-options/connection-pool-options.yml @@ -1,7 +1,7 @@ tests: - description: "Valid connection pool options are parsed correctly" - uri: "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3" + uri: "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3&maxConnecting=1" valid: true warning: false hosts: ~ @@ -10,6 +10,7 @@ tests: maxIdleTimeMS: 50000 maxPoolSize: 5 minPoolSize: 3 + maxConnecting: 1 - description: "Non-numeric maxIdleTimeMS causes a warning" uri: "mongodb://example.com/?maxIdleTimeMS=invalid" @@ -45,4 +46,23 @@ tests: hosts: ~ auth: ~ options: - minPoolSize: 0 + minPoolSize: 0 + + - + description: "maxConnecting=0 causes a warning" + uri: "mongodb://example.com/?maxConnecting=0" + valid: true + warning: true + hosts: ~ + auth: ~ + options: {} + + - + description: "maxConnecting<0 causes a warning" + uri: "mongodb://example.com/?maxConnecting=-1" + valid: true + warning: true + hosts: ~ + auth: ~ + options: {} + \ No newline at end of file diff --git a/test/tools/cmap_spec_runner.ts b/test/tools/cmap_spec_runner.ts index 78d6178f1b..6a707a5f98 100644 --- a/test/tools/cmap_spec_runner.ts +++ b/test/tools/cmap_spec_runner.ts @@ -19,6 +19,7 @@ type CmapOperation = type CmapPoolOptions = { maxPoolSize?: number; minPoolSize?: number; + maxConnecting?: number; maxIdleTimeMS?: number; waitQueueTimeoutMS?: number; }; diff --git a/test/tools/uri_spec_runner.ts b/test/tools/uri_spec_runner.ts index e5a9342dba..f81ef86d3e 100644 --- a/test/tools/uri_spec_runner.ts +++ b/test/tools/uri_spec_runner.ts @@ -309,6 +309,7 @@ export function executeUriValidationTest( //** DIRECTLY MAPPED OPTIONS **/ case 'zlibCompressionLevel': + case 'maxConnecting': case 'maxPoolSize': case 'minPoolSize': case 'connectTimeoutMS': diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index b3e3aed23b..f36278628a 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -97,6 +97,7 @@ describe('MongoOptions', function () { localThresholdMS: 3, logger: new Logger('Testing!'), loggerLevel: 'info', + maxConnecting: 5, maxIdleTimeMS: 3, maxPoolSize: 2, maxStalenessSeconds: 3, @@ -162,6 +163,7 @@ describe('MongoOptions', function () { 'heartbeatFrequencyMS=2', 'journal=true', 'localThresholdMS=2', + 'maxConnecting=5', 'maxIdleTimeMS=2', 'maxPoolSize=4', 'maxStalenessSeconds=2',