From 36fa752b24db04a58bb7a1beb36db0c6356791ef Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 11 Mar 2024 13:35:42 -0400 Subject: [PATCH] refactor(NODE-5915): topology close logic to be synchronous (#4021) --- src/cmap/connect.ts | 2 +- src/cmap/connection.ts | 12 +-- src/cmap/connection_pool.ts | 44 ++++------- src/index.ts | 1 - src/mongo_client.ts | 21 ++--- src/sdam/monitor.ts | 14 ++-- src/sdam/server.ts | 25 ++---- src/sdam/topology.ts | 77 ++++++++----------- src/utils.ts | 40 ---------- .../rtt_pinger.test.ts | 4 +- test/tools/cmap_spec_runner.ts | 25 ++---- ...records_for_mongos_discovery.prose.test.ts | 3 +- .../assorted/server_selection_spec_helper.js | 3 +- test/unit/cmap/connect.test.ts | 2 +- test/unit/error.test.ts | 10 ++- test/unit/sdam/monitor.test.ts | 3 +- test/unit/sdam/topology.test.js | 39 +++++----- test/unit/utils.test.ts | 32 -------- 18 files changed, 108 insertions(+), 249 deletions(-) diff --git a/src/cmap/connect.ts b/src/cmap/connect.ts index 24022c9d18..1b46926c49 100644 --- a/src/cmap/connect.ts +++ b/src/cmap/connect.ts @@ -43,7 +43,7 @@ export async function connect(options: ConnectionOptions): Promise { await performInitialHandshake(connection, options); return connection; } catch (error) { - connection?.destroy({ force: false }); + connection?.destroy(); throw error; } } diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 7ea71727e2..bb84c1005e 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -126,12 +126,6 @@ export interface ConnectionOptions mongoLogger?: MongoLogger | undefined; } -/** @internal */ -export interface DestroyOptions { - /** Force the destruction. */ - force: boolean; -} - /** @public */ export type ConnectionEvents = { commandStarted(event: CommandStartedEvent): void; @@ -301,14 +295,10 @@ export class Connection extends TypedEventEmitter { }, 1).unref(); // No need for this timer to hold the event loop open } - public destroy(options: DestroyOptions, callback?: Callback): void { + public destroy(): void { if (this.closed) { - if (typeof callback === 'function') process.nextTick(callback); return; } - if (typeof callback === 'function') { - this.once('close', () => process.nextTick(() => callback())); - } // load balanced mode requires that these listeners remain on the connection // after cleanup on timeouts, errors or close so we remove them before calling diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index 64b89ee120..195ad4dc9c 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -28,7 +28,6 @@ import { CancellationToken, TypedEventEmitter } from '../mongo_types'; import type { Server } from '../sdam/server'; import { type Callback, - eachAsync, List, makeCounter, promiseWithResolvers, @@ -493,25 +492,16 @@ export class ConnectionPool extends TypedEventEmitter { private interruptInUseConnections(minGeneration: number) { for (const connection of this[kCheckedOut]) { if (connection.generation <= minGeneration) { - this.checkIn(connection); connection.onError(new PoolClearedOnNetworkError(this)); + this.checkIn(connection); } } } /** Close the pool */ - close(callback: Callback): void; - close(options: CloseOptions, callback: Callback): void; - close(_options?: CloseOptions | Callback, _cb?: Callback): void { - let options = _options as CloseOptions; - const callback = (_cb ?? _options) as Callback; - if (typeof options === 'function') { - options = {}; - } - - options = Object.assign({ force: false }, options); + close(): void { if (this.closed) { - return callback(); + return; } // immediately cancel any in-flight connections @@ -526,21 +516,15 @@ export class ConnectionPool extends TypedEventEmitter { this.clearMinPoolSizeTimer(); this.processWaitQueue(); - eachAsync( - this[kConnections].toArray(), - (conn, cb) => { - this.emitAndLog( - ConnectionPool.CONNECTION_CLOSED, - new ConnectionClosedEvent(this, conn, 'poolClosed') - ); - conn.destroy({ force: !!options.force }, cb); - }, - err => { - this[kConnections].clear(); - this.emitAndLog(ConnectionPool.CONNECTION_POOL_CLOSED, new ConnectionPoolClosedEvent(this)); - callback(err); - } - ); + for (const conn of this[kConnections]) { + this.emitAndLog( + ConnectionPool.CONNECTION_CLOSED, + new ConnectionClosedEvent(this, conn, 'poolClosed') + ); + conn.destroy(); + } + this[kConnections].clear(); + this.emitAndLog(ConnectionPool.CONNECTION_POOL_CLOSED, new ConnectionPoolClosedEvent(this)); } /** @@ -592,7 +576,7 @@ export class ConnectionPool extends TypedEventEmitter { new ConnectionClosedEvent(this, connection, reason) ); // destroy the connection - process.nextTick(() => connection.destroy({ force: false })); + connection.destroy(); } private connectionIsStale(connection: Connection) { @@ -648,7 +632,7 @@ export class ConnectionPool extends TypedEventEmitter { // The pool might have closed since we started trying to create a connection if (this[kPoolState] !== PoolState.ready) { this[kPending]--; - connection.destroy({ force: true }); + connection.destroy(); callback(this.closed ? new PoolClosedError(this) : new PoolClearedError(this)); return; } diff --git a/src/index.ts b/src/index.ts index aae568dd79..fa88e4638b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -275,7 +275,6 @@ export type { Connection, ConnectionEvents, ConnectionOptions, - DestroyOptions, ProxyOptions } from './cmap/connection'; export type { diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 5c1d6e9cf4..f47558e443 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -552,7 +552,7 @@ export class MongoClient extends TypedEventEmitter { try { await promisify(callback => this.topology?.connect(options, callback))(); } catch (error) { - this.topology?.close({ force: true }); + this.topology?.close(); throw error; } }; @@ -614,19 +614,12 @@ export class MongoClient extends TypedEventEmitter { const topology = this.topology; this.topology = undefined; - await new Promise((resolve, reject) => { - topology.close({ force }, error => { - if (error) return reject(error); - const { encrypter } = this[kOptions]; - if (encrypter) { - return encrypter.closeCallback(this, force, error => { - if (error) return reject(error); - resolve(); - }); - } - resolve(); - }); - }); + topology.close(); + + const { encrypter } = this[kOptions]; + if (encrypter) { + await encrypter.close(this, force); + } } /** diff --git a/src/sdam/monitor.ts b/src/sdam/monitor.ts index 6accf8cd3c..cdd0a5110f 100644 --- a/src/sdam/monitor.ts +++ b/src/sdam/monitor.ts @@ -214,7 +214,7 @@ function resetMonitorState(monitor: Monitor) { monitor[kCancellationToken].emit('cancel'); - monitor.connection?.destroy({ force: true }); + monitor.connection?.destroy(); monitor.connection = null; } @@ -247,7 +247,7 @@ function checkServer(monitor: Monitor, callback: Callback) { ); function onHeartbeatFailed(err: Error) { - monitor.connection?.destroy({ force: true }); + monitor.connection?.destroy(); monitor.connection = null; monitor.emitAndLogHeartbeat( @@ -366,13 +366,13 @@ function checkServer(monitor: Monitor, callback: Callback) { await performInitialHandshake(connection, monitor.connectOptions); return connection; } catch (error) { - connection.destroy({ force: false }); + connection.destroy(); throw error; } })().then( connection => { if (isInCloseState(monitor)) { - connection.destroy({ force: true }); + connection.destroy(); return; } @@ -479,7 +479,7 @@ export class RTTPinger { this.closed = true; clearTimeout(this[kMonitorId]); - this.connection?.destroy({ force: true }); + this.connection?.destroy(); this.connection = undefined; } } @@ -495,7 +495,7 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) { function measureAndReschedule(conn?: Connection) { if (rttPinger.closed) { - conn?.destroy({ force: true }); + conn?.destroy(); return; } @@ -529,7 +529,7 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) { connection.command(ns('admin.$cmd'), { [commandName]: 1 }, undefined).then( () => measureAndReschedule(), () => { - rttPinger.connection?.destroy({ force: true }); + rttPinger.connection?.destroy(); rttPinger.connection = undefined; rttPinger[kRoundTripTime] = 0; return; diff --git a/src/sdam/server.ts b/src/sdam/server.ts index 01b8bb3219..8d552df360 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -1,6 +1,6 @@ import type { Document } from '../bson'; import { type AutoEncrypter } from '../client-side-encryption/auto_encrypter'; -import { type CommandOptions, Connection, type DestroyOptions } from '../cmap/connection'; +import { type CommandOptions, Connection } from '../cmap/connection'; import { ConnectionPool, type ConnectionPoolEvents, @@ -41,7 +41,6 @@ import type { GetMoreOptions } from '../operations/get_more'; import type { ClientSession } from '../sessions'; import { isTransactionCommand } from '../transactions'; import { - type Callback, type EventEmitterWithState, makeStateMachine, maxWireVersion, @@ -236,18 +235,8 @@ export class Server extends TypedEventEmitter { } /** Destroy the server connection */ - destroy(options?: DestroyOptions, callback?: Callback): void { - if (typeof options === 'function') { - callback = options; - options = { force: false }; - } - options = Object.assign({}, { force: false }, options); - + destroy(): void { if (this.s.state === STATE_CLOSED) { - if (typeof callback === 'function') { - callback(); - } - return; } @@ -257,13 +246,9 @@ export class Server extends TypedEventEmitter { this.monitor?.close(); } - this.pool.close(options, err => { - stateTransition(this, STATE_CLOSED); - this.emit('closed'); - if (typeof callback === 'function') { - callback(err); - } - }); + this.pool.close(); + stateTransition(this, STATE_CLOSED); + this.emit('closed'); } /** diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 68d8565738..92364748ea 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -2,8 +2,8 @@ import { promisify } from 'util'; import type { BSONSerializeOptions, Document } from '../bson'; import type { MongoCredentials } from '../cmap/auth/mongo_credentials'; -import type { ConnectionEvents, DestroyOptions } from '../cmap/connection'; -import type { CloseOptions, ConnectionPoolEvents } from '../cmap/connection_pool'; +import type { ConnectionEvents } from '../cmap/connection'; +import type { ConnectionPoolEvents } from '../cmap/connection_pool'; import type { ClientMetadata } from '../cmap/handshake/client_metadata'; import { DEFAULT_OPTIONS, FEATURE_FLAGS } from '../connection_string'; import { @@ -469,7 +469,8 @@ export class Topology extends TypedEventEmitter { selectServerOptions, (err, server) => { if (err) { - return this.close({ force: false }, () => exitWithError(err)); + this.close(); + return exitWithError(err); } const skipPingOnConnect = this.s.options[Symbol.for('@@mdb.skipPingOnConnect')] === true; @@ -495,41 +496,33 @@ export class Topology extends TypedEventEmitter { } /** Close this topology */ - close(options: CloseOptions): void; - close(options: CloseOptions, callback: Callback): void; - close(options?: CloseOptions, callback?: Callback): void { - options = options ?? { force: false }; - + close(): void { if (this.s.state === STATE_CLOSED || this.s.state === STATE_CLOSING) { - return callback?.(); + return; } - const destroyedServers = Array.from(this.s.servers.values(), server => { - return promisify(destroyServer)(server, this, { force: !!options?.force }); - }); + for (const server of this.s.servers.values()) { + destroyServer(server, this); + } - Promise.all(destroyedServers) - .then(() => { - this.s.servers.clear(); + this.s.servers.clear(); - stateTransition(this, STATE_CLOSING); + stateTransition(this, STATE_CLOSING); - drainWaitQueue(this[kWaitQueue], new MongoTopologyClosedError()); - drainTimerQueue(this.s.connectionTimers); + drainWaitQueue(this[kWaitQueue], new MongoTopologyClosedError()); + drainTimerQueue(this.s.connectionTimers); - if (this.s.srvPoller) { - this.s.srvPoller.stop(); - this.s.srvPoller.removeListener(SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords); - } + if (this.s.srvPoller) { + this.s.srvPoller.stop(); + this.s.srvPoller.removeListener(SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords); + } - this.removeListener(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology); + this.removeListener(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology); - stateTransition(this, STATE_CLOSED); + stateTransition(this, STATE_CLOSED); - // emit an event for close - this.emitAndLog(Topology.TOPOLOGY_CLOSED, new TopologyClosedEvent(this.s.id)); - }) - .finally(() => callback?.()); + // emit an event for close + this.emitAndLog(Topology.TOPOLOGY_CLOSED, new TopologyClosedEvent(this.s.id)); } /** @@ -773,30 +766,20 @@ export class Topology extends TypedEventEmitter { } /** Destroys a server, and removes all event listeners from the instance */ -function destroyServer( - server: Server, - topology: Topology, - options?: DestroyOptions, - callback?: Callback -) { - options = options ?? { force: false }; +function destroyServer(server: Server, topology: Topology) { for (const event of LOCAL_SERVER_EVENTS) { server.removeAllListeners(event); } - server.destroy(options, () => { - topology.emitAndLog( - Topology.SERVER_CLOSED, - new ServerClosedEvent(topology.s.id, server.description.address) - ); + server.destroy(); + topology.emitAndLog( + Topology.SERVER_CLOSED, + new ServerClosedEvent(topology.s.id, server.description.address) + ); - for (const event of SERVER_RELAY_EVENTS) { - server.removeAllListeners(event); - } - if (typeof callback === 'function') { - callback(); - } - }); + for (const event of SERVER_RELAY_EVENTS) { + server.removeAllListeners(event); + } } /** Predicts the TopologyType from options */ diff --git a/src/utils.ts b/src/utils.ts index 4a6f7a4e8c..98e3950fcb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -386,46 +386,6 @@ export function maxWireVersion(topologyOrServer?: Connection | Topology | Server return 0; } -/** - * Applies the function `eachFn` to each item in `arr`, in parallel. - * @internal - * - * @param arr - An array of items to asynchronously iterate over - * @param eachFn - A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete. - * @param callback - The callback called after every item has been iterated - */ -export function eachAsync( - arr: T[], - eachFn: (item: T, callback: (err?: AnyError) => void) => void, - callback: Callback -): void { - arr = arr || []; - - let idx = 0; - let awaiting = 0; - for (idx = 0; idx < arr.length; ++idx) { - awaiting++; - eachFn(arr[idx], eachCallback); - } - - if (awaiting === 0) { - callback(); - return; - } - - function eachCallback(err?: AnyError) { - awaiting--; - if (err) { - callback(err); - return; - } - - if (idx === arr.length && awaiting <= 0) { - callback(); - } - } -} - /** @internal */ export function arrayStrictEqual(arr: unknown[], arr2: unknown[]): boolean { if (!Array.isArray(arr) || !Array.isArray(arr2)) { diff --git a/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts b/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts index b171485a17..64a07c39e2 100644 --- a/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts +++ b/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts @@ -159,7 +159,7 @@ describe('class RTTPinger', () => { await client?.close(); }); - it('destroys the connection with force=true', async function () { + it('destroys the connection', async function () { await client.connect(); const rttPingers = await getRTTPingers(client); @@ -172,7 +172,7 @@ describe('class RTTPinger', () => { expect(spies).to.have.lengthOf.at.least(1); for (const spy of spies) { - expect(spy).to.have.been.calledWithExactly({ force: true }); + expect(spy).to.have.been.calledOnce; } }); }); diff --git a/test/tools/cmap_spec_runner.ts b/test/tools/cmap_spec_runner.ts index a497e6e192..f6d7e68bed 100644 --- a/test/tools/cmap_spec_runner.ts +++ b/test/tools/cmap_spec_runner.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { EventEmitter } from 'events'; import { clearTimeout, setTimeout } from 'timers'; -import { promisify } from 'util'; import { addContainerMetadata, @@ -206,8 +205,8 @@ const getTestOpDefinitions = (threadContext: ThreadContext) => ({ clear: function ({ interruptInUseConnections }: { interruptInUseConnections: boolean }) { return threadContext.pool.clear({ interruptInUseConnections }); }, - close: async function () { - return await promisify(ConnectionPool.prototype.close).call(threadContext.pool); + close: function () { + return ConnectionPool.prototype.close.call(threadContext.pool); }, ready: function () { return threadContext.pool.ready(); @@ -329,28 +328,20 @@ export class ThreadContext { closePool() { this.#server.pool = this.#originalServerPool; - return new Promise(resolve => { - ALL_POOL_EVENTS.forEach(ev => this.pool.removeAllListeners(ev)); - this.pool.close(resolve); - }); + ALL_POOL_EVENTS.forEach(ev => this.pool.removeAllListeners(ev)); + this.pool.close(); } async tearDown() { if (this.pool) { - await this.closePool(); + this.closePool(); } const connectionsToDestroy = Array.from(this.orphans).concat( Array.from(this.connections.values()) ); - const promises = connectionsToDestroy.map(conn => { - return new Promise((resolve, reject) => - conn.destroy({ force: true }, err => { - if (err) return reject(err); - resolve(); - }) - ); - }); - await Promise.all(promises); + for (const conn of connectionsToDestroy) { + conn.destroy(); + } this.poolEventsEventEmitter.removeAllListeners(); } } diff --git a/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts b/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts index bbc8c424f2..0dfde6ba2d 100644 --- a/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts +++ b/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts @@ -115,7 +115,8 @@ describe('Polling Srv Records for Mongos Discovery', () => { afterEach(function (done) { if (context.topology) { - context.topology.close({}, done); + context.topology.close(); + done(); } else { done(); } diff --git a/test/unit/assorted/server_selection_spec_helper.js b/test/unit/assorted/server_selection_spec_helper.js index 3aaa79d71a..c6e15a8731 100644 --- a/test/unit/assorted/server_selection_spec_helper.js +++ b/test/unit/assorted/server_selection_spec_helper.js @@ -110,7 +110,8 @@ function executeServerSelectionTest(testDefinition, testDone) { }); function done(err) { - topology.close({}, e => testDone(e || err)); + topology.close(); + testDone(err); } topology.connect(err => { diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index 65ad159b0b..0bb928501c 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -128,7 +128,7 @@ describe('Connect Tests', function () { }); afterEach(async () => { - connection.destroy({ force: true }); + connection.destroy(); await mock.cleanup(); }); diff --git a/test/unit/error.test.ts b/test/unit/error.test.ts index 4a9d1423a5..a4881150b0 100644 --- a/test/unit/error.test.ts +++ b/test/unit/error.test.ts @@ -377,7 +377,10 @@ describe('MongoErrors', () => { makeAndConnectReplSet((err, topology) => { // cleanup the server before calling done - const cleanup = err => topology.close({ force: true }, err2 => done(err || err2)); + const cleanup = err => { + topology.close(); + done(err); + }; if (err) { return cleanup(err); @@ -420,7 +423,10 @@ describe('MongoErrors', () => { makeAndConnectReplSet((err, topology) => { // cleanup the server before calling done - const cleanup = err => topology.close({}, err2 => done(err || err2)); + const cleanup = err => { + topology.close(); + done(err); + }; if (err) { return cleanup(err); diff --git a/test/unit/sdam/monitor.test.ts b/test/unit/sdam/monitor.test.ts index 325b411de2..1737b0a2dc 100644 --- a/test/unit/sdam/monitor.test.ts +++ b/test/unit/sdam/monitor.test.ts @@ -94,7 +94,8 @@ describe('monitoring', function () { const serverDescription = Array.from(topology.description.servers.values())[0]; expect(serverDescription).property('roundTripTime').to.be.greaterThan(0); - topology.close({}, done as any); + topology.close(); + done(); }, 500); }); }).skipReason = 'TODO(NODE-3819): Unskip flaky tests'; diff --git a/test/unit/sdam/topology.test.js b/test/unit/sdam/topology.test.js index cbc654420d..edc594b967 100644 --- a/test/unit/sdam/topology.test.js +++ b/test/unit/sdam/topology.test.js @@ -26,7 +26,7 @@ describe('Topology (unit)', function () { } if (topology) { - topology.close({}); + topology.close(); } }); @@ -104,7 +104,8 @@ describe('Topology (unit)', function () { .then(expect.fail, err => { expect(err).to.exist; expect(err).to.match(/timed out/); - topology.close({}, done); + topology.close(); + done(); }); }); }); @@ -251,7 +252,8 @@ describe('Topology (unit)', function () { expect(err).to.exist; expect(err).to.eql(serverDescription.error); expect(poolCleared).to.be.false; - topology.close({}, done); + topology.close(); + done(); }); }); }); @@ -401,17 +403,12 @@ describe('Topology (unit)', function () { it('should clean up listeners on close', function (done) { topology.s.state = 'connected'; // fake state to test clean up logic - topology.close({}, e => { - const srvPollerListeners = topology.s.srvPoller.listeners( - SrvPoller.SRV_RECORD_DISCOVERY - ); - expect(srvPollerListeners).to.have.lengthOf(0); - const topologyChangeListeners = topology.listeners( - Topology.TOPOLOGY_DESCRIPTION_CHANGED - ); - expect(topologyChangeListeners).to.have.lengthOf(0); - done(e); - }); + topology.close(); + const srvPollerListeners = topology.s.srvPoller.listeners(SrvPoller.SRV_RECORD_DISCOVERY); + expect(srvPollerListeners).to.have.lengthOf(0); + const topologyChangeListeners = topology.listeners(Topology.TOPOLOGY_DESCRIPTION_CHANGED); + expect(topologyChangeListeners).to.have.lengthOf(0); + done(); }); }); @@ -481,7 +478,8 @@ describe('Topology (unit)', function () { // occurs `requestCheck` will be called for an immediate check. expect(requestCheck).property('callCount').to.equal(1); - topology.close({}, done); + topology.close(); + done(); }); }); }); @@ -493,12 +491,11 @@ describe('Topology (unit)', function () { this.emit('connect'); }); - topology.close({}, () => { - topology.selectServer(ReadPreference.primary, { serverSelectionTimeoutMS: 2000 }, err => { - expect(err).to.exist; - expect(err).to.match(/Topology is closed/); - done(); - }); + topology.close(); + topology.selectServer(ReadPreference.primary, { serverSelectionTimeoutMS: 2000 }, err => { + expect(err).to.exist; + expect(err).to.match(/Topology is closed/); + done(); }); }); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index b5fcadbffc..8cb338c516 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -5,7 +5,6 @@ import { BufferPool, ByteUtils, compareObjectId, - eachAsync, HostAddress, hostMatchesWildcards, isHello, @@ -164,37 +163,6 @@ describe('driver utils', function () { }); }); - context('eachAsync()', function () { - it('should callback with an error', function (done) { - eachAsync( - [{ error: false }, { error: true }], - (item, cb) => { - cb(item.error ? new Error('error requested') : undefined); - }, - err => { - expect(err).to.exist; - done(); - } - ); - }); - - it('should propagate a synchronously thrown error', function (done) { - expect(() => - eachAsync( - [{}], - () => { - throw new Error('something wicked'); - }, - err => { - expect(err).to.not.exist; - done(err); - } - ) - ).to.throw(/something wicked/); - done(); - }); - }); - describe('class BufferPool', function () { it('should report the correct length', function () { const buffer = new BufferPool();