From 78eb356cd7cd1bb105cb19ad3872753296882c56 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 14 Aug 2024 13:12:39 -0400 Subject: [PATCH 01/52] add timeoutMode to find, findOne, listIndexes, aggregate, listCollections --- src/cursor/abstract_cursor.ts | 7 +++++++ src/operations/aggregate.ts | 4 ++++ src/operations/find.ts | 4 ++++ src/operations/indexes.ts | 6 ++++-- src/operations/list_collections.ts | 3 +++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index de53ed1b2eb..5d0fce6434f 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -59,6 +59,13 @@ export interface CursorStreamOptions { /** @public */ export type CursorFlag = (typeof CURSOR_FLAGS)[number]; +export const CursorTimeoutMode = Object.freeze({ + ITERATION: 'iteration', + LIFETIME: 'lifetime' +} as const); + +export type CursorTimeoutMode = (typeof CursorTimeoutMode)[keyof typeof CursorTimeoutMode]; + /** @public */ export interface AbstractCursorOptions extends BSONSerializeOptions { session?: ClientSession; diff --git a/src/operations/aggregate.ts b/src/operations/aggregate.ts index 98f6bf2e2bf..f726df46506 100644 --- a/src/operations/aggregate.ts +++ b/src/operations/aggregate.ts @@ -1,5 +1,6 @@ import type { Document } from '../bson'; import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses'; +import { type CursorTimeoutMode } from '../cursor/abstract_cursor'; import { MongoInvalidArgumentError } from '../error'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; @@ -35,6 +36,9 @@ export interface AggregateOptions extends CommandOperationOptions { let?: Document; out?: string; + + /** @internal */ + timeoutMode?: CursorTimeoutMode; } /** @internal */ diff --git a/src/operations/find.ts b/src/operations/find.ts index 5f359324d56..c39695cc0bc 100644 --- a/src/operations/find.ts +++ b/src/operations/find.ts @@ -1,5 +1,6 @@ import type { Document } from '../bson'; import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses'; +import { type CursorTimeoutMode } from '../cursor/abstract_cursor'; import { MongoInvalidArgumentError } from '../error'; import { ReadConcern } from '../read_concern'; import type { Server } from '../sdam/server'; @@ -64,6 +65,9 @@ export interface FindOptions * @deprecated Starting from MongoDB 4.4 this flag is not needed and will be ignored. */ oplogReplay?: boolean; + + /** @internal*/ + timeoutMode?: CursorTimeoutMode; } /** @internal */ diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index c96a5d73453..07fc277cb0d 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -1,7 +1,7 @@ import type { Document } from '../bson'; import { CursorResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; -import { type AbstractCursorOptions } from '../cursor/abstract_cursor'; +import { type AbstractCursorOptions, type CursorTimeoutMode } from '../cursor/abstract_cursor'; import { MongoCompatibilityError } from '../error'; import { type OneOrMore } from '../mongo_types'; import type { Server } from '../sdam/server'; @@ -360,7 +360,9 @@ export class DropIndexOperation extends CommandOperation { } /** @public */ -export type ListIndexesOptions = AbstractCursorOptions; +export type ListIndexesOptions = AbstractCursorOptions & { + timeoutMode?: CursorTimeoutMode; +}; /** @internal */ export class ListIndexesOperation extends CommandOperation { diff --git a/src/operations/list_collections.ts b/src/operations/list_collections.ts index 702db0fe3f2..50df243a3ff 100644 --- a/src/operations/list_collections.ts +++ b/src/operations/list_collections.ts @@ -1,5 +1,6 @@ import type { Binary, Document } from '../bson'; import { CursorResponse } from '../cmap/wire_protocol/responses'; +import { type CursorTimeoutMode } from '../cursor/abstract_cursor'; import type { Db } from '../db'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; @@ -16,6 +17,8 @@ export interface ListCollectionsOptions extends Omit Date: Wed, 14 Aug 2024 13:49:22 -0400 Subject: [PATCH 02/52] unskip spec tests --- .../client_side_operations_timeout.spec.test.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index e4c9eb3027c..da3b6a70d2f 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -8,16 +8,9 @@ const enabled = [ 'override-database-timeoutMS', 'override-operation-timeoutMS', 'retryability-legacy-timeouts', - 'retryability-timeoutMS' -]; - -const cursorOperations = [ - 'aggregate', - 'countDocuments', - 'listIndexes', - 'createChangeStream', - 'listCollections', - 'listCollectionNames' + 'retryability-timeoutMS', + 'runCursorCommand', + 'close-cursors' ]; const bulkWriteOperations = [ @@ -34,10 +27,6 @@ describe('CSOT spec tests', function () { test.skipReason = 'TODO(NODE-5684): Not working yet'; } - // Cursor operation - if (test.operations.find(operation => cursorOperations.includes(operation.name))) - test.skipReason = 'TODO(NODE-5684): Not working yet'; - if (bulkWriteOperations.includes(test.description)) test.skipReason = 'TODO(NODE-6274): update test runner to check errorResponse field of MongoBulkWriteError in isTimeoutError assertion'; From 6c423e095e4ad4f2c0675b9f979b4e04f4646f55 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 14 Aug 2024 13:49:31 -0400 Subject: [PATCH 03/52] implement prose tests --- ...ient_side_operations_timeout.prose.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts index 729bed42199..4463eae8873 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts @@ -3,12 +3,15 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; +import { type CommandStartedEvent } from '../../../mongodb'; import { + type CommandSucceededEvent, MongoClient, MongoOperationTimeoutError, MongoServerSelectionError, now } from '../../mongodb'; +import { type FailPoint } from '../../tools/utils'; // TODO(NODE-5824): Implement CSOT prose tests describe('CSOT spec prose tests', function () { @@ -220,6 +223,42 @@ describe('CSOT spec prose tests', function () { * blocking method for cursor iteration that executes `getMore` commands in a loop until a document is available or an * error occurs. */ + const failpoint: FailPoint = { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { + failCommands: ['getMore'], + blockConnection: true, + blockTimeMS: 15 + } + }; + let internalClient: MongoClient; + let client: MongoClient; + let commandStarted: CommandStartedEvent[]; + let commandSucceeded: CommandSucceededEvent[]; + + beforeEach(async function () { + internalClient = this.configuration.newClient(); + await internalClient.db('db').dropCollection('coll'); + await internalClient.db('db').collection('coll').insertOne({ x: 1 }); + await internalClient.db().admin().command(failpoint); + + client = this.configuration.newClient(undefined, { timeoutMS: 20 }); + commandStarted = []; + commandSucceeded = []; + + client.on('commandStarted', ev => commandStarted.push(ev)); + client.on('commandSucceeded', ev => commandSucceeded.push(ev)); + }); + + afterEach(async function () { + await internalClient + .db() + .admin() + .command({ ...failpoint, mode: 'off' }); + await internalClient.close(); + await client.close(); + }); context('Tailable cursors', () => { /** @@ -246,6 +285,27 @@ describe('CSOT spec prose tests', function () { * - Expect this to fail with a timeout error. * 1. Verify that a `find` command and two `getMore` commands were executed against the `db.coll` collection during the test. */ + + it('send correct number of finds and getMores', async function () { + const cursor = client.db('db').collection('coll').find({}, { tailable: true }); + const doc = await cursor.next(); + // FIXME: Account for object id + expect(doc).to.deep.equal({ x: 1 }); + // Check that there are no getMores sent + expect(commandStarted.filter(e => Object.hasOwn(e.command, 'getMore'))).to.have.lengthOf(0); + + const maybeError = await cursor.next().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceof(MongoOperationTimeoutError); + expect( + commandStarted.filter( + e => Object.hasOwn(e.command, 'find') || Object.hasOwn(e.command, 'getMore') + ) + ).to.have.lengthOf(3); + }); }); context('Change Streams', () => { @@ -270,6 +330,20 @@ describe('CSOT spec prose tests', function () { * - Expect this to fail with a timeout error. * 1. Verify that an `aggregate` command and two `getMore` commands were executed against the `db.coll` collection during the test. */ + it('sends correct number of aggregate and getMores', async function () { + const changeStream = client.db('db').collection('coll').watch(); + const maybeError = await changeStream.next().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceof(MongoOperationTimeoutError); + expect( + commandStarted.filter( + e => Object.hasOwn(e.command, 'aggregate') || Object.hasOwn(e.command, 'getMore') + ) + ).to.have.lengthOf(3); + }); }); }); From 978d8dd6cde108868c63b8aac63d53e79146f2e4 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 14 Aug 2024 14:19:54 -0400 Subject: [PATCH 04/52] Add timeoutMode to abstract cursor and add validation --- src/cursor/abstract_cursor.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 5d0fce6434f..104c3e474b0 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -111,6 +111,7 @@ export interface AbstractCursorOptions extends BSONSerializeOptions { noCursorTimeout?: boolean; /** @internal TODO(NODE-5688): make this public */ timeoutMS?: number; + timeoutMode?: CursorTimeoutMode; } /** @internal */ @@ -189,6 +190,26 @@ export abstract class AbstractCursor< ...pluckBSONSerializeOptions(options) }; this.cursorOptions.timeoutMS = options.timeoutMS; + if (this.cursorOptions.timeoutMS != null) { + this.cursorOptions.timeoutMode = options.timeoutMode; + } + + if (this.cursorOptions.timeoutMS != null) { + if (this.cursorOptions.timeoutMode == null) { + if (this.cursorOptions.tailable) { + this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION; + } else { + this.cursorOptions.timeoutMode = CursorTimeoutMode.LIFETIME; + } + } else { + if ( + this.cursorOptions.tailable && + this.cursorOptions.timeoutMode === CursorTimeoutMode.LIFETIME + ) { + throw new MongoAPIError("Cannot set tailable cursor's timeoutMode to LIFETIME"); + } + } + } const readConcern = ReadConcern.fromOptions(options); if (readConcern) { From 53173781d19e5bbc6d207edf899f0a523ef5c615 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 14 Aug 2024 14:35:44 -0400 Subject: [PATCH 05/52] Add $out and $merge validation --- src/cursor/aggregation_cursor.ts | 19 +++++- test/unit/cursor/aggregation_cursor.test.ts | 64 ++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index 1b1ad663ba9..fb6fc96be39 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -1,4 +1,5 @@ import type { Document } from '../bson'; +import { MongoAPIError } from '../error'; import type { ExplainVerbosityLike } from '../explain'; import type { MongoClient } from '../mongo_client'; import { AggregateOperation, type AggregateOptions } from '../operations/aggregate'; @@ -8,7 +9,7 @@ import type { Sort } from '../sort'; import type { MongoDBNamespace } from '../utils'; import { mergeOptions } from '../utils'; import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cursor'; -import { AbstractCursor } from './abstract_cursor'; +import { AbstractCursor, CursorTimeoutMode } from './abstract_cursor'; /** @public */ export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} @@ -36,6 +37,15 @@ export class AggregationCursor extends AbstractCursor { this.pipeline = pipeline; this.aggregateOptions = options; + + if ( + this.cursorOptions.timeoutMS != null && + this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + this.pipeline.filter(stage => { + Object.hasOwn(stage, '$out') || Object.hasOwn(stage, '$merge'); + }).length > 0 + ) + throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } clone(): AggregationCursor { @@ -93,6 +103,13 @@ export class AggregationCursor extends AbstractCursor { addStage(stage: Document): AggregationCursor; addStage(stage: Document): AggregationCursor { this.throwIfInitialized(); + if ( + this.cursorOptions.timeoutMS != null && + this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + (Object.hasOwn(stage, '$out') || Object.hasOwn(stage, '$merge')) + ) { + throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); + } this.pipeline.push(stage); return this as unknown as AggregationCursor; } diff --git a/test/unit/cursor/aggregation_cursor.test.ts b/test/unit/cursor/aggregation_cursor.test.ts index 32ca4125ff4..ab1571ffb8c 100644 --- a/test/unit/cursor/aggregation_cursor.test.ts +++ b/test/unit/cursor/aggregation_cursor.test.ts @@ -1,6 +1,11 @@ import { expect } from 'chai'; -import { type AggregationCursor, MongoClient } from '../../mongodb'; +import { + type AggregationCursor, + CursorTimeoutMode, + MongoAPIError, + MongoClient +} from '../../mongodb'; describe('class AggregationCursor', () => { let client: MongoClient; @@ -126,6 +131,38 @@ describe('class AggregationCursor', () => { }); context('when addStage, bespoke stage methods, or array is used to construct pipeline', () => { + context('when CSOT is enabled', () => { + let aggregationCursor: AggregationCursor; + before(function () { + aggregationCursor = client + .db('test') + .collection('test') + .aggregate([], { timeoutMS: 100, timeoutMode: CursorTimeoutMode.ITERATION }); + }); + + context('when a $out stage is add with .addStage()', () => { + it('throws a MongoAPIError', function () { + expect(() => { + aggregationCursor.addStage({ $out: 'test' }); + }).to.throw(MongoAPIError); + }); + }); + context('when a $merge stage is add with .addStage()', () => { + it('throws a MongoAPIError', function () { + expect(() => { + aggregationCursor.addStage({ $merge: {} }); + }).to.throw(MongoAPIError); + }); + }); + context('when a $out stage is add with .out()', () => { + it('throws a MongoAPIError', function () { + expect(() => { + aggregationCursor.out('test'); + }).to.throw(MongoAPIError); + }); + }); + }); + it('sets deeply identical aggregations pipelines', () => { const collection = client.db().collection('test'); @@ -157,4 +194,29 @@ describe('class AggregationCursor', () => { expect(builderGenericStageCursor.pipeline).to.deep.equal(expectedPipeline); }); }); + + describe('constructor()', () => { + context('when CSOT is enabled', () => { + let client: MongoClient; + before(function () { + client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); + }); + context('when timeoutMode=ITERATION and a $out stage is provided', function () { + expect(() => { + client + .db('test') + .collection('test') + .aggregate([{ $out: 'test' }], { timeoutMode: CursorTimeoutMode.ITERATION }); + }).to.throw(MongoAPIError); + }); + context('when timeoutMode=ITERATION and a $merge stage is provided', function () { + expect(() => { + client + .db('test') + .collection('test') + .aggregate([{ $merge: 'test' }], { timeoutMode: CursorTimeoutMode.ITERATION }); + }).to.throw(MongoAPIError); + }); + }); + }); }); From 7c459e00e059e0932ac19b19f931b981b49ede02 Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 15 Aug 2024 17:06:26 -0400 Subject: [PATCH 06/52] start csot cursor implementation --- src/cmap/connection.ts | 3 +- src/cursor/abstract_cursor.ts | 70 +++++++++++++++++++++------ src/cursor/aggregation_cursor.ts | 18 +++++-- src/cursor/change_stream_cursor.ts | 13 ++++- src/cursor/find_cursor.ts | 14 ++++-- src/cursor/list_collections_cursor.ts | 14 ++++-- src/cursor/list_indexes_cursor.ts | 12 ++++- src/cursor/run_command_cursor.ts | 14 ++++-- src/index.ts | 2 +- src/operations/indexes.ts | 3 ++ src/operations/operation.ts | 3 ++ src/operations/run_command.ts | 2 + src/timeout.ts | 16 ++++++ 13 files changed, 151 insertions(+), 33 deletions(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index a5ab7071efa..a6b09a4a9f5 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -85,6 +85,7 @@ export interface CommandOptions extends BSONSerializeOptions { documentsReturnedIn?: string; noResponse?: boolean; omitReadPreference?: boolean; + omitMaxTimeMS?: boolean; // TODO(NODE-2802): Currently the CommandOptions take a property willRetryWrite which is a hint // from executeOperation that the txnNum should be applied to this command. @@ -418,7 +419,7 @@ export class Connection extends TypedEventEmitter { ...options }; - if (options.timeoutContext?.csotEnabled()) { + if (!options.omitMaxTimeMS && options.timeoutContext?.csotEnabled()) { const { maxTimeMS } = options.timeoutContext; if (maxTimeMS > 0 && Number.isFinite(maxTimeMS)) cmd.maxTimeMS = maxTimeMS; } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 104c3e474b0..7ea57f7e867 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -20,6 +20,7 @@ import { ReadPreference, type ReadPreferenceLike } from '../read_preference'; import { type AsyncDisposable, configureResourceManagement } from '../resource_management'; import type { Server } from '../sdam/server'; import { ClientSession, maybeClearPinnedConnection } from '../sessions'; +import { TimeoutContext } from '../timeout'; import { type MongoDBNamespace, squashError } from '../utils'; /** @@ -59,11 +60,15 @@ export interface CursorStreamOptions { /** @public */ export type CursorFlag = (typeof CURSOR_FLAGS)[number]; +/** @internal */ export const CursorTimeoutMode = Object.freeze({ ITERATION: 'iteration', LIFETIME: 'lifetime' } as const); +/** @internal + * TODO(NODE-5688): Document and release + * */ export type CursorTimeoutMode = (typeof CursorTimeoutMode)[keyof typeof CursorTimeoutMode]; /** @public */ @@ -111,6 +116,7 @@ export interface AbstractCursorOptions extends BSONSerializeOptions { noCursorTimeout?: boolean; /** @internal TODO(NODE-5688): make this public */ timeoutMS?: number; + /** @internal TODO(NODE-5688): make this public */ timeoutMode?: CursorTimeoutMode; } @@ -131,6 +137,12 @@ export type AbstractCursorEvents = { [AbstractCursor.CLOSE](): void; }; +/** @internal */ +export type CursorInitializeOptions = { + omitMaxTimeMS?: boolean; + timeoutContext?: TimeoutContext; +}; + /** @public */ export abstract class AbstractCursor< TSchema = any, @@ -161,6 +173,8 @@ export abstract class AbstractCursor< private isKilled: boolean; /** @internal */ protected readonly cursorOptions: InternalAbstractCursorOptions; + /** @internal */ + protected timeoutContext?: TimeoutContext; /** @event */ static readonly CLOSE = 'close' as const; @@ -191,11 +205,7 @@ export abstract class AbstractCursor< }; this.cursorOptions.timeoutMS = options.timeoutMS; if (this.cursorOptions.timeoutMS != null) { - this.cursorOptions.timeoutMode = options.timeoutMode; - } - - if (this.cursorOptions.timeoutMS != null) { - if (this.cursorOptions.timeoutMode == null) { + if (options.timeoutMode == null) { if (this.cursorOptions.tailable) { this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION; } else { @@ -442,6 +452,9 @@ export abstract class AbstractCursor< await this.fetchBatch(); } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + if (this.cursorOptions.timeoutMode === 'iteration') { + this.timeoutContext?.refresh(); + } return null; } @@ -493,8 +506,8 @@ export abstract class AbstractCursor< /** * Frees any client-side resources used by the cursor. */ - async close(): Promise { - await this.cleanup(); + async close(timeoutMS?: number): Promise { + await this.cleanup(timeoutMS); } /** @@ -699,7 +712,8 @@ export abstract class AbstractCursor< /** @internal */ protected abstract _initialize( - session: ClientSession | undefined + session: ClientSession | undefined, + options?: CursorInitializeOptions ): Promise; /** @internal */ @@ -721,11 +735,12 @@ export abstract class AbstractCursor< { ...this.cursorOptions, session: this.cursorSession, - batchSize + batchSize, + omitMaxTimeMS: this.cursorOptions.timeoutMode != null } ); - return await executeOperation(this.cursorClient, getMoreOperation); + return await executeOperation(this.cursorClient, getMoreOperation, this.timeoutContext); } /** @@ -736,8 +751,22 @@ export abstract class AbstractCursor< * a significant refactor. */ private async cursorInit(): Promise { + if (this.cursorOptions.timeoutMS != null) { + this.timeoutContext = TimeoutContext.create({ + serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, + timeoutMS: this.cursorOptions.timeoutMS, + cursorTimeoutMode: this.cursorOptions.timeoutMode + }); + } + const omitMaxTimeMS = + this.cursorOptions.timeoutMS != null && + ((this.cursorOptions.timeoutMode === 'iteration' && !this.cursorOptions.tailable) || + (this.cursorOptions.tailable && !this.cursorOptions.awaitData)); try { - const state = await this._initialize(this.cursorSession); + const state = await this._initialize(this.cursorSession, { + timeoutContext: this.timeoutContext, + omitMaxTimeMS + }); const response = state.response; this.selectedServer = state.server; this.cursorId = response.id; @@ -747,7 +776,7 @@ export abstract class AbstractCursor< } catch (error) { // the cursor is now initialized, even if an error occurred this.initialized = true; - await this.cleanup(error); + await this.cleanup(undefined, error); throw error; } @@ -788,7 +817,7 @@ export abstract class AbstractCursor< this.documents = response; } catch (error) { try { - await this.cleanup(error); + await this.cleanup(undefined, error); } catch (error) { // `cleanupCursor` should never throw, squash and throw the original error squashError(error); @@ -809,7 +838,7 @@ export abstract class AbstractCursor< } /** @internal */ - private async cleanup(error?: Error) { + private async cleanup(timeoutMS?: number, error?: Error) { this.isClosed = true; const session = this.cursorSession; try { @@ -824,11 +853,22 @@ export abstract class AbstractCursor< this.isKilled = true; const cursorId = this.cursorId; this.cursorId = Long.ZERO; + let timeoutContext: TimeoutContext | undefined; + if (timeoutMS != null) { + timeoutContext = TimeoutContext.create({ + serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, + timeoutMS + }); + } else { + this.timeoutContext?.refresh(); + timeoutContext = this.timeoutContext; + } await executeOperation( this.cursorClient, new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, { session - }) + }), + timeoutContext ); } } catch (error) { diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index fb6fc96be39..d374ad3c9a8 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -8,7 +8,11 @@ import type { ClientSession } from '../sessions'; import type { Sort } from '../sort'; import type { MongoDBNamespace } from '../utils'; import { mergeOptions } from '../utils'; -import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cursor'; +import type { + AbstractCursorOptions, + CursorInitializeOptions, + InitialCursorResponse +} from './abstract_cursor'; import { AbstractCursor, CursorTimeoutMode } from './abstract_cursor'; /** @public */ @@ -61,14 +65,22 @@ export class AggregationCursor extends AbstractCursor { } /** @internal */ - async _initialize(session: ClientSession): Promise { + async _initialize( + session: ClientSession, + options?: CursorInitializeOptions + ): Promise { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.aggregateOptions, ...this.cursorOptions, + omitMaxTimeMS: options?.omitMaxTimeMS, session }); - const response = await executeOperation(this.client, aggregateOperation); + const response = await executeOperation( + this.client, + aggregateOperation, + options?.timeoutContext + ); return { server: aggregateOperation.server, session, response }; } diff --git a/src/cursor/change_stream_cursor.ts b/src/cursor/change_stream_cursor.ts index b42ce3e1302..ba06ad485f9 100644 --- a/src/cursor/change_stream_cursor.ts +++ b/src/cursor/change_stream_cursor.ts @@ -17,6 +17,7 @@ import { maxWireVersion, type MongoDBNamespace } from '../utils'; import { AbstractCursor, type AbstractCursorOptions, + type CursorInitializeOptions, type InitialCursorResponse } from './abstract_cursor'; @@ -126,14 +127,22 @@ export class ChangeStreamCursor< }); } - async _initialize(session: ClientSession): Promise { + async _initialize( + session: ClientSession, + options?: CursorInitializeOptions + ): Promise { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.cursorOptions, ...this.changeStreamCursorOptions, + omitMaxTimeMS: options?.omitMaxTimeMS, session }); - const response = await executeOperation(session.client, aggregateOperation); + const response = await executeOperation( + session.client, + aggregateOperation, + options?.timeoutContext + ); const server = aggregateOperation.server; this.maxWireVersion = maxWireVersion(server); diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index e4b3dbc03c2..9df7c480521 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -11,7 +11,11 @@ import type { Hint } from '../operations/operation'; import type { ClientSession } from '../sessions'; import { formatSort, type Sort, type SortDirection } from '../sort'; import { emitWarningOnce, mergeOptions, type MongoDBNamespace, squashError } from '../utils'; -import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; +import { + AbstractCursor, + type CursorInitializeOptions, + type InitialCursorResponse +} from './abstract_cursor'; /** @public Flags allowed for cursor */ export const FLAGS = [ @@ -62,14 +66,18 @@ export class FindCursor extends AbstractCursor { } /** @internal */ - async _initialize(session: ClientSession): Promise { + async _initialize( + session: ClientSession, + options?: CursorInitializeOptions + ): Promise { const findOperation = new FindOperation(this.namespace, this.cursorFilter, { ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, + omitMaxTimeMS: options?.omitMaxTimeMS, session }); - const response = await executeOperation(this.client, findOperation); + const response = await executeOperation(this.client, findOperation, options?.timeoutContext); // the response is not a cursor when `explain` is enabled this.numReturned = response.batchSize; diff --git a/src/cursor/list_collections_cursor.ts b/src/cursor/list_collections_cursor.ts index a529709556d..aab2ffe88c7 100644 --- a/src/cursor/list_collections_cursor.ts +++ b/src/cursor/list_collections_cursor.ts @@ -7,7 +7,11 @@ import { type ListCollectionsOptions } from '../operations/list_collections'; import type { ClientSession } from '../sessions'; -import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; +import { + AbstractCursor, + type CursorInitializeOptions, + type InitialCursorResponse +} from './abstract_cursor'; /** @public */ export class ListCollectionsCursor< @@ -34,14 +38,18 @@ export class ListCollectionsCursor< } /** @internal */ - async _initialize(session: ClientSession | undefined): Promise { + async _initialize( + session: ClientSession | undefined, + options?: CursorInitializeOptions + ): Promise { const operation = new ListCollectionsOperation(this.parent, this.filter, { ...this.cursorOptions, ...this.options, + omitMaxTimeMS: options?.omitMaxTimeMS, session }); - const response = await executeOperation(this.parent.client, operation); + const response = await executeOperation(this.parent.client, operation, options?.timeoutContext); return { server: operation.server, session, response }; } diff --git a/src/cursor/list_indexes_cursor.ts b/src/cursor/list_indexes_cursor.ts index 799ddf5bdb5..45802cc918f 100644 --- a/src/cursor/list_indexes_cursor.ts +++ b/src/cursor/list_indexes_cursor.ts @@ -2,7 +2,11 @@ import type { Collection } from '../collection'; import { executeOperation } from '../operations/execute_operation'; import { ListIndexesOperation, type ListIndexesOptions } from '../operations/indexes'; import type { ClientSession } from '../sessions'; -import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; +import { + AbstractCursor, + type CursorInitializeOptions, + type InitialCursorResponse +} from './abstract_cursor'; /** @public */ export class ListIndexesCursor extends AbstractCursor { @@ -23,10 +27,14 @@ export class ListIndexesCursor extends AbstractCursor { } /** @internal */ - async _initialize(session: ClientSession | undefined): Promise { + async _initialize( + session: ClientSession | undefined, + options?: CursorInitializeOptions + ): Promise { const operation = new ListIndexesOperation(this.parent, { ...this.cursorOptions, ...this.options, + omitMaxTimeMS: options?.omitMaxTimeMS, session }); diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 78b9826b9b1..5c7d3c8c78a 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -9,7 +9,11 @@ import type { ReadConcernLike } from '../read_concern'; import type { ReadPreferenceLike } from '../read_preference'; import type { ClientSession } from '../sessions'; import { ns } from '../utils'; -import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; +import { + AbstractCursor, + type CursorInitializeOptions, + type InitialCursorResponse +} from './abstract_cursor'; /** @public */ export type RunCursorCommandOptions = { @@ -97,15 +101,19 @@ export class RunCommandCursor extends AbstractCursor { } /** @internal */ - protected async _initialize(session: ClientSession): Promise { + protected async _initialize( + session: ClientSession, + options?: CursorInitializeOptions + ): Promise { const operation = new RunCommandOperation(this.db, this.command, { ...this.cursorOptions, session: session, readPreference: this.cursorOptions.readPreference, + omitMaxTimeMS: options?.omitMaxTimeMS, responseType: CursorResponse }); - const response = await executeOperation(this.client, operation); + const response = await executeOperation(this.client, operation, options?.timeoutContext); return { server: operation.server, diff --git a/src/index.ts b/src/index.ts index 6a97b262166..e5899c72757 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,7 +106,7 @@ export { AutoEncryptionLoggerLevel } from './client-side-encryption/auto_encrypt export { GSSAPICanonicalizationValue } from './cmap/auth/gssapi'; export { AuthMechanism } from './cmap/auth/providers'; export { Compressor } from './cmap/wire_protocol/compression'; -export { CURSOR_FLAGS } from './cursor/abstract_cursor'; +export { CURSOR_FLAGS, CursorInitializeOptions, CursorTimeoutMode } from './cursor/abstract_cursor'; export { MongoErrorLabel } from './error'; export { ExplainVerbosity } from './explain'; export { ServerApiVersion } from './mongo_client'; diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index 07fc277cb0d..f665ef3f27c 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -361,7 +361,10 @@ export class DropIndexOperation extends CommandOperation { /** @public */ export type ListIndexesOptions = AbstractCursorOptions & { + /** @internal TODO(NODE-5688): make this public */ timeoutMode?: CursorTimeoutMode; + /** @internal TODO(NODE-5688): make this public */ + omitMaxTimeMS?: boolean; }; /** @internal */ diff --git a/src/operations/operation.ts b/src/operations/operation.ts index e56b2173d6b..342ddb63a0a 100644 --- a/src/operations/operation.ts +++ b/src/operations/operation.ts @@ -36,6 +36,9 @@ export interface OperationOptions extends BSONSerializeOptions { bypassPinningCheck?: boolean; omitReadPreference?: boolean; + /** @internal Hint to `executeOperation` to omit maxTimeMS */ + omitMaxTimeMS?: boolean; + /** @internal TODO(NODE-5688): make this public */ timeoutMS?: number; } diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index b91e2d0344e..1747f10c8be 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -17,6 +17,8 @@ export type RunCommandOptions = { readPreference?: ReadPreferenceLike; /** @internal */ timeoutMS?: number; + /** @internal */ + omitMaxTimeMS?: boolean; } & BSONSerializeOptions; /** @internal */ diff --git a/src/timeout.ts b/src/timeout.ts index 4007ffa50a6..da0cb75e75f 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -1,5 +1,6 @@ import { clearTimeout, setTimeout } from 'timers'; +import { type CursorTimeoutMode } from './cursor/abstract_cursor'; import { MongoInvalidArgumentError, MongoOperationTimeoutError, MongoRuntimeError } from './error'; import { csotMin, noop } from './utils'; @@ -125,6 +126,7 @@ export type CSOTTimeoutContextOptions = { timeoutMS: number; serverSelectionTimeoutMS: number; socketTimeoutMS?: number; + cursorTimeoutMode?: CursorTimeoutMode; }; function isLegacyTimeoutContextOptions(v: unknown): v is LegacyTimeoutContextOptions { @@ -170,6 +172,8 @@ export abstract class TimeoutContext { abstract get timeoutForSocketRead(): Timeout | null; abstract csotEnabled(): this is CSOTTimeoutContext; + + abstract refresh(): void; } /** @internal */ @@ -180,6 +184,7 @@ export class CSOTTimeoutContext extends TimeoutContext { clearConnectionCheckoutTimeout: boolean; clearServerSelectionTimeout: boolean; + cursorTimeoutMode?: CursorTimeoutMode; private _serverSelectionTimeout?: Timeout | null; private _connectionCheckoutTimeout?: Timeout | null; @@ -195,6 +200,7 @@ export class CSOTTimeoutContext extends TimeoutContext { this.serverSelectionTimeoutMS = options.serverSelectionTimeoutMS; this.socketTimeoutMS = options.socketTimeoutMS; + this.cursorTimeoutMode = options.cursorTimeoutMode; this.clearServerSelectionTimeout = false; this.clearConnectionCheckoutTimeout = true; @@ -268,6 +274,12 @@ export class CSOTTimeoutContext extends TimeoutContext { if (remainingTimeMS > 0) return Timeout.expires(remainingTimeMS); throw new MongoOperationTimeoutError('Timed out before socket read'); } + + refresh(): void { + this.start = Math.trunc(performance.now()); + this._serverSelectionTimeout?.clear(); + this._connectionCheckoutTimeout?.clear(); + } } /** @internal */ @@ -306,4 +318,8 @@ export class LegacyTimeoutContext extends TimeoutContext { get timeoutForSocketRead(): Timeout | null { return null; } + + refresh(): void { + return; + } } From 0a2e4ce10deae2d11a28466904e86bd311995c95 Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 15 Aug 2024 17:07:04 -0400 Subject: [PATCH 07/52] WIP: int testing --- .../node_csot.test.ts | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index d7d4a4ede5a..1ee0e7e6a71 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -7,6 +7,7 @@ import { BSON, type ClientSession, type Collection, + type CommandStartedEvent, Connection, type Db, type FindCursor, @@ -320,4 +321,124 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }); }); }); + + describe('Non-Tailable cursors', () => { + let client: MongoClient; + let internalClient: MongoClient; + let commandStarted: CommandStartedEvent[]; + const failpoint: FailPoint = { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { + failCommands: ['find'], + blockConnection: true, + blockTimeMS: 30 + } + }; + + beforeEach(async function () { + internalClient = this.configuration.newClient(undefined); + await internalClient.db('db').dropCollection('coll'); + await internalClient + .db('db') + .collection('coll') + .insertMany( + Array.from({ length: 3 }, () => { + return { x: 1 }; + }) + ); + + await internalClient.db().admin().command(failpoint); + + client = this.configuration.newClient(undefined, { timeoutMS: 20, monitorCommands: true }); + commandStarted = []; + client.on('commandStarted', ev => commandStarted.push(ev)); + }); + + afterEach(async function () { + await internalClient + .db() + .admin() + .command({ ...failpoint, mode: 'off' }); + await internalClient.close(); + await client.close(); + }); + + context('ITERATION mode', () => { + context('when executing a valid operation', () => { + it('must apply the configured timeoutMS to the initial operation execution', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 3, timeoutMode: 'iteration' }) + .limit(3); + + const maybeError = await cursor.next().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceOf(MongoOperationTimeoutError); + }); + + it('refreshes the timeout for any getMores', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 1, timeoutMode: 'iteration' }) + .project({ _id: 0 }) + .limit(2); + + const firstDoc = await cursor.next(); + expect(firstDoc).to.deep.equal({ x: 1 }); + + const maybeError = await cursor.next().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceOf(MongoOperationTimeoutError); + }); + it('does not append a maxTimeMS to the original command or getMores', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 1, timeoutMode: 'iteration' }) + .project({ _id: 0 }); + await cursor.toArray(); + + expect(commandStarted).to.have.length.gte(3); // Find and 2 getMores + expect( + commandStarted.filter(ev => { + return ( + ev.command.find != null && + ev.command.getMore != null && + ev.command.maxTimeMS != null + ); + }) + ).to.have.lengthOf(0); + }); + }); + }); + + context('LIFETIME mode', () => { + context('when executing a next call', () => { + context( + 'when there are documents available from previously retrieved batch and timeout has expired', + () => { + it('returns documents without error'); + } + ); + context('when a getMore is required and the timeout has expired', () => { + it('throws a MongoOperationTimeoutError'); + }); + it('does not apply maxTimeMS to a getMore'); + }); + }); + }); + + describe.skip('Tailable non-awaitData cursors').skipReason = + 'TODO(NODE-6305): implement CSOT for Tailable cursors'; + describe.skip('Tailable awaitData cursors').skipReason = + 'TODO(NODE-6305): implement CSOT for Tailable cursors'; }); From ac97b5a9ccad213d0e0f8a21c055af518027c04d Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 16 Aug 2024 13:40:11 -0400 Subject: [PATCH 08/52] WIP --- src/operations/execute_operation.ts | 3 +-- src/timeout.ts | 11 +++++++++++ ...ient_side_operations_timeout.prose.test.ts | 19 +++++++++++-------- test/unit/cursor/aggregation_cursor.test.ts | 6 ++---- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/operations/execute_operation.ts b/src/operations/execute_operation.ts index 695e4ab92a0..39686dac409 100644 --- a/src/operations/execute_operation.ts +++ b/src/operations/execute_operation.ts @@ -285,8 +285,7 @@ async function tryOperation< previousOperationError = operationError; // Reset timeouts - timeoutContext.serverSelectionTimeout?.clear(); - timeoutContext.connectionCheckoutTimeout?.clear(); + timeoutContext.clear(); } } diff --git a/src/timeout.ts b/src/timeout.ts index da0cb75e75f..ba0f080b042 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -174,6 +174,8 @@ export abstract class TimeoutContext { abstract csotEnabled(): this is CSOTTimeoutContext; abstract refresh(): void; + + abstract clear(): void; } /** @internal */ @@ -280,6 +282,11 @@ export class CSOTTimeoutContext extends TimeoutContext { this._serverSelectionTimeout?.clear(); this._connectionCheckoutTimeout?.clear(); } + + clear(): void { + this._serverSelectionTimeout?.clear(); + this._connectionCheckoutTimeout?.clear(); + } } /** @internal */ @@ -322,4 +329,8 @@ export class LegacyTimeoutContext extends TimeoutContext { refresh(): void { return; } + + clear(): void { + return; + } } diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts index 4463eae8873..f98ac862ec7 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts @@ -217,7 +217,7 @@ describe('CSOT spec prose tests', function () { }); }); - context.skip('5. Blocking Iteration Methods', () => { + context('5. Blocking Iteration Methods', () => { /** * Tests in this section MUST only be run against server versions 4.4 and higher and only apply to drivers that have a * blocking method for cursor iteration that executes `getMore` commands in a loop until a document is available or an @@ -229,7 +229,7 @@ describe('CSOT spec prose tests', function () { data: { failCommands: ['getMore'], blockConnection: true, - blockTimeMS: 15 + blockTimeMS: 20 } }; let internalClient: MongoClient; @@ -240,10 +240,11 @@ describe('CSOT spec prose tests', function () { beforeEach(async function () { internalClient = this.configuration.newClient(); await internalClient.db('db').dropCollection('coll'); + await internalClient.db('db').createCollection('coll', { capped: true, size: 1_000_000 }); await internalClient.db('db').collection('coll').insertOne({ x: 1 }); await internalClient.db().admin().command(failpoint); - client = this.configuration.newClient(undefined, { timeoutMS: 20 }); + client = this.configuration.newClient(undefined, { timeoutMS: 20, monitorCommands: true }); commandStarted = []; commandSucceeded = []; @@ -287,12 +288,16 @@ describe('CSOT spec prose tests', function () { */ it('send correct number of finds and getMores', async function () { - const cursor = client.db('db').collection('coll').find({}, { tailable: true }); + const cursor = client + .db('db') + .collection('coll') + .find({}, { tailable: true }) + .project({ _id: 0 }); const doc = await cursor.next(); // FIXME: Account for object id expect(doc).to.deep.equal({ x: 1 }); // Check that there are no getMores sent - expect(commandStarted.filter(e => Object.hasOwn(e.command, 'getMore'))).to.have.lengthOf(0); + expect(commandStarted.filter(e => e.command.getMore != null)).to.have.lengthOf(0); const maybeError = await cursor.next().then( () => null, @@ -301,9 +306,7 @@ describe('CSOT spec prose tests', function () { expect(maybeError).to.be.instanceof(MongoOperationTimeoutError); expect( - commandStarted.filter( - e => Object.hasOwn(e.command, 'find') || Object.hasOwn(e.command, 'getMore') - ) + commandStarted.filter(e => e.command.find != null || e.command.getMore != null) ).to.have.lengthOf(3); }); }); diff --git a/test/unit/cursor/aggregation_cursor.test.ts b/test/unit/cursor/aggregation_cursor.test.ts index ab1571ffb8c..39ab4d60715 100644 --- a/test/unit/cursor/aggregation_cursor.test.ts +++ b/test/unit/cursor/aggregation_cursor.test.ts @@ -197,11 +197,8 @@ describe('class AggregationCursor', () => { describe('constructor()', () => { context('when CSOT is enabled', () => { - let client: MongoClient; - before(function () { - client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); - }); context('when timeoutMode=ITERATION and a $out stage is provided', function () { + const client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); expect(() => { client .db('test') @@ -210,6 +207,7 @@ describe('class AggregationCursor', () => { }).to.throw(MongoAPIError); }); context('when timeoutMode=ITERATION and a $merge stage is provided', function () { + const client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); expect(() => { client .db('test') From 02f835e171e734ec01dfad02b113069c731c4df3 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 16 Aug 2024 16:03:22 -0400 Subject: [PATCH 09/52] fix test --- test/unit/cursor/aggregation_cursor.test.ts | 43 ++++++++++++--------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/test/unit/cursor/aggregation_cursor.test.ts b/test/unit/cursor/aggregation_cursor.test.ts index 39ab4d60715..82ae18745b0 100644 --- a/test/unit/cursor/aggregation_cursor.test.ts +++ b/test/unit/cursor/aggregation_cursor.test.ts @@ -1,10 +1,11 @@ import { expect } from 'chai'; import { - type AggregationCursor, + AggregationCursor, CursorTimeoutMode, MongoAPIError, - MongoClient + MongoClient, + ns } from '../../mongodb'; describe('class AggregationCursor', () => { @@ -140,21 +141,21 @@ describe('class AggregationCursor', () => { .aggregate([], { timeoutMS: 100, timeoutMode: CursorTimeoutMode.ITERATION }); }); - context('when a $out stage is add with .addStage()', () => { + context('when a $out stage is added with .addStage()', () => { it('throws a MongoAPIError', function () { expect(() => { aggregationCursor.addStage({ $out: 'test' }); }).to.throw(MongoAPIError); }); }); - context('when a $merge stage is add with .addStage()', () => { + context('when a $merge stage is added with .addStage()', () => { it('throws a MongoAPIError', function () { expect(() => { aggregationCursor.addStage({ $merge: {} }); }).to.throw(MongoAPIError); }); }); - context('when a $out stage is add with .out()', () => { + context('when a $out stage is added with .out()', () => { it('throws a MongoAPIError', function () { expect(() => { aggregationCursor.out('test'); @@ -198,22 +199,26 @@ describe('class AggregationCursor', () => { describe('constructor()', () => { context('when CSOT is enabled', () => { context('when timeoutMode=ITERATION and a $out stage is provided', function () { - const client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); - expect(() => { - client - .db('test') - .collection('test') - .aggregate([{ $out: 'test' }], { timeoutMode: CursorTimeoutMode.ITERATION }); - }).to.throw(MongoAPIError); + it('throws a MongoAPIError', function () { + expect( + () => + new AggregationCursor(client, ns('db.coll'), [{ $out: 'test' }], { + timeoutMS: 100, + timeoutMode: 'iteration' + }) + ).to.throw(MongoAPIError); + }); }); context('when timeoutMode=ITERATION and a $merge stage is provided', function () { - const client = new MongoClient('mongodb://iLoveJavascript', { timeoutMS: 100 }); - expect(() => { - client - .db('test') - .collection('test') - .aggregate([{ $merge: 'test' }], { timeoutMode: CursorTimeoutMode.ITERATION }); - }).to.throw(MongoAPIError); + it('throws a MongoAPIError', function () { + expect( + () => + new AggregationCursor(client, ns('db.coll'), [{ $merge: 'test' }], { + timeoutMS: 100, + timeoutMode: 'iteration' + }) + ).to.throw(MongoAPIError); + }); }); }); }); From 1e65e254c23611e7cab3bc5462592dc2feba038b Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 16 Aug 2024 16:03:58 -0400 Subject: [PATCH 10/52] correctly propagate timeoutMode --- src/cursor/abstract_cursor.ts | 1 + src/cursor/aggregation_cursor.ts | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 7ea57f7e867..017e1f7dfd9 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -218,6 +218,7 @@ export abstract class AbstractCursor< ) { throw new MongoAPIError("Cannot set tailable cursor's timeoutMode to LIFETIME"); } + this.cursorOptions.timeoutMode = options.timeoutMode; } } diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index d374ad3c9a8..48f6086d72c 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -45,9 +45,7 @@ export class AggregationCursor extends AbstractCursor { if ( this.cursorOptions.timeoutMS != null && this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - this.pipeline.filter(stage => { - Object.hasOwn(stage, '$out') || Object.hasOwn(stage, '$merge'); - }).length > 0 + this.pipeline.filter(s => s.$out != null || s.$merge != null).length > 0 ) throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } From 0bc7793c700429ebab72e56f905e8d32e2546d80 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 16 Aug 2024 17:40:03 -0400 Subject: [PATCH 11/52] rework test skip logic --- ...lient_side_operations_timeout.spec.test.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index da3b6a70d2f..b4725fefc64 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -10,27 +10,48 @@ const enabled = [ 'retryability-legacy-timeouts', 'retryability-timeoutMS', 'runCursorCommand', - 'close-cursors' + 'close-cursors', + 'non-tailable-cursors', + 'global-timeoutMS', + 'legacy-timeouts', + 'command-execution', + 'cursors', + 'error-transformations' ]; -const bulkWriteOperations = [ - 'timeoutMS applies to whole operation, not individual attempts - bulkWrite on collection', - 'timeoutMS applies to whole operation, not individual attempts - insertMany on collection' -]; +const skipped = { + bulkWrite: 'TODO(NODE-6274)', + 'change-streams': 'TODO(NODE-6035)', + 'convenient-transactions': 'TODO(NODE-5687)', + //'deprecated-options': 'TODO(NODE-5689)', + 'gridfs-advanced': 'TODO(NODE-6275)', + 'gridfs-delete': 'TODO(NODE-6275)', + 'gridfs-download': 'TODO(NODE-6275)', + 'gridfs-find': 'TODO(NODE-6275)', + 'gridfs-upload': 'TODO(NODE-6275)', + 'sessions-inherit-timeoutMS': 'TODO(NODE-5687)', + 'sessions-override-operation-timeoutMS': 'TODO(NODE-5687)', + 'sessions-override-timeoutMS': 'TODO(NODE-5687)', + 'tailable-awaitData': 'TODO(NODE-6035)', + 'tailable-non-awaitData': 'TODO(NODE-6035)' +}; + +const bulkWriteOperations = + /timeoutMS applies to whole operation, not individual attempts - (bulkWrite|insertMany) on .*/; describe('CSOT spec tests', function () { const specs = loadSpecTests(join('client-side-operations-timeout')); for (const spec of specs) { for (const test of spec.tests) { - // not one of the test suites listed in kickoff - if (!enabled.includes(spec.name)) { - test.skipReason = 'TODO(NODE-5684): Not working yet'; + if (skipped[spec.name] != null) { + test.skipReason = skipped[spec.name]; } - if (bulkWriteOperations.includes(test.description)) + if (bulkWriteOperations.test(test.description)) test.skipReason = 'TODO(NODE-6274): update test runner to check errorResponse field of MongoBulkWriteError in isTimeoutError assertion'; } } + runUnifiedSuite(specs); }); From f83d3ef021b6eadb3d0c2022ae449147f2d98c0e Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 15:28:40 -0400 Subject: [PATCH 12/52] wip --- src/cursor/abstract_cursor.ts | 42 ++++++++++++++++++-------------- src/cursor/run_command_cursor.ts | 5 +++- src/timeout.ts | 2 ++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 017e1f7dfd9..e2a118542c8 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -63,7 +63,7 @@ export type CursorFlag = (typeof CURSOR_FLAGS)[number]; /** @internal */ export const CursorTimeoutMode = Object.freeze({ ITERATION: 'iteration', - LIFETIME: 'lifetime' + LIFETIME: 'cursorLifetime' } as const); /** @internal @@ -206,20 +206,20 @@ export abstract class AbstractCursor< this.cursorOptions.timeoutMS = options.timeoutMS; if (this.cursorOptions.timeoutMS != null) { if (options.timeoutMode == null) { - if (this.cursorOptions.tailable) { + if (options.tailable) { this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION; } else { this.cursorOptions.timeoutMode = CursorTimeoutMode.LIFETIME; } } else { - if ( - this.cursorOptions.tailable && - this.cursorOptions.timeoutMode === CursorTimeoutMode.LIFETIME - ) { + if (options.tailable && this.cursorOptions.timeoutMode === CursorTimeoutMode.LIFETIME) { throw new MongoAPIError("Cannot set tailable cursor's timeoutMode to LIFETIME"); } this.cursorOptions.timeoutMode = options.timeoutMode; } + } else { + if (options.timeoutMode != null) + throw new MongoAPIError('Cannot set timeoutMode without setting timeoutMS'); } const readConcern = ReadConcern.fromOptions(options); @@ -444,19 +444,22 @@ export abstract class AbstractCursor< throw new MongoCursorExhaustedError(); } - do { - const doc = this.documents?.shift(this.cursorOptions); - if (doc != null) { - if (this.transform != null) return await this.transformDocument(doc); - return doc; - } - await this.fetchBatch(); - } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + try { + do { + const doc = this.documents?.shift(this.cursorOptions); + if (doc != null) { + if (this.transform != null) return await this.transformDocument(doc); + return doc; + } + await this.fetchBatch(); + } while (!this.isDead || (this.documents?.length ?? 0) !== 0); - if (this.cursorOptions.timeoutMode === 'iteration') { - this.timeoutContext?.refresh(); + return null; + } finally { + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) { + this.timeoutContext?.refresh(); + } } - return null; } /** @@ -689,6 +692,8 @@ export abstract class AbstractCursor< this.cursorId = null; this.documents?.clear(); + this.timeoutContext?.clear(); + this.timeoutContext = undefined; this.isClosed = false; this.isKilled = false; this.initialized = false; @@ -761,7 +766,8 @@ export abstract class AbstractCursor< } const omitMaxTimeMS = this.cursorOptions.timeoutMS != null && - ((this.cursorOptions.timeoutMode === 'iteration' && !this.cursorOptions.tailable) || + ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + !this.cursorOptions.tailable) || (this.cursorOptions.tailable && !this.cursorOptions.awaitData)); try { const state = await this._initialize(this.cursorSession, { diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 5c7d3c8c78a..0522a3ee519 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -12,6 +12,7 @@ import { ns } from '../utils'; import { AbstractCursor, type CursorInitializeOptions, + type CursorTimeoutMode, type InitialCursorResponse } from './abstract_cursor'; @@ -19,6 +20,8 @@ import { export type RunCursorCommandOptions = { readPreference?: ReadPreferenceLike; session?: ClientSession; + timeoutMS?: number; + timeoutMode?: CursorTimeoutMode; } & BSONSerializeOptions; /** @public */ @@ -131,6 +134,6 @@ export class RunCommandCursor extends AbstractCursor { ...this.getMoreOptions }); - return await executeOperation(this.client, getMoreOperation); + return await executeOperation(this.client, getMoreOperation, this.timeoutContext); } } diff --git a/src/timeout.ts b/src/timeout.ts index ba0f080b042..d7ba7b1b0bf 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -209,6 +209,7 @@ export class CSOTTimeoutContext extends TimeoutContext { } get maxTimeMS(): number { + console.log(this.remainingTimeMS, this.minRoundTripTime); return this.remainingTimeMS - this.minRoundTripTime; } @@ -279,6 +280,7 @@ export class CSOTTimeoutContext extends TimeoutContext { refresh(): void { this.start = Math.trunc(performance.now()); + this.minRoundTripTime = 0; this._serverSelectionTimeout?.clear(); this._connectionCheckoutTimeout?.clear(); } From 9549fd47a56d41e20c61446d5636f1dc1a1b9cb4 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 15:29:20 -0400 Subject: [PATCH 13/52] prose tests WIP --- ...ient_side_operations_timeout.prose.test.ts | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts index f98ac862ec7..cc9ddbfd916 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.prose.test.ts @@ -240,8 +240,11 @@ describe('CSOT spec prose tests', function () { beforeEach(async function () { internalClient = this.configuration.newClient(); await internalClient.db('db').dropCollection('coll'); - await internalClient.db('db').createCollection('coll', { capped: true, size: 1_000_000 }); - await internalClient.db('db').collection('coll').insertOne({ x: 1 }); + // Creating capped collection to be able to create tailable find cursor + const coll = await internalClient + .db('db') + .createCollection('coll', { capped: true, size: 1_000_000 }); + await coll.insertOne({ x: 1 }); await internalClient.db().admin().command(failpoint); client = this.configuration.newClient(undefined, { timeoutMS: 20, monitorCommands: true }); @@ -287,14 +290,13 @@ describe('CSOT spec prose tests', function () { * 1. Verify that a `find` command and two `getMore` commands were executed against the `db.coll` collection during the test. */ - it('send correct number of finds and getMores', async function () { + it.skip('send correct number of finds and getMores', async function () { const cursor = client .db('db') .collection('coll') - .find({}, { tailable: true }) + .find({}, { tailable: true, awaitData: true }) .project({ _id: 0 }); const doc = await cursor.next(); - // FIXME: Account for object id expect(doc).to.deep.equal({ x: 1 }); // Check that there are no getMores sent expect(commandStarted.filter(e => e.command.getMore != null)).to.have.lengthOf(0); @@ -305,10 +307,11 @@ describe('CSOT spec prose tests', function () { ); expect(maybeError).to.be.instanceof(MongoOperationTimeoutError); - expect( - commandStarted.filter(e => e.command.find != null || e.command.getMore != null) - ).to.have.lengthOf(3); - }); + // Expect 1 find + expect(commandStarted.filter(e => e.command.find != null)).to.have.lengthOf(1); + // Expect 2 getMore + expect(commandStarted.filter(e => e.command.getMore != null)).to.have.lengthOf(2); + }).skipReason = 'TODO(NODE-6305)'; }); context('Change Streams', () => { @@ -333,7 +336,7 @@ describe('CSOT spec prose tests', function () { * - Expect this to fail with a timeout error. * 1. Verify that an `aggregate` command and two `getMore` commands were executed against the `db.coll` collection during the test. */ - it('sends correct number of aggregate and getMores', async function () { + it.skip('sends correct number of aggregate and getMores', async function () { const changeStream = client.db('db').collection('coll').watch(); const maybeError = await changeStream.next().then( () => null, @@ -341,12 +344,15 @@ describe('CSOT spec prose tests', function () { ); expect(maybeError).to.be.instanceof(MongoOperationTimeoutError); - expect( - commandStarted.filter( - e => Object.hasOwn(e.command, 'aggregate') || Object.hasOwn(e.command, 'getMore') - ) - ).to.have.lengthOf(3); - }); + const aggregates = commandStarted + .filter(e => e.command.aggregate != null) + .map(e => e.command); + const getMores = commandStarted.filter(e => e.command.getMore != null).map(e => e.command); + // Expect 1 aggregate + expect(aggregates).to.have.lengthOf(1); + // Expect 1 getMore + expect(getMores).to.have.lengthOf(1); + }).skipReason = 'TODO(NODE-6305)'; }); }); From 173796b3bb38be8e72749e50b61632da3c3f18ed Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 15:30:45 -0400 Subject: [PATCH 14/52] put CSOT options on runCursorCommand --- ...client_side_operations_timeout.spec.test.ts | 18 +----------------- test/tools/unified-spec-runner/operations.ts | 4 +++- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index b4725fefc64..eb6b467ef3e 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -3,27 +3,11 @@ import { join } from 'path'; import { loadSpecTests } from '../../spec'; import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; -const enabled = [ - 'override-collection-timeoutMS', - 'override-database-timeoutMS', - 'override-operation-timeoutMS', - 'retryability-legacy-timeouts', - 'retryability-timeoutMS', - 'runCursorCommand', - 'close-cursors', - 'non-tailable-cursors', - 'global-timeoutMS', - 'legacy-timeouts', - 'command-execution', - 'cursors', - 'error-transformations' -]; - const skipped = { bulkWrite: 'TODO(NODE-6274)', 'change-streams': 'TODO(NODE-6035)', 'convenient-transactions': 'TODO(NODE-5687)', - //'deprecated-options': 'TODO(NODE-5689)', + 'deprecated-options': 'TODO(NODE-5689)', 'gridfs-advanced': 'TODO(NODE-6275)', 'gridfs-delete': 'TODO(NODE-6275)', 'gridfs-download': 'TODO(NODE-6275)', diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index 0d7fc18970a..3a63053d622 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -772,7 +772,9 @@ operations.set('runCursorCommand', async ({ entities, operation }: OperationFunc const { command, ...opts } = operation.arguments!; const cursor = db.runCursorCommand(command, { readPreference: ReadPreference.fromOptions({ readPreference: opts.readPreference }), - session: opts.session + session: opts.session, + timeoutMode: opts.timeoutMode, + timeoutMS: opts.timeoutMS }); if (!Number.isNaN(+opts.batchSize)) cursor.setBatchSize(+opts.batchSize); From 9fc4c4e3b7bacc20bed3b58fafb1639cc105cbac Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 16:49:55 -0400 Subject: [PATCH 15/52] finish up int tests --- .../node_csot.test.ts | 144 +++++++++++++++--- 1 file changed, 125 insertions(+), 19 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index 1ee0e7e6a71..d9f9056d6fe 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -1,8 +1,11 @@ /* Anything javascript specific relating to timeouts */ +import { setTimeout } from 'node:timers/promises'; + import { expect } from 'chai'; import * as semver from 'semver'; import * as sinon from 'sinon'; +import { type CommandSucceededEvent } from '../../../lib/cmap/command_monitoring_events'; import { BSON, type ClientSession, @@ -326,13 +329,14 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { let client: MongoClient; let internalClient: MongoClient; let commandStarted: CommandStartedEvent[]; + let commandSucceeded: CommandSucceededEvent[]; const failpoint: FailPoint = { configureFailPoint: 'failCommand', mode: 'alwaysOn', data: { - failCommands: ['find'], + failCommands: ['find', 'getMore'], blockConnection: true, - blockTimeMS: 30 + blockTimeMS: 25 } }; @@ -350,9 +354,11 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { await internalClient.db().admin().command(failpoint); - client = this.configuration.newClient(undefined, { timeoutMS: 20, monitorCommands: true }); + client = this.configuration.newClient(undefined, { monitorCommands: true }); commandStarted = []; + commandSucceeded = []; client.on('commandStarted', ev => commandStarted.push(ev)); + client.on('commandSucceeded', ev => commandSucceeded.push(ev)); }); afterEach(async function () { @@ -365,12 +371,12 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }); context('ITERATION mode', () => { - context('when executing a valid operation', () => { + context('when executing an operation', () => { it('must apply the configured timeoutMS to the initial operation execution', async function () { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 3, timeoutMode: 'iteration' }) + .find({}, { batchSize: 3, timeoutMode: 'iteration', timeoutMS: 10 }) .limit(3); const maybeError = await cursor.next().then( @@ -385,25 +391,26 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 1, timeoutMode: 'iteration' }) - .project({ _id: 0 }) - .limit(2); + .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 50 }) + .project({ _id: 0 }); - const firstDoc = await cursor.next(); - expect(firstDoc).to.deep.equal({ x: 1 }); + // Iterating over 3 documents in the collection, each artificially taking ~25 ms due to failpoint. If timeoutMS is not refreshed, then we'd expect to error + for await (const doc of cursor) { + expect(doc).to.deep.equal({ x: 1 }); + } - const maybeError = await cursor.next().then( - () => null, - e => e - ); + const finds = commandSucceeded.filter(ev => ev.commandName === 'find'); + const getMores = commandSucceeded.filter(ev => ev.commandName === 'getMore'); - expect(maybeError).to.be.instanceOf(MongoOperationTimeoutError); + expect(finds).to.have.length(1); // Expecting 1 find + expect(getMores).to.have.length(3); // Expecting 3 getMores (including final empty getMore) }); + it('does not append a maxTimeMS to the original command or getMores', async function () { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 1, timeoutMode: 'iteration' }) + .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 100 }) .project({ _id: 0 }); await cursor.toArray(); @@ -422,17 +429,116 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }); context('LIFETIME mode', () => { + let client: MongoClient; + let internalClient: MongoClient; + let commandStarted: CommandStartedEvent[]; + let commandSucceeded: CommandSucceededEvent[]; + const failpoint: FailPoint = { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { + failCommands: ['find', 'getMore'], + blockConnection: true, + blockTimeMS: 25 + } + }; + + beforeEach(async function () { + internalClient = this.configuration.newClient(undefined); + await internalClient.db('db').dropCollection('coll'); + await internalClient + .db('db') + .collection('coll') + .insertMany( + Array.from({ length: 3 }, () => { + return { x: 1 }; + }) + ); + + await internalClient.db().admin().command(failpoint); + + client = this.configuration.newClient(undefined, { monitorCommands: true }); + commandStarted = []; + commandSucceeded = []; + client.on('commandStarted', ev => commandStarted.push(ev)); + client.on('commandSucceeded', ev => commandSucceeded.push(ev)); + }); + + afterEach(async function () { + await internalClient + .db() + .admin() + .command({ ...failpoint, mode: 'off' }); + await internalClient.close(); + await client.close(); + }); context('when executing a next call', () => { context( 'when there are documents available from previously retrieved batch and timeout has expired', () => { - it('returns documents without error'); + it('returns documents without error', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { timeoutMode: 'cursorLifetime', timeoutMS: 50 }) + .project({ _id: 0 }); + const doc = await cursor.next(); + expect(doc).to.deep.equal({ x: 1 }); + expect(cursor.documents.length).to.be.gt(0); + + await setTimeout(50); + + const docOrErr = await cursor.next().then( + d => d, + e => e + ); + + expect(docOrErr).to.not.be.instanceOf(MongoOperationTimeoutError); + expect(docOrErr).to.be.deep.equal({ x: 1 }); + }); } ); context('when a getMore is required and the timeout has expired', () => { - it('throws a MongoOperationTimeoutError'); + it('throws a MongoOperationTimeoutError', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 50 }) + .project({ _id: 0 }); + const doc = await cursor.next(); + expect(doc).to.deep.equal({ x: 1 }); + expect(cursor.documents.length).to.equal(0); + + await setTimeout(50); + + const docOrErr = await cursor.next().then( + d => d, + e => e + ); + + expect(docOrErr).to.be.instanceOf(MongoOperationTimeoutError); + }); + }); + + it('does not apply maxTimeMS to a getMore', async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 1000 }) + .project({ _id: 0 }); + for await (const _doc of cursor) { + // Ignore _doc + } + + const getMores = commandStarted + .filter(ev => ev.command.getMore != null) + .map(ev => ev.command); + expect(getMores.length).to.be.gt(0); + + for (const getMore of getMores) { + expect(getMore.maxTimeMS).to.not.exist; + } }); - it('does not apply maxTimeMS to a getMore'); }); }); }); From 507af55ac7dfabebf336b1e05d2aa686bfe6bd9c Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 16:51:45 -0400 Subject: [PATCH 16/52] timeoutContext refresh fix --- src/cursor/abstract_cursor.ts | 26 ++++++++++++-------------- src/timeout.ts | 8 +++++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index e2a118542c8..5feba7d3257 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -444,22 +444,20 @@ export abstract class AbstractCursor< throw new MongoCursorExhaustedError(); } - try { - do { - const doc = this.documents?.shift(this.cursorOptions); - if (doc != null) { - if (this.transform != null) return await this.transformDocument(doc); - return doc; - } - await this.fetchBatch(); - } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) { + this.timeoutContext?.refresh(); + } - return null; - } finally { - if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) { - this.timeoutContext?.refresh(); + do { + const doc = this.documents?.shift(this.cursorOptions); + if (doc != null) { + if (this.transform != null) return await this.transformDocument(doc); + return doc; } - } + await this.fetchBatch(); + } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + + return null; } /** diff --git a/src/timeout.ts b/src/timeout.ts index d7ba7b1b0bf..237ad52b88e 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -87,6 +87,7 @@ export class Timeout extends Promise { clear(): void { clearTimeout(this.id); this.id = undefined; + this.timedOut = false; this.cleared = true; } @@ -209,7 +210,6 @@ export class CSOTTimeoutContext extends TimeoutContext { } get maxTimeMS(): number { - console.log(this.remainingTimeMS, this.minRoundTripTime); return this.remainingTimeMS - this.minRoundTripTime; } @@ -282,12 +282,14 @@ export class CSOTTimeoutContext extends TimeoutContext { this.start = Math.trunc(performance.now()); this.minRoundTripTime = 0; this._serverSelectionTimeout?.clear(); - this._connectionCheckoutTimeout?.clear(); + this._serverSelectionTimeout = undefined; + this._connectionCheckoutTimeout = undefined; } clear(): void { this._serverSelectionTimeout?.clear(); - this._connectionCheckoutTimeout?.clear(); + this._serverSelectionTimeout = undefined; + this._connectionCheckoutTimeout = undefined; } } From 1f27040648c38a1c03ed00ad11ae6b6e8b4f4221 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 20 Aug 2024 17:38:58 -0400 Subject: [PATCH 17/52] apply CSOT to hasNext and tryNext --- src/cursor/abstract_cursor.ts | 66 +++++++++++++++++++++-------------- src/timeout.ts | 10 +++--- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 5feba7d3257..0edb9776387 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -428,12 +428,18 @@ export abstract class AbstractCursor< return false; } - do { - if ((this.documents?.length ?? 0) !== 0) { - return true; + try { + do { + if ((this.documents?.length ?? 0) !== 0) { + return true; + } + await this.fetchBatch(); + } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + } finally { + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); } - await this.fetchBatch(); - } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + } return false; } @@ -444,18 +450,20 @@ export abstract class AbstractCursor< throw new MongoCursorExhaustedError(); } - if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) { - this.timeoutContext?.refresh(); - } - - do { - const doc = this.documents?.shift(this.cursorOptions); - if (doc != null) { - if (this.transform != null) return await this.transformDocument(doc); - return doc; + try { + do { + const doc = this.documents?.shift(this.cursorOptions); + if (doc != null) { + if (this.transform != null) return await this.transformDocument(doc); + return doc; + } + await this.fetchBatch(); + } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + } finally { + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); } - await this.fetchBatch(); - } while (!this.isDead || (this.documents?.length ?? 0) !== 0); + } return null; } @@ -468,18 +476,24 @@ export abstract class AbstractCursor< throw new MongoCursorExhaustedError(); } - let doc = this.documents?.shift(this.cursorOptions); - if (doc != null) { - if (this.transform != null) return await this.transformDocument(doc); - return doc; - } + try { + let doc = this.documents?.shift(this.cursorOptions); + if (doc != null) { + if (this.transform != null) return await this.transformDocument(doc); + return doc; + } - await this.fetchBatch(); + await this.fetchBatch(); - doc = this.documents?.shift(this.cursorOptions); - if (doc != null) { - if (this.transform != null) return await this.transformDocument(doc); - return doc; + doc = this.documents?.shift(this.cursorOptions); + if (doc != null) { + if (this.transform != null) return await this.transformDocument(doc); + return doc; + } + } finally { + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); + } } return null; diff --git a/src/timeout.ts b/src/timeout.ts index 237ad52b88e..4e6bf0a5091 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -268,28 +268,26 @@ export class CSOTTimeoutContext extends TimeoutContext { const { remainingTimeMS } = this; if (!Number.isFinite(remainingTimeMS)) return null; if (remainingTimeMS > 0) return Timeout.expires(remainingTimeMS); - throw new MongoOperationTimeoutError('Timed out before socket write'); + throw new MongoOperationTimeoutError(`Timed out before socket write after ${this.timeoutMS}ms`); } get timeoutForSocketRead(): Timeout | null { const { remainingTimeMS } = this; if (!Number.isFinite(remainingTimeMS)) return null; if (remainingTimeMS > 0) return Timeout.expires(remainingTimeMS); - throw new MongoOperationTimeoutError('Timed out before socket read'); + throw new MongoOperationTimeoutError(`Timed out before socket read after ${this.timeoutMS}ms`); } refresh(): void { this.start = Math.trunc(performance.now()); this.minRoundTripTime = 0; this._serverSelectionTimeout?.clear(); - this._serverSelectionTimeout = undefined; - this._connectionCheckoutTimeout = undefined; + this._connectionCheckoutTimeout?.clear(); } clear(): void { this._serverSelectionTimeout?.clear(); - this._serverSelectionTimeout = undefined; - this._connectionCheckoutTimeout = undefined; + this._connectionCheckoutTimeout?.clear(); } } From af02eb8b529ee5b54dfecdebcf2c99f465b92f11 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 21 Aug 2024 14:43:30 -0400 Subject: [PATCH 18/52] sync spec tests --- .../client-side-operations-timeout/README.md | 661 ++++++++++++++++++ .../change-streams.json | 20 +- .../change-streams.yml | 30 +- .../close-cursors.json | 12 +- .../close-cursors.yml | 12 +- .../command-execution.json | 2 +- .../command-execution.yml | 5 +- .../convenient-transactions.json | 22 +- .../convenient-transactions.yml | 15 +- .../deprecated-options.json | 2 +- .../deprecated-options.yml | 2 +- .../gridfs-advanced.yml | 2 +- .../non-tailable-cursors.json | 20 +- .../non-tailable-cursors.yml | 32 +- .../retryability-timeoutMS.json | 250 +++++++ .../retryability-timeoutMS.yml | 100 +++ .../sessions-inherit-timeoutMS.json | 28 +- .../sessions-inherit-timeoutMS.yml | 19 +- ...sessions-override-operation-timeoutMS.json | 32 +- .../sessions-override-operation-timeoutMS.yml | 23 +- .../sessions-override-timeoutMS.json | 28 +- .../sessions-override-timeoutMS.yml | 19 +- .../tailable-awaitData.json | 14 +- .../tailable-awaitData.yml | 18 +- .../tailable-non-awaitData.json | 10 +- .../tailable-non-awaitData.yml | 12 +- 26 files changed, 1249 insertions(+), 141 deletions(-) create mode 100644 test/spec/client-side-operations-timeout/README.md diff --git a/test/spec/client-side-operations-timeout/README.md b/test/spec/client-side-operations-timeout/README.md new file mode 100644 index 00000000000..a960c2de219 --- /dev/null +++ b/test/spec/client-side-operations-timeout/README.md @@ -0,0 +1,661 @@ +# Client Side Operations Timeouts Tests + +______________________________________________________________________ + +## Introduction + +This document describes the tests that drivers MUST run to validate the behavior of the timeoutMS option. These tests +are broken up into automated YAML/JSON tests and additional prose tests. + +## Spec Tests + +This directory contains a set of YAML and JSON spec tests. Drivers MUST run these as described in the "Unified Test +Runner" specification. Because the tests introduced in this specification are timing-based, there is a risk that some of +them may intermittently fail without any bugs being present in the driver. As a mitigation, drivers MAY execute these +tests in two new Evergreen tasks that use single-node replica sets: one with only authentication enabled and another +with both authentication and TLS enabled. Drivers that choose to do so SHOULD use the `single-node-auth.json` and +`single-node-auth-ssl.json` files in the `drivers-evergreen-tools` repository to create these clusters. + +## Prose Tests + +There are some tests that cannot be expressed in the unified YAML/JSON format. For each of these tests, drivers MUST +create a MongoClient without the `timeoutMS` option set (referred to as `internalClient`). Any fail points set during a +test MUST be unset using `internalClient` after the test has been executed. All MongoClient instances created for tests +MUST be configured with read/write concern `majority`, read preference `primary`, and command monitoring enabled to +listen for `command_started` events. + +### 1. Multi-batch inserts + +This test MUST only run against standalones on server versions 4.4 and higher. The `insertMany` call takes an +exceedingly long time on replicasets and sharded clusters. Drivers MAY adjust the timeouts used in this test to allow +for differing bulk encoding performance. + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 2 + }, + data: { + failCommands: ["insert"], + blockConnection: true, + blockTimeMS: 1010 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) with `timeoutMS=2000`. + +4. Using `client`, insert 50 1-megabyte documents in a single `insertMany` call. + + - Expect this to fail with a timeout error. + +5. Verify that two `insert` commands were executed against `db.coll` as part of the `insertMany` call. + +### 2. maxTimeMS is not set for commands sent to mongocryptd + +This test MUST only be run against enterprise server versions 4.2 and higher. + +1. Launch a mongocryptd process on 23000. +2. Create a MongoClient (referred to as `client`) using the URI `mongodb://localhost:23000/?timeoutMS=1000`. +3. Using `client`, execute the `{ ping: 1 }` command against the `admin` database. +4. Verify via command monitoring that the `ping` command sent did not contain a `maxTimeMS` field. + +### 3. ClientEncryption + +Each test under this category MUST only be run against server versions 4.4 and higher. In these tests, `LOCAL_MASTERKEY` +refers to the following base64: + +```javascript +Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk +``` + +For each test, perform the following setup: + +1. Using `internalClient`, drop and create the `keyvault.datakeys` collection. + +2. Create a MongoClient (referred to as `keyVaultClient`) with `timeoutMS=10`. + +3. Create a `ClientEncryption` object that wraps `keyVaultClient` (referred to as `clientEncryption`). Configure this + object with `keyVaultNamespace` set to `keyvault.datakeys` and the following KMS providers map: + + ```javascript + { + "local": { "key": } + } + ``` + +#### createDataKey + +1. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: ["insert"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +2. Call `clientEncryption.createDataKey()` with the `local` KMS provider. + + - Expect this to fail with a timeout error. + +3. Verify that an `insert` command was executed against to `keyvault.datakeys` as part of the `createDataKey` call. + +#### encrypt + +1. Call `client_encryption.createDataKey()` with the `local` KMS provider. + + - Expect a BSON binary with subtype 4 to be returned, referred to as `datakeyId`. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: ["find"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Call `clientEncryption.encrypt()` with the value `hello`, the algorithm + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`, and the keyId `datakeyId`. + + - Expect this to fail with a timeout error. + +4. Verify that a `find` command was executed against the `keyvault.datakeys` collection as part of the `encrypt` call. + +#### decrypt + +1. Call `clientEncryption.createDataKey()` with the `local` KMS provider. + + - Expect this to return a BSON binary with subtype 4, referred to as `dataKeyId`. + +2. Call `clientEncryption.encrypt()` with the value `hello`, the algorithm + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`, and the keyId `dataKeyId`. + + - Expect this to return a BSON binary with subtype 6, referred to as `encrypted`. + +3. Close and re-create the `keyVaultClient` and `clientEncryption` objects. + +4. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: ["find"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +5. Call `clientEncryption.decrypt()` with the value `encrypted`. + + - Expect this to fail with a timeout error. + +6. Verify that a `find` command was executed against the `keyvault.datakeys` collection as part of the `decrypt` call. + +### 4. Background Connection Pooling + +The tests in this section MUST only be run if the server version is 4.4 or higher and the URI has authentication fields +(i.e. a username and password). Each test in this section requires drivers to create a MongoClient and then wait for +some CMAP events to be published. Drivers MUST wait for up to 10 seconds and fail the test if the specified events are +not published within that time. + +#### timeoutMS used for handshake commands + +1. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: ["saslContinue"], + blockConnection: true, + blockTimeMS: 15, + appName: "timeoutBackgroundPoolTest" + } + } + ``` + +2. Create a MongoClient (referred to as `client`) configured with the following: + + - `minPoolSize` of 1 + - `timeoutMS` of 10 + - `appName` of `timeoutBackgroundPoolTest` + - CMAP monitor configured to listen for `ConnectionCreatedEvent` and `ConnectionClosedEvent` events. + +3. Wait for a `ConnectionCreatedEvent` and a `ConnectionClosedEvent` to be published. + +#### timeoutMS is refreshed for each handshake command + +1. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: "alwaysOn", + data: { + failCommands: ["hello", "isMaster", "saslContinue"], + blockConnection: true, + blockTimeMS: 15, + appName: "refreshTimeoutBackgroundPoolTest" + } + } + ``` + +2. Create a MongoClient (referred to as `client`) configured with the following: + + - `minPoolSize` of 1 + - `timeoutMS` of 20 + - `appName` of `refreshTimeoutBackgroundPoolTest` + - CMAP monitor configured to listen for `ConnectionCreatedEvent` and `ConnectionReady` events. + +3. Wait for a `ConnectionCreatedEvent` and a `ConnectionReady` to be published. + +### 5. Blocking Iteration Methods + +Tests in this section MUST only be run against server versions 4.4 and higher and only apply to drivers that have a +blocking method for cursor iteration that executes `getMore` commands in a loop until a document is available or an +error occurs. + +#### Tailable cursors + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, insert the document `{ x: 1 }` into `db.coll`. + +3. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: "alwaysOn", + data: { + failCommands: ["getMore"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +4. Create a new MongoClient (referred to as `client`) with `timeoutMS=20`. + +5. Using `client`, create a tailable cursor on `db.coll` with `cursorType=tailable`. + + - Expect this to succeed and return a cursor with a non-zero ID. + +6. Call either a blocking or non-blocking iteration method on the cursor. + + - Expect this to succeed and return the document `{ x: 1 }` without sending a `getMore` command. + +7. Call the blocking iteration method on the resulting cursor. + + - Expect this to fail with a timeout error. + +8. Verify that a `find` command and two `getMore` commands were executed against the `db.coll` collection during the + test. + +#### Change Streams + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: "alwaysOn", + data: { + failCommands: ["getMore"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) with `timeoutMS=20`. + +4. Using `client`, use the `watch` helper to create a change stream against `db.coll`. + + - Expect this to succeed and return a change stream with a non-zero ID. + +5. Call the blocking iteration method on the resulting change stream. + + - Expect this to fail with a timeout error. + +6. Verify that an `aggregate` command and two `getMore` commands were executed against the `db.coll` collection during + the test. + +### 6. GridFS - Upload + +Tests in this section MUST only be run against server versions 4.4 and higher. + +#### uploads via openUploadStream can be timed out + +1. Using `internalClient`, drop and re-create the `db.fs.files` and `db.fs.chunks` collections. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["insert"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) with `timeoutMS=10`. + +4. Using `client`, create a GridFS bucket (referred to as `bucket`) that wraps the `db` database. + +5. Call `bucket.open_upload_stream()` with the filename `filename` to create an upload stream (referred to as + `uploadStream`). + + - Expect this to succeed and return a non-null stream. + +6. Using `uploadStream`, upload a single `0x12` byte. + +7. Call `uploadStream.close()` to flush the stream and insert chunks. + + - Expect this to fail with a timeout error. + +#### Aborting an upload stream can be timed out + +This test only applies to drivers that provide an API to abort a GridFS upload stream. + +1. Using `internalClient`, drop and re-create the `db.fs.files` and `db.fs.chunks` collections. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["delete"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) with `timeoutMS=10`. + +4. Using `client`, create a GridFS bucket (referred to as `bucket`) that wraps the `db` database with + `chunkSizeBytes=2`. + +5. Call `bucket.open_upload_stream()` with the filename `filename` to create an upload stream (referred to as + `uploadStream`). + + - Expect this to succeed and return a non-null stream. + +6. Using `uploadStream`, upload the bytes `[0x01, 0x02, 0x03, 0x04]`. + +7. Call `uploadStream.abort()`. + + - Expect this to fail with a timeout error. + +### 7. GridFS - Download + +This test MUST only be run against server versions 4.4 and higher. + +1. Using `internalClient`, drop and re-create the `db.fs.files` and `db.fs.chunks` collections. + +2. Using `internalClient`, insert the following document into the `db.fs.files` collection: + + ```javascript + { + "_id": { + "$oid": "000000000000000000000005" + }, + "length": 10, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "md5": "57d83cd477bfb1ccd975ab33d827a92b", + "filename": "length-10", + "contentType": "application/octet-stream", + "aliases": [], + "metadata": {} + } + ``` + +3. Create a new MongoClient (referred to as `client`) with `timeoutMS=10`. + +4. Using `client`, create a GridFS bucket (referred to as `bucket`) that wraps the `db` database. + +5. Call `bucket.open_download_stream` with the id `{ "$oid": "000000000000000000000005" }` to create a download stream + (referred to as `downloadStream`). + + - Expect this to succeed and return a non-null stream. + +6. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["find"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +7. Read from the `downloadStream`. + + - Expect this to fail with a timeout error. + +8. Verify that two `find` commands were executed during the read: one against `db.fs.files` and another against + `db.fs.chunks`. + +### 8. Server Selection + +#### serverSelectionTimeoutMS honored if timeoutMS is not set + +1. Create a MongoClient (referred to as `client`) with URI `mongodb://invalid/?serverSelectionTimeoutMS=10`. +2. Using `client`, execute the command `{ ping: 1 }` against the `admin` database. + - Expect this to fail with a server selection timeout error after no more than 15ms. + +#### timeoutMS honored for server selection if it's lower than serverSelectionTimeoutMS + +1. Create a MongoClient (referred to as `client`) with URI + `mongodb://invalid/?timeoutMS=10&serverSelectionTimeoutMS=20`. +2. Using `client`, run the command `{ ping: 1 }` against the `admin` database. + - Expect this to fail with a server selection timeout error after no more than 15ms. + +#### serverSelectionTimeoutMS honored for server selection if it's lower than timeoutMS + +1. Create a MongoClient (referred to as `client`) with URI + `mongodb://invalid/?timeoutMS=20&serverSelectionTimeoutMS=10`. +2. Using `client`, run the command `{ ping: 1 }` against the `admin` database. + - Expect this to fail with a server selection timeout error after no more than 15ms. + +#### serverSelectionTimeoutMS honored for server selection if timeoutMS=0 + +1. Create a MongoClient (referred to as `client`) with URI `mongodb://invalid/?timeoutMS=0&serverSelectionTimeoutMS=10`. +2. Using `client`, run the command `{ ping: 1 }` against the `admin` database. + - Expect this to fail with a server selection timeout error after no more than 15ms. + +#### timeoutMS honored for connection handshake commands if it's lower than serverSelectionTimeoutMS + +This test MUST only be run if the server version is 4.4 or higher and the URI has authentication fields (i.e. a username +and password). + +1. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: failCommand, + mode: { times: 1 }, + data: { + failCommands: ["saslContinue"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +2. Create a new MongoClient (referred to as `client`) with `timeoutMS=10` and `serverSelectionTimeoutMS=20`. + +3. Using `client`, insert the document `{ x: 1 }` into collection `db.coll`. + + - Expect this to fail with a timeout error after no more than 15ms. + +#### serverSelectionTimeoutMS honored for connection handshake commands if it's lower than timeoutMS + +This test MUST only be run if the server version is 4.4 or higher and the URI has authentication fields (i.e. a username +and password). + +1. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: failCommand, + mode: { times: 1 }, + data: { + failCommands: ["saslContinue"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +2. Create a new MongoClient (referred to as `client`) with `timeoutMS=20` and `serverSelectionTimeoutMS=10`. + +3. Using `client`, insert the document `{ x: 1 }` into collection `db.coll`. + + - Expect this to fail with a timeout error after no more than 15ms. + +### 9. endSession + +This test MUST only be run against replica sets and sharded clusters with server version 4.4 or higher. It MUST be run +three times: once with the timeout specified via the MongoClient `timeoutMS` option, once with the timeout specified via +the ClientSession `defaultTimeoutMS` option, and once more with the timeout specified via the `timeoutMS` option for the +`endSession` operation. In all cases, the timeout MUST be set to 10 milliseconds. + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: failCommand, + mode: { times: 1 }, + data: { + failCommands: ["abortTransaction"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) and an explicit ClientSession derived from that MongoClient + (referred to as `session`). + +4. Execute the following code: + + ```typescript + coll = client.database("db").collection("coll") + session.start_transaction() + coll.insert_one({x: 1}, session=session) + ``` + +5. Using `session`, execute `session.end_session` + + - Expect this to fail with a timeout error after no more than 15ms. + +### 10. Convenient Transactions + +Tests in this section MUST only run against replica sets and sharded clusters with server versions 4.4 or higher. + +#### timeoutMS is refreshed for abortTransaction if the callback fails + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: failCommand, + mode: { times: 2 }, + data: { + failCommands: ["insert", "abortTransaction"], + blockConnection: true, + blockTimeMS: 15 + } + } + ``` + +3. Create a new MongoClient (referred to as `client`) configured with `timeoutMS=10` and an explicit ClientSession + derived from that MongoClient (referred to as `session`). + +4. Using `session`, execute a `withTransaction` operation with the following callback: + + ```typescript + def callback() { + coll = client.database("db").collection("coll") + coll.insert_one({ _id: 1 }, session=session) + } + ``` + +5. Expect the previous `withTransaction` call to fail with a timeout error. + +6. Verify that the following events were published during the `withTransaction` call: + + 1. `command_started` and `command_failed` events for an `insert` command. + 2. `command_started` and `command_failed` events for an `abortTransaction` command. + +### 11. Multi-batch bulkWrites + +This test MUST only run against server versions 8.0+. + +1. Using `internalClient`, drop the `db.coll` collection. + +2. Using `internalClient`, set the following fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { + times: 2 + }, + data: { + failCommands: ["bulkWrite"], + blockConnection: true, + blockTimeMS: 1010 + } + } + ``` + +3. Using `internalClient`, perform a `hello` command and record the `maxBsonObjectSize` and `maxMessageSizeBytes` values + in the response. + +4. Create a new MongoClient (referred to as `client`) with `timeoutMS=2000`. + +5. Create a list of write models (referred to as `models`) with the following write model repeated + (`maxMessageSizeBytes / maxBsonObjectSize + 1`) times: + + ```json + InsertOne { + "namespace": "db.coll", + "document": { "a": "b".repeat(maxBsonObjectSize - 500) } + } + ``` + +6. Call `bulkWrite` on `client` with `models`. + + - Expect this to fail with a timeout error. + +7. Verify that two `bulkWrite` commands were executed as part of the `MongoClient.bulkWrite` call. + +## Unit Tests + +The tests enumerated in this section could not be expressed in either spec or prose format. Drivers SHOULD implement +these if it is possible to do so using the driver's existing test infrastructure. + +- Operations should ignore `waitQueueTimeoutMS` if `timeoutMS` is also set. +- If `timeoutMS` is set for an operation, the remaining `timeoutMS` value should apply to connection checkout after a + server has been selected. +- If `timeoutMS` is not set for an operation, `waitQueueTimeoutMS` should apply to connection checkout after a server + has been selected. +- If a new connection is required to execute an operation, + `min(remaining computedServerSelectionTimeout, connectTimeoutMS)` should apply to socket establishment. +- For drivers that have control over OCSP behavior, `min(remaining computedServerSelectionTimeout, 5 seconds)` should + apply to HTTP requests against OCSP responders. +- If `timeoutMS` is unset, operations fail after two non-consecutive socket timeouts. +- The remaining `timeoutMS` value should apply to HTTP requests against KMS servers for CSFLE. +- The remaining `timeoutMS` value should apply to commands sent to mongocryptd as part of automatic encryption. +- When doing `minPoolSize` maintenance, `connectTimeoutMS` is used as the timeout for socket establishment. diff --git a/test/spec/client-side-operations-timeout/change-streams.json b/test/spec/client-side-operations-timeout/change-streams.json index aef77bb452d..8cffb08e267 100644 --- a/test/spec/client-side-operations-timeout/change-streams.json +++ b/test/spec/client-side-operations-timeout/change-streams.json @@ -104,7 +104,7 @@ "aggregate" ], "blockConnection": true, - "blockTimeMS": 55 + "blockTimeMS": 250 } } } @@ -114,7 +114,7 @@ "object": "collection", "arguments": { "pipeline": [], - "timeoutMS": 50 + "timeoutMS": 200 }, "expectError": { "isTimeoutError": true @@ -242,7 +242,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 150 } } } @@ -252,7 +252,7 @@ "object": "collection", "arguments": { "pipeline": [], - "timeoutMS": 20, + "timeoutMS": 200, "batchSize": 2, "maxAwaitTimeMS": 1 }, @@ -310,7 +310,7 @@ "object": "collection", "arguments": { "pipeline": [], - "timeoutMS": 20 + "timeoutMS": 200 }, "saveResultAsEntity": "changeStream" }, @@ -330,7 +330,7 @@ "aggregate" ], "blockConnection": true, - "blockTimeMS": 12, + "blockTimeMS": 120, "errorCode": 7, "errorLabels": [ "ResumableChangeStreamError" @@ -412,7 +412,7 @@ "arguments": { "pipeline": [], "maxAwaitTimeMS": 1, - "timeoutMS": 100 + "timeoutMS": 200 }, "saveResultAsEntity": "changeStream" }, @@ -431,7 +431,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 150 + "blockTimeMS": 250 } } } @@ -534,7 +534,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } @@ -544,7 +544,7 @@ "object": "collection", "arguments": { "pipeline": [], - "timeoutMS": 10 + "timeoutMS": 200 }, "saveResultAsEntity": "changeStream" }, diff --git a/test/spec/client-side-operations-timeout/change-streams.yml b/test/spec/client-side-operations-timeout/change-streams.yml index b2a052d01b2..c813be035ac 100644 --- a/test/spec/client-side-operations-timeout/change-streams.yml +++ b/test/spec/client-side-operations-timeout/change-streams.yml @@ -67,12 +67,12 @@ tests: data: failCommands: ["aggregate"] blockConnection: true - blockTimeMS: 55 + blockTimeMS: 250 - name: createChangeStream object: *collection arguments: pipeline: [] - timeoutMS: 50 + timeoutMS: 200 expectError: isTimeoutError: true expectEvents: @@ -142,12 +142,12 @@ tests: data: failCommands: ["aggregate", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 150 - name: createChangeStream object: *collection arguments: pipeline: [] - timeoutMS: 20 + timeoutMS: 200 batchSize: 2 maxAwaitTimeMS: 1 saveResultAsEntity: &changeStream changeStream @@ -171,16 +171,16 @@ tests: maxTimeMS: 1 # The timeout should be applied to the entire resume attempt, not individually to each command. The test creates a - # change stream with timeoutMS=20 which returns an empty initial batch and then sets a fail point to block both - # getMore and aggregate for 12ms each and fail with a resumable error. When the resume attempt happens, the getMore - # and aggregate block for longer than 20ms total, so it times out. + # change stream with timeoutMS=200 which returns an empty initial batch and then sets a fail point to block both + # getMore and aggregate for 120ms each and fail with a resumable error. When the resume attempt happens, the getMore + # and aggregate block for longer than 200ms total, so it times out. - description: "timeoutMS applies to full resume attempt in a next call" operations: - name: createChangeStream object: *collection arguments: pipeline: [] - timeoutMS: 20 + timeoutMS: 200 saveResultAsEntity: &changeStream changeStream - name: failPoint object: testRunner @@ -192,7 +192,7 @@ tests: data: failCommands: ["getMore", "aggregate"] blockConnection: true - blockTimeMS: 12 + blockTimeMS: 120 errorCode: 7 # HostNotFound - resumable but does not require an SDAM state change. # failCommand doesn't correctly add the ResumableChangeStreamError by default. It needs to be specified # manually here so the error is considered resumable. The failGetMoreAfterCursorCheckout fail point @@ -234,9 +234,9 @@ tests: # Specify a short maxAwaitTimeMS because otherwise the getMore on the new cursor will wait for 1000ms and # time out. maxAwaitTimeMS: 1 - timeoutMS: 100 + timeoutMS: 200 saveResultAsEntity: &changeStream changeStream - # Block getMore for 150ms to force the next() call to time out. + # Block getMore for 250ms to force the next() call to time out. - name: failPoint object: testRunner arguments: @@ -247,7 +247,7 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 150 + blockTimeMS: 250 # The original aggregate didn't return any events so this should do a getMore and return a timeout error. - name: iterateUntilDocumentOrError object: *changeStream @@ -290,7 +290,7 @@ tests: collection: *collectionName # The timeoutMS value should be refreshed for getMore's. This is a failure test. The createChangeStream operation - # sets timeoutMS=10 and the getMore blocks for 15ms, causing iteration to fail with a timeout error. + # sets timeoutMS=200 and the getMore blocks for 250ms, causing iteration to fail with a timeout error. - description: "timeoutMS is refreshed for getMore - failure" operations: - name: failPoint @@ -303,12 +303,12 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: createChangeStream object: *collection arguments: pipeline: [] - timeoutMS: 10 + timeoutMS: 200 saveResultAsEntity: &changeStream changeStream # The first iteration should do a getMore - name: iterateUntilDocumentOrError diff --git a/test/spec/client-side-operations-timeout/close-cursors.json b/test/spec/client-side-operations-timeout/close-cursors.json index 1361971c4ce..79b0de7b6aa 100644 --- a/test/spec/client-side-operations-timeout/close-cursors.json +++ b/test/spec/client-side-operations-timeout/close-cursors.json @@ -75,7 +75,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 50 + "blockTimeMS": 250 } } } @@ -86,7 +86,7 @@ "arguments": { "filter": {}, "batchSize": 2, - "timeoutMS": 20 + "timeoutMS": 200 }, "saveResultAsEntity": "cursor" }, @@ -175,7 +175,7 @@ "killCursors" ], "blockConnection": true, - "blockTimeMS": 30 + "blockTimeMS": 250 } } } @@ -186,7 +186,7 @@ "arguments": { "filter": {}, "batchSize": 2, - "timeoutMS": 20 + "timeoutMS": 200 }, "saveResultAsEntity": "cursor" }, @@ -194,7 +194,7 @@ "name": "close", "object": "cursor", "arguments": { - "timeoutMS": 40 + "timeoutMS": 400 } } ], @@ -215,7 +215,7 @@ { "commandStartedEvent": { "command": { - "killCursors": "collection", + "killCursors": "coll", "maxTimeMS": { "$$type": [ "int", diff --git a/test/spec/client-side-operations-timeout/close-cursors.yml b/test/spec/client-side-operations-timeout/close-cursors.yml index db26e79ca31..c4c4ea0acda 100644 --- a/test/spec/client-side-operations-timeout/close-cursors.yml +++ b/test/spec/client-side-operations-timeout/close-cursors.yml @@ -46,13 +46,13 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 50 + blockTimeMS: 250 - name: createFindCursor object: *collection arguments: filter: {} batchSize: 2 - timeoutMS: 20 + timeoutMS: 200 saveResultAsEntity: &cursor cursor # Iterate the cursor three times. The third should do a getMore, which should fail with a timeout error. - name: iterateUntilDocumentOrError @@ -99,18 +99,18 @@ tests: data: failCommands: ["killCursors"] blockConnection: true - blockTimeMS: 30 + blockTimeMS: 250 - name: createFindCursor object: *collection arguments: filter: {} batchSize: 2 - timeoutMS: 20 + timeoutMS: 200 saveResultAsEntity: &cursor cursor - name: close object: *cursor arguments: - timeoutMS: 40 + timeoutMS: 400 expectEvents: - client: *client events: @@ -120,7 +120,7 @@ tests: commandName: find - commandStartedEvent: command: - killCursors: *collection + killCursors: *collectionName maxTimeMS: { $$type: ["int", "long"] } commandName: killCursors - commandSucceededEvent: diff --git a/test/spec/client-side-operations-timeout/command-execution.json b/test/spec/client-side-operations-timeout/command-execution.json index b9b306c7fb6..aa9c3eb23f3 100644 --- a/test/spec/client-side-operations-timeout/command-execution.json +++ b/test/spec/client-side-operations-timeout/command-execution.json @@ -3,7 +3,7 @@ "schemaVersion": "1.9", "runOnRequirements": [ { - "minServerVersion": "4.9", + "minServerVersion": "4.4.7", "topologies": [ "single", "replicaset", diff --git a/test/spec/client-side-operations-timeout/command-execution.yml b/test/spec/client-side-operations-timeout/command-execution.yml index 400a90867a3..6ba0585b3ca 100644 --- a/test/spec/client-side-operations-timeout/command-execution.yml +++ b/test/spec/client-side-operations-timeout/command-execution.yml @@ -3,9 +3,8 @@ description: "timeoutMS behaves correctly during command execution" schemaVersion: "1.9" runOnRequirements: - # The appName filter cannot be used to set a fail point on connection handshakes until server version 4.9 due to - # SERVER-49220/SERVER-49336. - - minServerVersion: "4.9" + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: "4.4.7" # Skip load-balanced and serverless which do not support RTT measurements. topologies: [ single, replicaset, sharded ] serverless: forbid diff --git a/test/spec/client-side-operations-timeout/convenient-transactions.json b/test/spec/client-side-operations-timeout/convenient-transactions.json index 07e676d5f51..3868b3026c2 100644 --- a/test/spec/client-side-operations-timeout/convenient-transactions.json +++ b/test/spec/client-side-operations-timeout/convenient-transactions.json @@ -21,7 +21,7 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 50 + "timeoutMS": 500 }, "useMultipleMongoses": false, "observeEvents": [ @@ -81,6 +81,9 @@ } } ] + }, + "expectError": { + "isClientError": true } } ], @@ -109,7 +112,7 @@ "insert" ], "blockConnection": true, - "blockTimeMS": 30 + "blockTimeMS": 300 } } } @@ -182,6 +185,21 @@ } } } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction", + "databaseName": "admin", + "command": { + "abortTransaction": 1, + "maxTimeMS": { + "$$type": [ + "int", + "long" + ] + } + } + } } ] } diff --git a/test/spec/client-side-operations-timeout/convenient-transactions.yml b/test/spec/client-side-operations-timeout/convenient-transactions.yml index d79aa4bd058..02d48b83242 100644 --- a/test/spec/client-side-operations-timeout/convenient-transactions.yml +++ b/test/spec/client-side-operations-timeout/convenient-transactions.yml @@ -13,7 +13,7 @@ createEntities: - client: id: &client client uriOptions: - timeoutMS: 50 + timeoutMS: 500 useMultipleMongoses: false observeEvents: - commandStartedEvent @@ -49,6 +49,8 @@ tests: timeoutMS: 100 expectError: isClientError: true + expectError: + isClientError: true expectEvents: # The only operation run fails with a client-side error, so there should be no events for the client. - client: *client @@ -66,7 +68,7 @@ tests: data: failCommands: ["insert"] blockConnection: true - blockTimeMS: 30 + blockTimeMS: 300 - name: withTransaction object: *session arguments: @@ -88,9 +90,6 @@ tests: expectEvents: - client: *client events: - # Because the second insert expects an error and gets an error, it technically succeeds, so withTransaction - # will try to run commitTransaction. This will fail client-side, though, because the timeout has already - # expired, so no command is sent. - commandStartedEvent: commandName: insert databaseName: *databaseName @@ -103,3 +102,9 @@ tests: command: insert: *collectionName maxTimeMS: { $$type: ["int", "long"] } + - commandStartedEvent: + commandName: abortTransaction + databaseName: admin + command: + abortTransaction: 1 + maxTimeMS: { $$type: [ "int", "long" ] } diff --git a/test/spec/client-side-operations-timeout/deprecated-options.json b/test/spec/client-side-operations-timeout/deprecated-options.json index 322e9449101..d3e4631ff43 100644 --- a/test/spec/client-side-operations-timeout/deprecated-options.json +++ b/test/spec/client-side-operations-timeout/deprecated-options.json @@ -1,5 +1,5 @@ { - "description": "operations ignore deprected timeout options if timeoutMS is set", + "description": "operations ignore deprecated timeout options if timeoutMS is set", "schemaVersion": "1.9", "runOnRequirements": [ { diff --git a/test/spec/client-side-operations-timeout/deprecated-options.yml b/test/spec/client-side-operations-timeout/deprecated-options.yml index 461ba6ab139..582a8983ae2 100644 --- a/test/spec/client-side-operations-timeout/deprecated-options.yml +++ b/test/spec/client-side-operations-timeout/deprecated-options.yml @@ -1,4 +1,4 @@ -description: "operations ignore deprected timeout options if timeoutMS is set" +description: "operations ignore deprecated timeout options if timeoutMS is set" schemaVersion: "1.9" diff --git a/test/spec/client-side-operations-timeout/gridfs-advanced.yml b/test/spec/client-side-operations-timeout/gridfs-advanced.yml index bc788bacc35..f6c37e165b2 100644 --- a/test/spec/client-side-operations-timeout/gridfs-advanced.yml +++ b/test/spec/client-side-operations-timeout/gridfs-advanced.yml @@ -119,7 +119,7 @@ tests: update: *filesCollectionName maxTimeMS: { $$type: ["int", "long"] } - # Tests for the "drop" opration. Any tests that might result in multiple commands being sent do not have expectEvents + # Tests for the "drop" operation. Any tests that might result in multiple commands being sent do not have expectEvents # assertions as these assertions reduce test robustness and can cause flaky failures. - description: "timeoutMS can be overridden for drop" diff --git a/test/spec/client-side-operations-timeout/non-tailable-cursors.json b/test/spec/client-side-operations-timeout/non-tailable-cursors.json index 0a5448a6bb2..291c6e72aa1 100644 --- a/test/spec/client-side-operations-timeout/non-tailable-cursors.json +++ b/test/spec/client-side-operations-timeout/non-tailable-cursors.json @@ -17,7 +17,7 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 10 + "timeoutMS": 200 }, "useMultipleMongoses": false, "observeEvents": [ @@ -84,7 +84,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } @@ -143,7 +143,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 125 } } } @@ -153,7 +153,7 @@ "object": "collection", "arguments": { "filter": {}, - "timeoutMS": 20, + "timeoutMS": 200, "batchSize": 2 }, "expectError": { @@ -221,7 +221,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 150 } } } @@ -232,7 +232,7 @@ "arguments": { "filter": {}, "timeoutMode": "cursorLifetime", - "timeoutMS": 20, + "timeoutMS": 200, "batchSize": 2 }, "expectError": { @@ -299,7 +299,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } @@ -355,7 +355,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 125 } } } @@ -366,7 +366,7 @@ "arguments": { "filter": {}, "timeoutMode": "iteration", - "timeoutMS": 20, + "timeoutMS": 200, "batchSize": 2 } } @@ -427,7 +427,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } diff --git a/test/spec/client-side-operations-timeout/non-tailable-cursors.yml b/test/spec/client-side-operations-timeout/non-tailable-cursors.yml index 8cd953dec45..29037b4c0a3 100644 --- a/test/spec/client-side-operations-timeout/non-tailable-cursors.yml +++ b/test/spec/client-side-operations-timeout/non-tailable-cursors.yml @@ -12,7 +12,7 @@ createEntities: - client: id: &client client uriOptions: - timeoutMS: 10 + timeoutMS: 200 useMultipleMongoses: false observeEvents: - commandStartedEvent @@ -53,7 +53,7 @@ tests: data: failCommands: ["find"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: find object: *collection arguments: @@ -86,14 +86,14 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 - # Run a find with timeoutMS=20 and batchSize=1 to force two batches, which will cause a find and a getMore to be - # sent. Both will block for 15ms so together they will go over the timeout. + blockTimeMS: 125 + # Run a find with timeoutMS=200 and batchSize=1 to force two batches, which will cause a find and a getMore to be + # sent. Both will block for 125ms, so together they will go over the timeout. - name: find object: *collection arguments: filter: {} - timeoutMS: 20 + timeoutMS: 200 batchSize: 2 expectError: isTimeoutError: true @@ -127,13 +127,13 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 150 - name: find object: *collection arguments: filter: {} timeoutMode: cursorLifetime - timeoutMS: 20 + timeoutMS: 200 batchSize: 2 expectError: isTimeoutError: true @@ -168,7 +168,7 @@ tests: data: failCommands: ["find"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: find object: *collection arguments: @@ -187,8 +187,8 @@ tests: maxTimeMS: { $$exists: false } # If timeoutMode=ITERATION, timeoutMS applies separately to the initial find and the getMore on the cursor. Neither - # command should have a maxTimeMS field. This is a success test. The "find" is executed with timeoutMS=20 and both - # "find" and "getMore" commands are blocked for 15ms each. Neither exceeds the timeout, so iteration succeeds. + # command should have a maxTimeMS field. This is a success test. The "find" is executed with timeoutMS=200 and both + # "find" and "getMore" commands are blocked for 125ms each. Neither exceeds the timeout, so iteration succeeds. - description: "timeoutMS is refreshed for getMore if timeoutMode is iteration - success" operations: - name: failPoint @@ -201,13 +201,13 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 125 - name: find object: *collection arguments: filter: {} timeoutMode: iteration - timeoutMS: 20 + timeoutMS: 200 batchSize: 2 expectEvents: - client: *client @@ -227,8 +227,8 @@ tests: maxTimeMS: { $$exists: false } # If timeoutMode=ITERATION, timeoutMS applies separately to the initial find and the getMore on the cursor. Neither - # command should have a maxTimeMS field. This is a failure test. The "find" inherits timeoutMS=10 and "getMore" - # commands are blocked for 15ms, causing iteration to fail with a timeout error. + # command should have a maxTimeMS field. This is a failure test. The "find" inherits timeoutMS=200 and "getMore" + # commands are blocked for 250ms, causing iteration to fail with a timeout error. - description: "timeoutMS is refreshed for getMore if timeoutMode is iteration - failure" operations: - name: failPoint @@ -241,7 +241,7 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: find object: *collection arguments: diff --git a/test/spec/client-side-operations-timeout/retryability-timeoutMS.json b/test/spec/client-side-operations-timeout/retryability-timeoutMS.json index a28dbd26854..9daad260ef3 100644 --- a/test/spec/client-side-operations-timeout/retryability-timeoutMS.json +++ b/test/spec/client-side-operations-timeout/retryability-timeoutMS.json @@ -108,6 +108,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - insertOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -198,6 +203,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - insertOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -327,6 +337,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - insertMany on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -419,6 +434,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - insertMany on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -546,6 +566,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - deleteOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -634,6 +659,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - deleteOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -760,6 +790,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - replaceOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -851,6 +886,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - replaceOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -982,6 +1022,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - updateOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1075,6 +1120,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - updateOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1203,6 +1253,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - findOneAndDelete on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1291,6 +1346,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - findOneAndDelete on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1417,6 +1477,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - findOneAndReplace on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1508,6 +1573,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - findOneAndReplace on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1639,6 +1709,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - findOneAndUpdate on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1732,6 +1807,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - findOneAndUpdate on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1868,6 +1948,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - bulkWrite on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -1964,6 +2049,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - bulkWrite on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2095,6 +2185,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - listDatabases on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2183,6 +2278,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - listDatabases on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2303,6 +2403,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - listDatabaseNames on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2390,6 +2495,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - listDatabaseNames on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2512,6 +2622,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - createChangeStream on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2600,6 +2715,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - createChangeStream on client", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2730,6 +2850,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - aggregate on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2825,6 +2950,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - aggregate on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -2955,6 +3085,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - listCollections on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3043,6 +3178,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - listCollections on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3166,6 +3306,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - listCollectionNames on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3254,6 +3399,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - listCollectionNames on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3377,6 +3527,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - createChangeStream on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3465,6 +3620,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - createChangeStream on database", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3588,6 +3748,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - aggregate on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3676,6 +3841,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - aggregate on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3799,6 +3969,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - count on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -3887,6 +4062,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - count on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4010,6 +4190,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - countDocuments on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4098,6 +4283,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - countDocuments on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4218,6 +4408,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - estimatedDocumentCount on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4305,6 +4500,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - estimatedDocumentCount on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4428,6 +4628,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - distinct on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4517,6 +4722,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - distinct on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4641,6 +4851,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - find on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4729,6 +4944,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - find on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4852,6 +5072,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - findOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -4940,6 +5165,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - findOne on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -5060,6 +5290,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - listIndexes on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -5147,6 +5382,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - listIndexes on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -5269,6 +5509,11 @@ }, { "description": "operation is retried multiple times for non-zero timeoutMS - createChangeStream on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", @@ -5357,6 +5602,11 @@ }, { "description": "operation is retried multiple times if timeoutMS is zero - createChangeStream on collection", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1" + } + ], "operations": [ { "name": "failPoint", diff --git a/test/spec/client-side-operations-timeout/retryability-timeoutMS.yml b/test/spec/client-side-operations-timeout/retryability-timeoutMS.yml index 039f7ca42ef..6f47d6c2e42 100644 --- a/test/spec/client-side-operations-timeout/retryability-timeoutMS.yml +++ b/test/spec/client-side-operations-timeout/retryability-timeoutMS.yml @@ -84,6 +84,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - insertOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -125,6 +127,8 @@ tests: insert: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - insertOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -191,6 +195,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - insertMany on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -233,6 +239,8 @@ tests: insert: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - insertMany on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -299,6 +307,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - deleteOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -340,6 +350,8 @@ tests: delete: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - deleteOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -406,6 +418,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - replaceOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -448,6 +462,8 @@ tests: update: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - replaceOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -515,6 +531,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - updateOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -557,6 +575,8 @@ tests: update: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - updateOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -623,6 +643,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - findOneAndDelete on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -664,6 +686,8 @@ tests: findAndModify: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - findOneAndDelete on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -730,6 +754,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - findOneAndReplace on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -772,6 +798,8 @@ tests: findAndModify: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - findOneAndReplace on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -839,6 +867,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - findOneAndUpdate on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -881,6 +911,8 @@ tests: findAndModify: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - findOneAndUpdate on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -949,6 +981,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - bulkWrite on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -992,6 +1026,8 @@ tests: insert: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - bulkWrite on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1059,6 +1095,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - listDatabases on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1100,6 +1138,8 @@ tests: listDatabases: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - listDatabases on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1163,6 +1203,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - listDatabaseNames on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1203,6 +1245,8 @@ tests: listDatabases: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - listDatabaseNames on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1267,6 +1311,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - createChangeStream on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1308,6 +1354,8 @@ tests: aggregate: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - createChangeStream on client" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1373,6 +1421,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - aggregate on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1414,6 +1464,8 @@ tests: aggregate: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - aggregate on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1479,6 +1531,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - listCollections on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1520,6 +1574,8 @@ tests: listCollections: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - listCollections on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1585,6 +1641,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - listCollectionNames on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1626,6 +1684,8 @@ tests: listCollections: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - listCollectionNames on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1691,6 +1751,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - createChangeStream on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1732,6 +1794,8 @@ tests: aggregate: 1 maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - createChangeStream on database" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1797,6 +1861,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - aggregate on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1838,6 +1904,8 @@ tests: aggregate: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - aggregate on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1903,6 +1971,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - count on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -1944,6 +2014,8 @@ tests: count: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - count on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2009,6 +2081,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - countDocuments on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2050,6 +2124,8 @@ tests: aggregate: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - countDocuments on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2113,6 +2189,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - estimatedDocumentCount on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2153,6 +2231,8 @@ tests: count: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - estimatedDocumentCount on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2218,6 +2298,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - distinct on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2260,6 +2342,8 @@ tests: distinct: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - distinct on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2326,6 +2410,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - find on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2367,6 +2453,8 @@ tests: find: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - find on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2432,6 +2520,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - findOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2473,6 +2563,8 @@ tests: find: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - findOne on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2536,6 +2628,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - listIndexes on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2576,6 +2670,8 @@ tests: listIndexes: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - listIndexes on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2640,6 +2736,8 @@ tests: expectError: isTimeoutError: true - description: "operation is retried multiple times for non-zero timeoutMS - createChangeStream on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -2681,6 +2779,8 @@ tests: aggregate: *collectionName maxTimeMS: { $$type: ["int", "long"] } - description: "operation is retried multiple times if timeoutMS is zero - createChangeStream on collection" + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner diff --git a/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.json b/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.json index abbc3217327..13ea91c7948 100644 --- a/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.json +++ b/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.json @@ -21,7 +21,7 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 50 + "timeoutMS": 500 }, "useMultipleMongoses": false, "observeEvents": [ @@ -78,7 +78,7 @@ "commitTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -165,7 +165,7 @@ "abortTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -249,7 +249,7 @@ "insert" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -302,6 +302,26 @@ "commandFailedEvent": { "commandName": "insert" } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction", + "databaseName": "admin", + "command": { + "abortTransaction": 1, + "maxTimeMS": { + "$$type": [ + "int", + "long" + ] + } + } + } + }, + { + "commandFailedEvent": { + "commandName": "abortTransaction" + } } ] } diff --git a/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.yml b/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.yml index 184ef7eb9e7..c79384e5f0b 100644 --- a/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.yml +++ b/test/spec/client-side-operations-timeout/sessions-inherit-timeoutMS.yml @@ -13,7 +13,7 @@ createEntities: - client: id: &client client uriOptions: - timeoutMS: 50 + timeoutMS: 500 useMultipleMongoses: false observeEvents: - commandStartedEvent @@ -52,7 +52,7 @@ tests: data: failCommands: ["commitTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -95,7 +95,7 @@ tests: data: failCommands: ["abortTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -136,7 +136,7 @@ tests: data: failCommands: ["insert"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: withTransaction object: *session arguments: @@ -153,9 +153,6 @@ tests: expectEvents: - client: *client events: - # Because the insert expects an error and gets an error, it technically succeeds, so withTransaction will - # try to run commitTransaction. This will fail client-side, though, because the timeout has already expired, - # so no command is sent. - commandStartedEvent: commandName: insert databaseName: *databaseName @@ -166,3 +163,11 @@ tests: maxTimeMS: { $$type: ["int", "long"] } - commandFailedEvent: commandName: insert + - commandStartedEvent: + commandName: abortTransaction + databaseName: admin + command: + abortTransaction: 1 + maxTimeMS: { $$type: [ "int", "long" ] } + - commandFailedEvent: + commandName: abortTransaction diff --git a/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.json b/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.json index 0254b184a14..441c698328c 100644 --- a/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.json +++ b/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.json @@ -75,7 +75,7 @@ "commitTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -98,7 +98,7 @@ "name": "commitTransaction", "object": "session", "arguments": { - "timeoutMS": 50 + "timeoutMS": 500 }, "expectError": { "isTimeoutError": true @@ -165,7 +165,7 @@ "abortTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -188,7 +188,7 @@ "name": "abortTransaction", "object": "session", "arguments": { - "timeoutMS": 50 + "timeoutMS": 500 } } ], @@ -252,7 +252,7 @@ "insert" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -261,7 +261,7 @@ "name": "withTransaction", "object": "session", "arguments": { - "timeoutMS": 50, + "timeoutMS": 500, "callback": [ { "name": "insertOne", @@ -306,6 +306,26 @@ "commandFailedEvent": { "commandName": "insert" } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction", + "databaseName": "admin", + "command": { + "abortTransaction": 1, + "maxTimeMS": { + "$$type": [ + "int", + "long" + ] + } + } + } + }, + { + "commandFailedEvent": { + "commandName": "abortTransaction" + } } ] } diff --git a/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.yml b/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.yml index 8a80a65720a..bee91dc4cb8 100644 --- a/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.yml +++ b/test/spec/client-side-operations-timeout/sessions-override-operation-timeoutMS.yml @@ -50,7 +50,7 @@ tests: data: failCommands: ["commitTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -61,7 +61,7 @@ tests: - name: commitTransaction object: *session arguments: - timeoutMS: 50 + timeoutMS: 500 expectError: isTimeoutError: true expectEvents: @@ -95,7 +95,7 @@ tests: data: failCommands: ["abortTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -106,7 +106,7 @@ tests: - name: abortTransaction object: *session arguments: - timeoutMS: 50 + timeoutMS: 500 expectEvents: - client: *client events: @@ -138,11 +138,11 @@ tests: data: failCommands: ["insert"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: withTransaction object: *session arguments: - timeoutMS: 50 + timeoutMS: 500 callback: - name: insertOne object: *collection @@ -156,9 +156,6 @@ tests: expectEvents: - client: *client events: - # Because the insert expects an error and gets an error, it technically succeeds, so withTransaction will - # try to run commitTransaction. This will fail client-side, though, because the timeout has already expired, - # so no command is sent. - commandStartedEvent: commandName: insert databaseName: *databaseName @@ -169,3 +166,11 @@ tests: maxTimeMS: { $$type: ["int", "long"] } - commandFailedEvent: commandName: insert + - commandStartedEvent: + commandName: abortTransaction + databaseName: admin + command: + abortTransaction: 1 + maxTimeMS: { $$type: ["int", "long"] } + - commandFailedEvent: + commandName: abortTransaction diff --git a/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.json b/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.json index c46ae4dd506..d90152e909c 100644 --- a/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.json +++ b/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.json @@ -47,7 +47,7 @@ "id": "session", "client": "client", "sessionOptions": { - "defaultTimeoutMS": 50 + "defaultTimeoutMS": 500 } } } @@ -78,7 +78,7 @@ "commitTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -165,7 +165,7 @@ "abortTransaction" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -249,7 +249,7 @@ "insert" ], "blockConnection": true, - "blockTimeMS": 60 + "blockTimeMS": 600 } } } @@ -302,6 +302,26 @@ "commandFailedEvent": { "commandName": "insert" } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction", + "databaseName": "admin", + "command": { + "abortTransaction": 1, + "maxTimeMS": { + "$$type": [ + "int", + "long" + ] + } + } + } + }, + { + "commandFailedEvent": { + "commandName": "abortTransaction" + } } ] } diff --git a/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.yml b/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.yml index 61aaab4d97e..73aaf9ff2a7 100644 --- a/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.yml +++ b/test/spec/client-side-operations-timeout/sessions-override-timeoutMS.yml @@ -29,7 +29,7 @@ createEntities: id: &session session client: *client sessionOptions: - defaultTimeoutMS: 50 + defaultTimeoutMS: 500 initialData: - collectionName: *collectionName @@ -52,7 +52,7 @@ tests: data: failCommands: ["commitTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -95,7 +95,7 @@ tests: data: failCommands: ["abortTransaction"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: startTransaction object: *session - name: insertOne @@ -136,7 +136,7 @@ tests: data: failCommands: ["insert"] blockConnection: true - blockTimeMS: 60 + blockTimeMS: 600 - name: withTransaction object: *session arguments: @@ -153,9 +153,6 @@ tests: expectEvents: - client: *client events: - # Because the insert expects an error and gets an error, it technically succeeds, so withTransaction will - # try to run commitTransaction. This will fail client-side, though, because the timeout has already expired, - # so no command is sent. - commandStartedEvent: commandName: insert databaseName: *databaseName @@ -166,3 +163,11 @@ tests: maxTimeMS: { $$type: ["int", "long"] } - commandFailedEvent: commandName: insert + - commandStartedEvent: + commandName: abortTransaction + databaseName: admin + command: + abortTransaction: 1 + maxTimeMS: { $$type: [ "int", "long" ] } + - commandFailedEvent: + commandName: abortTransaction diff --git a/test/spec/client-side-operations-timeout/tailable-awaitData.json b/test/spec/client-side-operations-timeout/tailable-awaitData.json index 6da85c77835..535fb692434 100644 --- a/test/spec/client-side-operations-timeout/tailable-awaitData.json +++ b/test/spec/client-side-operations-timeout/tailable-awaitData.json @@ -17,7 +17,7 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 10 + "timeoutMS": 200 }, "useMultipleMongoses": false, "observeEvents": [ @@ -130,7 +130,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 300 } } } @@ -188,7 +188,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 150 } } } @@ -199,7 +199,7 @@ "arguments": { "filter": {}, "cursorType": "tailableAwait", - "timeoutMS": 20, + "timeoutMS": 250, "batchSize": 1 }, "saveResultAsEntity": "tailableCursor" @@ -272,7 +272,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 150 } } } @@ -283,7 +283,7 @@ "arguments": { "filter": {}, "cursorType": "tailableAwait", - "timeoutMS": 20, + "timeoutMS": 250, "batchSize": 1, "maxAwaitTimeMS": 1 }, @@ -354,7 +354,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } diff --git a/test/spec/client-side-operations-timeout/tailable-awaitData.yml b/test/spec/client-side-operations-timeout/tailable-awaitData.yml index 422c6fb5370..52b9b3b456c 100644 --- a/test/spec/client-side-operations-timeout/tailable-awaitData.yml +++ b/test/spec/client-side-operations-timeout/tailable-awaitData.yml @@ -12,7 +12,7 @@ createEntities: - client: id: &client client uriOptions: - timeoutMS: 10 + timeoutMS: 200 useMultipleMongoses: false observeEvents: - commandStartedEvent @@ -83,7 +83,7 @@ tests: data: failCommands: ["find"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 300 - name: find object: *collection arguments: @@ -117,13 +117,13 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 150 - name: createFindCursor object: *collection arguments: filter: {} cursorType: tailableAwait - timeoutMS: 20 + timeoutMS: 250 batchSize: 1 saveResultAsEntity: &tailableCursor tailableCursor # Iterate twice to force a getMore. The first iteration will return the document from the first batch and the @@ -165,13 +165,13 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 150 - name: createFindCursor object: *collection arguments: filter: {} cursorType: tailableAwait - timeoutMS: 20 + timeoutMS: 250 batchSize: 1 maxAwaitTimeMS: 1 saveResultAsEntity: &tailableCursor tailableCursor @@ -199,8 +199,8 @@ tests: collection: *collectionName maxTimeMS: 1 - # The timeoutMS value should be refreshed for getMore's. This is a failure test. The find inherits timeoutMS=10 from - # the collection and the getMore blocks for 15ms, causing iteration to fail with a timeout error. + # The timeoutMS value should be refreshed for getMore's. This is a failure test. The find inherits timeoutMS=200 from + # the collection and the getMore blocks for 250ms, causing iteration to fail with a timeout error. - description: "timeoutMS is refreshed for getMore - failure" operations: - name: failPoint @@ -213,7 +213,7 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: createFindCursor object: *collection arguments: diff --git a/test/spec/client-side-operations-timeout/tailable-non-awaitData.json b/test/spec/client-side-operations-timeout/tailable-non-awaitData.json index 34ee6609636..e88230e4f7a 100644 --- a/test/spec/client-side-operations-timeout/tailable-non-awaitData.json +++ b/test/spec/client-side-operations-timeout/tailable-non-awaitData.json @@ -17,7 +17,7 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 10 + "timeoutMS": 200 }, "useMultipleMongoses": false, "observeEvents": [ @@ -94,7 +94,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } @@ -154,7 +154,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 150 } } } @@ -165,7 +165,7 @@ "arguments": { "filter": {}, "cursorType": "tailable", - "timeoutMS": 20, + "timeoutMS": 200, "batchSize": 1 }, "saveResultAsEntity": "tailableCursor" @@ -239,7 +239,7 @@ "getMore" ], "blockConnection": true, - "blockTimeMS": 15 + "blockTimeMS": 250 } } } diff --git a/test/spec/client-side-operations-timeout/tailable-non-awaitData.yml b/test/spec/client-side-operations-timeout/tailable-non-awaitData.yml index 766b46e658b..eb75deaa65c 100644 --- a/test/spec/client-side-operations-timeout/tailable-non-awaitData.yml +++ b/test/spec/client-side-operations-timeout/tailable-non-awaitData.yml @@ -12,7 +12,7 @@ createEntities: - client: id: &client client uriOptions: - timeoutMS: 10 + timeoutMS: 200 useMultipleMongoses: false observeEvents: - commandStartedEvent @@ -59,7 +59,7 @@ tests: data: failCommands: ["find"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: find object: *collection arguments: @@ -96,13 +96,13 @@ tests: data: failCommands: ["find", "getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 150 - name: createFindCursor object: *collection arguments: filter: {} cursorType: tailable - timeoutMS: 20 + timeoutMS: 200 batchSize: 1 saveResultAsEntity: &tailableCursor tailableCursor # Iterate the cursor twice: the first iteration will return the document from the batch in the find and the @@ -131,7 +131,7 @@ tests: maxTimeMS: { $$exists: false } # The timeoutMS option should apply separately to the initial "find" and each getMore. This is a failure test. The - # find inherits timeoutMS=10 from the collection and the getMore command blocks for 15ms, causing iteration to fail + # find inherits timeoutMS=200 from the collection and the getMore command blocks for 250ms, causing iteration to fail # with a timeout error. - description: "timeoutMS is refreshed for getMore - failure" operations: @@ -145,7 +145,7 @@ tests: data: failCommands: ["getMore"] blockConnection: true - blockTimeMS: 15 + blockTimeMS: 250 - name: createFindCursor object: *collection arguments: From 679c02cf80b23edb919a962afe5723984da4928b Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 21 Aug 2024 14:43:49 -0400 Subject: [PATCH 19/52] add omitMaxTimeMS --- src/cursor/run_command_cursor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 0522a3ee519..dbe53640ee6 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -131,7 +131,8 @@ export class RunCommandCursor extends AbstractCursor { const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, { ...this.cursorOptions, session: this.session, - ...this.getMoreOptions + ...this.getMoreOptions, + omitMaxTimeMS: this.cursorOptions.timeoutMode != null }); return await executeOperation(this.client, getMoreOperation, this.timeoutContext); From abc1cfdf6662b3e311168c4b84c0b3595144ae79 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 21 Aug 2024 14:44:34 -0400 Subject: [PATCH 20/52] skipping spec tests --- ...lient_side_operations_timeout.spec.test.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index eb6b467ef3e..ff5818be2b5 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -3,7 +3,7 @@ import { join } from 'path'; import { loadSpecTests } from '../../spec'; import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; -const skipped = { +const skippedSpecs = { bulkWrite: 'TODO(NODE-6274)', 'change-streams': 'TODO(NODE-6035)', 'convenient-transactions': 'TODO(NODE-5687)', @@ -20,20 +20,31 @@ const skipped = { 'tailable-non-awaitData': 'TODO(NODE-6035)' }; -const bulkWriteOperations = - /timeoutMS applies to whole operation, not individual attempts - (bulkWrite|insertMany) on .*/; +const skippedTests = { + 'timeoutMS can be configured on a MongoClient - insertMany on collection': 'TODO(NODE-6274)', + 'timeoutMS can be configured on a MongoClient - bulkWrite on collection': 'TODO(NODE-6274)', + 'timeoutMS can be configured on a MongoClient - createChangeStream on client': 'TODO(NODE-6305)', + 'timeoutMS applies to whole operation, not individual attempts - createChangeStream on client': + 'TODO(NODE-6305)', + 'Tailable cursor iteration timeoutMS is refreshed for getMore - failure': 'TODO(NODE-6305)', + 'Tailable cursor awaitData iteration timeoutMS is refreshed for getMore - failure': + 'TODO(NODE-6305)', + 'timeoutMS applies to whole operation, not individual attempts - insertMany on collection': + 'TODO(NODE-6274)', + 'timeoutMS applies to whole operation, not individual attempts - bulkWrite on collection': + 'TODO(NODE-6274)' +}; describe('CSOT spec tests', function () { const specs = loadSpecTests(join('client-side-operations-timeout')); for (const spec of specs) { for (const test of spec.tests) { - if (skipped[spec.name] != null) { - test.skipReason = skipped[spec.name]; + if (skippedSpecs[spec.name] != null) { + test.skipReason = skippedSpecs[spec.name]; + } + if (skippedTests[test.description] != null) { + test.skipReason = skippedTests[test.description]; } - - if (bulkWriteOperations.test(test.description)) - test.skipReason = - 'TODO(NODE-6274): update test runner to check errorResponse field of MongoBulkWriteError in isTimeoutError assertion'; } } From b64af7b645ed7024c7473d04de7b94a96ff091df Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 21 Aug 2024 14:44:45 -0400 Subject: [PATCH 21/52] pass through timeoutMS arg to cursor.close --- test/tools/unified-spec-runner/operations.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index 3a63053d622..4249fa63409 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -209,7 +209,8 @@ operations.set('close', async ({ entities, operation }) => { /* eslint-disable no-empty */ try { const cursor = entities.getEntity('cursor', operation.object); - await cursor.close(); + const timeoutMS = operation.arguments?.timeoutMS; + await cursor.close(timeoutMS); return; } catch {} From 628eb55bc88e2954b509803b65d8408e6386832a Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 23 Aug 2024 16:32:32 -0400 Subject: [PATCH 22/52] Fix tests and dropindexes --- src/collection.ts | 5 +++-- .../client_side_operations_timeout.spec.test.ts | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index d7dd7b43ac0..545c875580c 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -11,7 +11,7 @@ import { type ListSearchIndexesOptions } from './cursor/list_search_indexes_cursor'; import type { Db } from './db'; -import { MongoInvalidArgumentError } from './error'; +import { MongoInvalidArgumentError, MongoOperationTimeoutError } from './error'; import type { MongoClient, PkFactory } from './mongo_client'; import type { Filter, @@ -673,7 +673,8 @@ export class Collection { new DropIndexOperation(this as TODO_NODE_3286, '*', resolveOptions(this, options)) ); return true; - } catch { + } catch (error) { + if (error instanceof MongoOperationTimeoutError) throw error; return false; } } diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index ff5818be2b5..2df28425295 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -32,7 +32,13 @@ const skippedTests = { 'timeoutMS applies to whole operation, not individual attempts - insertMany on collection': 'TODO(NODE-6274)', 'timeoutMS applies to whole operation, not individual attempts - bulkWrite on collection': - 'TODO(NODE-6274)' + 'TODO(NODE-6274)', + 'command is not sent if RTT is greater than timeoutMS': 'TODO(DRIVERS-2965)', + 'Non=tailable cursor iteration timeoutMS is refreshed for getMore if timeoutMode is iteration - failure': + 'TODO(DRIVERS-2965)', + 'Non-tailable cursor lifetime remaining timeoutMS applied to getMore if timeoutMode is unset': + 'TODO(DRIVERS-2965)' + //'timeoutMS can be configured on a MongoClient - dropIndexes on collection': 'Successfully returns false on failure, but does not surface CSOT' }; describe('CSOT spec tests', function () { From e90e7ed8f0eb8f5f5aa5d5bed46a930d140da0b6 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 23 Aug 2024 17:40:39 -0400 Subject: [PATCH 23/52] add catch --- .../node_csot.test.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index d9f9056d6fe..ea0e3330096 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -5,12 +5,13 @@ import { expect } from 'chai'; import * as semver from 'semver'; import * as sinon from 'sinon'; -import { type CommandSucceededEvent } from '../../../lib/cmap/command_monitoring_events'; import { BSON, type ClientSession, type Collection, + type CommandFailedEvent, type CommandStartedEvent, + type CommandSucceededEvent, Connection, type Db, type FindCursor, @@ -174,8 +175,8 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { describe('server-side maxTimeMS errors are transformed', () => { let client: MongoClient; - let commandsSucceeded; - let commandsFailed; + let commandsSucceeded: CommandSucceededEvent[]; + let commandsFailed: CommandFailedEvent[]; beforeEach(async function () { client = this.configuration.newClient({ timeoutMS: 500_000, monitorCommands: true }); @@ -341,8 +342,11 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }; beforeEach(async function () { - internalClient = this.configuration.newClient(undefined); - await internalClient.db('db').dropCollection('coll'); + internalClient = this.configuration.newClient(); + await internalClient + .db('db') + .dropCollection('coll') + .catch(() => null); await internalClient .db('db') .collection('coll') @@ -444,8 +448,11 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }; beforeEach(async function () { - internalClient = this.configuration.newClient(undefined); - await internalClient.db('db').dropCollection('coll'); + internalClient = this.configuration.newClient(); + await internalClient + .db('db') + .dropCollection('coll') + .catch(() => null); await internalClient .db('db') .collection('coll') From cbbd7e7982ce388adb9840c5ce0edebf066ba3d9 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 26 Aug 2024 14:25:13 -0400 Subject: [PATCH 24/52] ensure that CSOT tests only run against 4.4 or greater --- .../node_csot.test.ts | 168 ++++++++++-------- 1 file changed, 95 insertions(+), 73 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index ea0e3330096..ca9cde5e960 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -22,7 +22,9 @@ import { } from '../../mongodb'; import { type FailPoint } from '../../tools/utils'; -describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { +const metadata = { requires: { mongodb: '>=4.4' } }; + +describe('CSOT driver tests', metadata, () => { describe('timeoutMS inheritance', () => { let client: MongoClient; let db: Db; @@ -225,18 +227,22 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { await client.db('admin').command({ ...failpoint, mode: 'off' }); }); - it('throws a MongoOperationTimeoutError error and emits command failed', async () => { - const error = await client - .db() - .command({ ping: 1 }) - .catch(error => error); - expect(error).to.be.instanceOf(MongoOperationTimeoutError); - expect(error.cause).to.be.instanceOf(MongoServerError); - expect(error.cause).to.have.property('code', 50); - - expect(commandsFailed).to.have.lengthOf(1); - expect(commandsFailed).to.have.nested.property('[0].failure.cause.code', 50); - }); + it( + 'throws a MongoOperationTimeoutError error and emits command failed', + metadata, + async () => { + const error = await client + .db() + .command({ ping: 1 }) + .catch(error => error); + expect(error).to.be.instanceOf(MongoOperationTimeoutError); + expect(error.cause).to.be.instanceOf(MongoServerError); + expect(error.cause).to.have.property('code', 50); + + expect(commandsFailed).to.have.lengthOf(1); + expect(commandsFailed).to.have.nested.property('[0].failure.cause.code', 50); + } + ); }); describe('when a maxTimeExpired error is returned inside a writeErrors array', () => { @@ -271,18 +277,22 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { afterEach(() => sinon.restore()); - it('throws a MongoOperationTimeoutError error and emits command succeeded', async () => { - const error = await client - .db('admin') - .command({ giveMeWriteErrors: 1 }) - .catch(error => error); - expect(error).to.be.instanceOf(MongoOperationTimeoutError); - expect(error.cause).to.be.instanceOf(MongoServerError); - expect(error.cause).to.have.nested.property('writeErrors[3].code', 50); - - expect(commandsSucceeded).to.have.lengthOf(1); - expect(commandsSucceeded).to.have.nested.property('[0].reply.writeErrors[3].code', 50); - }); + it( + 'throws a MongoOperationTimeoutError error and emits command succeeded', + metadata, + async () => { + const error = await client + .db('admin') + .command({ giveMeWriteErrors: 1 }) + .catch(error => error); + expect(error).to.be.instanceOf(MongoOperationTimeoutError); + expect(error.cause).to.be.instanceOf(MongoServerError); + expect(error.cause).to.have.nested.property('writeErrors[3].code', 50); + + expect(commandsSucceeded).to.have.lengthOf(1); + expect(commandsSucceeded).to.have.nested.property('[0].reply.writeErrors[3].code', 50); + } + ); }); describe('when a maxTimeExpired error is returned inside a writeConcernError embedded document', () => { @@ -310,19 +320,23 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { await client.db('admin').command({ ...failpoint, mode: 'off' }); }); - it('throws a MongoOperationTimeoutError error and emits command succeeded', async () => { - const error = await client - .db() - .collection('a') - .insertOne({}) - .catch(error => error); - expect(error).to.be.instanceOf(MongoOperationTimeoutError); - expect(error.cause).to.be.instanceOf(MongoServerError); - expect(error.cause).to.have.nested.property('writeConcernError.code', 50); - - expect(commandsSucceeded).to.have.lengthOf(1); - expect(commandsSucceeded).to.have.nested.property('[0].reply.writeConcernError.code', 50); - }); + it( + 'throws a MongoOperationTimeoutError error and emits command succeeded', + metadata, + async () => { + const error = await client + .db() + .collection('a') + .insertOne({}) + .catch(error => error); + expect(error).to.be.instanceOf(MongoOperationTimeoutError); + expect(error.cause).to.be.instanceOf(MongoServerError); + expect(error.cause).to.have.nested.property('writeConcernError.code', 50); + + expect(commandsSucceeded).to.have.lengthOf(1); + expect(commandsSucceeded).to.have.nested.property('[0].reply.writeConcernError.code', 50); + } + ); }); }); @@ -376,22 +390,26 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { context('ITERATION mode', () => { context('when executing an operation', () => { - it('must apply the configured timeoutMS to the initial operation execution', async function () { - const cursor = client - .db('db') - .collection('coll') - .find({}, { batchSize: 3, timeoutMode: 'iteration', timeoutMS: 10 }) - .limit(3); + it( + 'must apply the configured timeoutMS to the initial operation execution', + metadata, + async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 3, timeoutMode: 'iteration', timeoutMS: 10 }) + .limit(3); - const maybeError = await cursor.next().then( - () => null, - e => e - ); + const maybeError = await cursor.next().then( + () => null, + e => e + ); - expect(maybeError).to.be.instanceOf(MongoOperationTimeoutError); - }); + expect(maybeError).to.be.instanceOf(MongoOperationTimeoutError); + } + ); - it('refreshes the timeout for any getMores', async function () { + it('refreshes the timeout for any getMores', metadata, async function () { const cursor = client .db('db') .collection('coll') @@ -410,25 +428,29 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { expect(getMores).to.have.length(3); // Expecting 3 getMores (including final empty getMore) }); - it('does not append a maxTimeMS to the original command or getMores', async function () { - const cursor = client - .db('db') - .collection('coll') - .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 100 }) - .project({ _id: 0 }); - await cursor.toArray(); - - expect(commandStarted).to.have.length.gte(3); // Find and 2 getMores - expect( - commandStarted.filter(ev => { - return ( - ev.command.find != null && - ev.command.getMore != null && - ev.command.maxTimeMS != null - ); - }) - ).to.have.lengthOf(0); - }); + it( + 'does not append a maxTimeMS to the original command or getMores', + metadata, + async function () { + const cursor = client + .db('db') + .collection('coll') + .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 100 }) + .project({ _id: 0 }); + await cursor.toArray(); + + expect(commandStarted).to.have.length.gte(3); // Find and 2 getMores + expect( + commandStarted.filter(ev => { + return ( + ev.command.find != null && + ev.command.getMore != null && + ev.command.maxTimeMS != null + ); + }) + ).to.have.lengthOf(0); + } + ); }); }); @@ -483,7 +505,7 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { context( 'when there are documents available from previously retrieved batch and timeout has expired', () => { - it('returns documents without error', async function () { + it('returns documents without error', metadata, async function () { const cursor = client .db('db') .collection('coll') @@ -506,7 +528,7 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { } ); context('when a getMore is required and the timeout has expired', () => { - it('throws a MongoOperationTimeoutError', async function () { + it('throws a MongoOperationTimeoutError', metadata, async function () { const cursor = client .db('db') .collection('coll') @@ -527,7 +549,7 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => { }); }); - it('does not apply maxTimeMS to a getMore', async function () { + it('does not apply maxTimeMS to a getMore', metadata, async function () { const cursor = client .db('db') .collection('coll') From fd43e60cf5f50c435616014a41ad37809cdfff3f Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 26 Aug 2024 14:25:32 -0400 Subject: [PATCH 25/52] lint --- src/cursor/run_command_cursor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index dbe53640ee6..7eac7f975ab 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -21,6 +21,7 @@ export type RunCursorCommandOptions = { readPreference?: ReadPreferenceLike; session?: ClientSession; timeoutMS?: number; + /** @internal */ timeoutMode?: CursorTimeoutMode; } & BSONSerializeOptions; From bcb6e70fef46fb5536f1ceb812b4716d61a96430 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 26 Aug 2024 14:27:44 -0400 Subject: [PATCH 26/52] ignore --- .../integration/client-side-operations-timeout/node_csot.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index ca9cde5e960..8ff2eeb21cd 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -555,6 +555,7 @@ describe('CSOT driver tests', metadata, () => { .collection('coll') .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 1000 }) .project({ _id: 0 }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars for await (const _doc of cursor) { // Ignore _doc } From 2ac4b0fcc244550209da5f59d7c20f94cf6ac3cf Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 26 Aug 2024 14:56:18 -0400 Subject: [PATCH 27/52] update export test --- test/unit/index.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 2766c717712..f217f2ada40 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -48,6 +48,8 @@ const EXPECTED_EXPORTS = [ 'ConnectionPoolMonitoringEvent', 'ConnectionPoolReadyEvent', 'ConnectionReadyEvent', + 'CursorInitializeOptions', + 'CursorTimeoutMode', 'CURSOR_FLAGS', 'Db', 'DBRef', From ba0dc00f1131c5418f2ee6676d42a6c14ad34710 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 26 Aug 2024 17:21:27 -0400 Subject: [PATCH 28/52] remove TODO --- src/cmap/connection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index a6b09a4a9f5..b0869c45e06 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -615,7 +615,6 @@ export class Connection extends TypedEventEmitter { for await (const document of this.sendCommand(ns, command, options, responseType)) { if (options.timeoutContext?.csotEnabled()) { if (MongoDBResponse.is(document)) { - // TODO(NODE-5684): test coverage to be added once cursors are enabling CSOT if (document.isMaxTimeExpiredError) { throw new MongoOperationTimeoutError('Server reported a timeout error', { cause: new MongoServerError(document.toObject()) From 1fb6c7df578266312dab73df5da6066d7eb6f6f7 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 13:43:39 -0400 Subject: [PATCH 29/52] use cursorOptions instead of CursorInitializeOptions --- src/cursor/abstract_cursor.ts | 31 +++++++++++++++------------ src/cursor/aggregation_cursor.ts | 13 +++-------- src/cursor/change_stream_cursor.ts | 9 +++----- src/cursor/find_cursor.ts | 9 +++----- src/cursor/list_collections_cursor.ts | 9 +++----- src/cursor/list_indexes_cursor.ts | 15 ++++--------- src/cursor/run_command_cursor.ts | 11 ++++------ 7 files changed, 37 insertions(+), 60 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 0edb9776387..8a40fc0e975 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -130,6 +130,9 @@ export type InternalAbstractCursorOptions = Omit; /** @internal */ @@ -754,7 +765,7 @@ export abstract class AbstractCursor< ...this.cursorOptions, session: this.cursorSession, batchSize, - omitMaxTimeMS: this.cursorOptions.timeoutMode != null + omitMaxTimeMS: this.cursorOptions.omitMaxTimeMSOnGetMore } ); @@ -776,16 +787,8 @@ export abstract class AbstractCursor< cursorTimeoutMode: this.cursorOptions.timeoutMode }); } - const omitMaxTimeMS = - this.cursorOptions.timeoutMS != null && - ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - !this.cursorOptions.tailable) || - (this.cursorOptions.tailable && !this.cursorOptions.awaitData)); try { - const state = await this._initialize(this.cursorSession, { - timeoutContext: this.timeoutContext, - omitMaxTimeMS - }); + const state = await this._initialize(this.cursorSession); const response = state.response; this.selectedServer = state.server; this.cursorId = response.id; diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index 48f6086d72c..5b094e20320 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -63,22 +63,15 @@ export class AggregationCursor extends AbstractCursor { } /** @internal */ - async _initialize( - session: ClientSession, - options?: CursorInitializeOptions - ): Promise { + async _initialize(session: ClientSession): Promise { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.aggregateOptions, ...this.cursorOptions, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); - const response = await executeOperation( - this.client, - aggregateOperation, - options?.timeoutContext - ); + const response = await executeOperation(this.client, aggregateOperation, this.timeoutContext); return { server: aggregateOperation.server, session, response }; } diff --git a/src/cursor/change_stream_cursor.ts b/src/cursor/change_stream_cursor.ts index ba06ad485f9..da86f5cb27a 100644 --- a/src/cursor/change_stream_cursor.ts +++ b/src/cursor/change_stream_cursor.ts @@ -127,21 +127,18 @@ export class ChangeStreamCursor< }); } - async _initialize( - session: ClientSession, - options?: CursorInitializeOptions - ): Promise { + async _initialize(session: ClientSession): Promise { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.cursorOptions, ...this.changeStreamCursorOptions, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); const response = await executeOperation( session.client, aggregateOperation, - options?.timeoutContext + this.timeoutContext ); const server = aggregateOperation.server; diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index 9df7c480521..95b7886ef3b 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -66,18 +66,15 @@ export class FindCursor extends AbstractCursor { } /** @internal */ - async _initialize( - session: ClientSession, - options?: CursorInitializeOptions - ): Promise { + async _initialize(session: ClientSession): Promise { const findOperation = new FindOperation(this.namespace, this.cursorFilter, { ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); - const response = await executeOperation(this.client, findOperation, options?.timeoutContext); + const response = await executeOperation(this.client, findOperation, this.timeoutContext); // the response is not a cursor when `explain` is enabled this.numReturned = response.batchSize; diff --git a/src/cursor/list_collections_cursor.ts b/src/cursor/list_collections_cursor.ts index aab2ffe88c7..9ca7bd77192 100644 --- a/src/cursor/list_collections_cursor.ts +++ b/src/cursor/list_collections_cursor.ts @@ -38,18 +38,15 @@ export class ListCollectionsCursor< } /** @internal */ - async _initialize( - session: ClientSession | undefined, - options?: CursorInitializeOptions - ): Promise { + async _initialize(session: ClientSession | undefined): Promise { const operation = new ListCollectionsOperation(this.parent, this.filter, { ...this.cursorOptions, ...this.options, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); - const response = await executeOperation(this.parent.client, operation, options?.timeoutContext); + const response = await executeOperation(this.parent.client, operation, this.timeoutContext); return { server: operation.server, session, response }; } diff --git a/src/cursor/list_indexes_cursor.ts b/src/cursor/list_indexes_cursor.ts index 45802cc918f..9bdaaa56d39 100644 --- a/src/cursor/list_indexes_cursor.ts +++ b/src/cursor/list_indexes_cursor.ts @@ -2,11 +2,7 @@ import type { Collection } from '../collection'; import { executeOperation } from '../operations/execute_operation'; import { ListIndexesOperation, type ListIndexesOptions } from '../operations/indexes'; import type { ClientSession } from '../sessions'; -import { - AbstractCursor, - type CursorInitializeOptions, - type InitialCursorResponse -} from './abstract_cursor'; +import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; /** @public */ export class ListIndexesCursor extends AbstractCursor { @@ -27,18 +23,15 @@ export class ListIndexesCursor extends AbstractCursor { } /** @internal */ - async _initialize( - session: ClientSession | undefined, - options?: CursorInitializeOptions - ): Promise { + async _initialize(session: ClientSession | undefined): Promise { const operation = new ListIndexesOperation(this.parent, { ...this.cursorOptions, ...this.options, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); - const response = await executeOperation(this.parent.client, operation); + const response = await executeOperation(this.parent.client, operation, this.timeoutContext); return { server: operation.server, session, response }; } diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 7eac7f975ab..5676128776f 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -105,19 +105,16 @@ export class RunCommandCursor extends AbstractCursor { } /** @internal */ - protected async _initialize( - session: ClientSession, - options?: CursorInitializeOptions - ): Promise { + protected async _initialize(session: ClientSession): Promise { const operation = new RunCommandOperation(this.db, this.command, { ...this.cursorOptions, session: session, readPreference: this.cursorOptions.readPreference, - omitMaxTimeMS: options?.omitMaxTimeMS, + omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, responseType: CursorResponse }); - const response = await executeOperation(this.client, operation, options?.timeoutContext); + const response = await executeOperation(this.client, operation, this?.timeoutContext); return { server: operation.server, @@ -133,7 +130,7 @@ export class RunCommandCursor extends AbstractCursor { ...this.cursorOptions, session: this.session, ...this.getMoreOptions, - omitMaxTimeMS: this.cursorOptions.timeoutMode != null + omitMaxTimeMS: this.cursorOptions.omitMaxTimeMSOnGetMore }); return await executeOperation(this.client, getMoreOperation, this.timeoutContext); From 343829fb5d363313268df504d8d50070d8fafc5f Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 13:44:55 -0400 Subject: [PATCH 30/52] remove CursorInitializeOptions and fix export --- src/cursor/abstract_cursor.ts | 6 ------ src/index.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 8a40fc0e975..de8f1da2993 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -140,12 +140,6 @@ export type AbstractCursorEvents = { [AbstractCursor.CLOSE](): void; }; -/** @internal */ -export type CursorInitializeOptions = { - omitMaxTimeMS?: boolean; - timeoutContext?: TimeoutContext; -}; - /** @public */ export abstract class AbstractCursor< TSchema = any, diff --git a/src/index.ts b/src/index.ts index e5899c72757..0a5a0e71446 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,7 +106,7 @@ export { AutoEncryptionLoggerLevel } from './client-side-encryption/auto_encrypt export { GSSAPICanonicalizationValue } from './cmap/auth/gssapi'; export { AuthMechanism } from './cmap/auth/providers'; export { Compressor } from './cmap/wire_protocol/compression'; -export { CURSOR_FLAGS, CursorInitializeOptions, CursorTimeoutMode } from './cursor/abstract_cursor'; +export { CURSOR_FLAGS, type CursorTimeoutMode } from './cursor/abstract_cursor'; export { MongoErrorLabel } from './error'; export { ExplainVerbosity } from './explain'; export { ServerApiVersion } from './mongo_client'; From 441f71d0480a519e995638e16e13692842da6e67 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 13:53:39 -0400 Subject: [PATCH 31/52] Review fixes --- src/cursor/abstract_cursor.ts | 4 ++-- src/cursor/aggregation_cursor.ts | 10 +++------- src/cursor/change_stream_cursor.ts | 1 - src/cursor/find_cursor.ts | 6 +----- src/cursor/list_collections_cursor.ts | 6 +----- src/cursor/run_command_cursor.ts | 2 +- src/operations/indexes.ts | 2 +- 7 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index de8f1da2993..e39e96b28c2 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -528,8 +528,8 @@ export abstract class AbstractCursor< /** * Frees any client-side resources used by the cursor. */ - async close(timeoutMS?: number): Promise { - await this.cleanup(timeoutMS); + async close(options?: { timeoutMS?: number }): Promise { + await this.cleanup(options?.timeoutMS); } /** diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index 5b094e20320..7f8f354f18b 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -8,11 +8,7 @@ import type { ClientSession } from '../sessions'; import type { Sort } from '../sort'; import type { MongoDBNamespace } from '../utils'; import { mergeOptions } from '../utils'; -import type { - AbstractCursorOptions, - CursorInitializeOptions, - InitialCursorResponse -} from './abstract_cursor'; +import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cursor'; import { AbstractCursor, CursorTimeoutMode } from './abstract_cursor'; /** @public */ @@ -45,7 +41,7 @@ export class AggregationCursor extends AbstractCursor { if ( this.cursorOptions.timeoutMS != null && this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - this.pipeline.filter(s => s.$out != null || s.$merge != null).length > 0 + this.pipeline.find(stage => stage.$out != null || stage.$merge != null) != null ) throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } @@ -109,7 +105,7 @@ export class AggregationCursor extends AbstractCursor { if ( this.cursorOptions.timeoutMS != null && this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - (Object.hasOwn(stage, '$out') || Object.hasOwn(stage, '$merge')) + (stage.$out != null || stage.$merge != null) ) { throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } diff --git a/src/cursor/change_stream_cursor.ts b/src/cursor/change_stream_cursor.ts index da86f5cb27a..586a2c7fe06 100644 --- a/src/cursor/change_stream_cursor.ts +++ b/src/cursor/change_stream_cursor.ts @@ -17,7 +17,6 @@ import { maxWireVersion, type MongoDBNamespace } from '../utils'; import { AbstractCursor, type AbstractCursorOptions, - type CursorInitializeOptions, type InitialCursorResponse } from './abstract_cursor'; diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index 95b7886ef3b..bf3cfb8cf5b 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -11,11 +11,7 @@ import type { Hint } from '../operations/operation'; import type { ClientSession } from '../sessions'; import { formatSort, type Sort, type SortDirection } from '../sort'; import { emitWarningOnce, mergeOptions, type MongoDBNamespace, squashError } from '../utils'; -import { - AbstractCursor, - type CursorInitializeOptions, - type InitialCursorResponse -} from './abstract_cursor'; +import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; /** @public Flags allowed for cursor */ export const FLAGS = [ diff --git a/src/cursor/list_collections_cursor.ts b/src/cursor/list_collections_cursor.ts index 9ca7bd77192..c39b45d553f 100644 --- a/src/cursor/list_collections_cursor.ts +++ b/src/cursor/list_collections_cursor.ts @@ -7,11 +7,7 @@ import { type ListCollectionsOptions } from '../operations/list_collections'; import type { ClientSession } from '../sessions'; -import { - AbstractCursor, - type CursorInitializeOptions, - type InitialCursorResponse -} from './abstract_cursor'; +import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor'; /** @public */ export class ListCollectionsCursor< diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 5676128776f..6c5c6fe5448 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -11,7 +11,6 @@ import type { ClientSession } from '../sessions'; import { ns } from '../utils'; import { AbstractCursor, - type CursorInitializeOptions, type CursorTimeoutMode, type InitialCursorResponse } from './abstract_cursor'; @@ -20,6 +19,7 @@ import { export type RunCursorCommandOptions = { readPreference?: ReadPreferenceLike; session?: ClientSession; + /** @internal */ timeoutMS?: number; /** @internal */ timeoutMode?: CursorTimeoutMode; diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index f665ef3f27c..220d438d834 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -363,7 +363,7 @@ export class DropIndexOperation extends CommandOperation { export type ListIndexesOptions = AbstractCursorOptions & { /** @internal TODO(NODE-5688): make this public */ timeoutMode?: CursorTimeoutMode; - /** @internal TODO(NODE-5688): make this public */ + /** @internal */ omitMaxTimeMS?: boolean; }; From a9b4f026913bea0f17fb15b8515813eaec7b17ba Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:13:16 -0400 Subject: [PATCH 32/52] fix setting of omitMaxTimeMS opts --- src/cursor/abstract_cursor.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index e39e96b28c2..24cf28086ba 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -201,13 +201,6 @@ export abstract class AbstractCursor< ...pluckBSONSerializeOptions(options) }; this.cursorOptions.timeoutMS = options.timeoutMS; - this.cursorOptions.omitMaxTimeMSOnInitialCommand = - this.cursorOptions.timeoutMS != null && - ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - !this.cursorOptions.tailable) || - (this.cursorOptions.tailable && !this.cursorOptions.awaitData)); - this.cursorOptions.omitMaxTimeMSOnGetMore = this.cursorOptions.timeoutMS != null; - if (this.cursorOptions.timeoutMS != null) { if (options.timeoutMode == null) { if (options.tailable) { @@ -227,6 +220,12 @@ export abstract class AbstractCursor< if (options.timeoutMode != null) throw new MongoInvalidArgumentError('Cannot set timeoutMode without setting timeoutMS'); } + this.cursorOptions.omitMaxTimeMSOnInitialCommand = + this.cursorOptions.timeoutMS != null && + ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + !this.cursorOptions.tailable) || + (this.cursorOptions.tailable && !this.cursorOptions.awaitData)); + this.cursorOptions.omitMaxTimeMSOnGetMore = this.cursorOptions.timeoutMS != null; const readConcern = ReadConcern.fromOptions(options); if (readConcern) { From 041b202c975207936624e44a458d272d44814b3a Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:14:10 -0400 Subject: [PATCH 33/52] ensure that timeoutMS is provided to cursor.close in UTR --- test/tools/unified-spec-runner/operations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index 4249fa63409..30ee9bee9f2 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -210,7 +210,7 @@ operations.set('close', async ({ entities, operation }) => { try { const cursor = entities.getEntity('cursor', operation.object); const timeoutMS = operation.arguments?.timeoutMS; - await cursor.close(timeoutMS); + await cursor.close({ timeoutMS }); return; } catch {} From cff3c3c1e728ae5d31d98efeb9ab60f7ad0c652a Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:17:08 -0400 Subject: [PATCH 34/52] add modified spec test --- ...lient_side_operations_timeout.spec.test.ts | 10 +- .../command-execution.json | 153 ++++++++++++++++++ 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index 2df28425295..9e00f0a449c 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -38,11 +38,10 @@ const skippedTests = { 'TODO(DRIVERS-2965)', 'Non-tailable cursor lifetime remaining timeoutMS applied to getMore if timeoutMode is unset': 'TODO(DRIVERS-2965)' - //'timeoutMS can be configured on a MongoClient - dropIndexes on collection': 'Successfully returns false on failure, but does not surface CSOT' }; describe('CSOT spec tests', function () { - const specs = loadSpecTests(join('client-side-operations-timeout')); + const specs = loadSpecTests('client-side-operations-timeout'); for (const spec of specs) { for (const test of spec.tests) { if (skippedSpecs[spec.name] != null) { @@ -56,3 +55,10 @@ describe('CSOT spec tests', function () { runUnifiedSuite(specs); }); + +describe('CSOT modified spec tests', function () { + const specs = loadSpecTests( + join('..', 'integration', 'client-side-operations-timeout', 'unified-csot-node-specs') + ); + runUnifiedSuite(specs); +}); diff --git a/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json b/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json new file mode 100644 index 00000000000..8300e403749 --- /dev/null +++ b/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json @@ -0,0 +1,153 @@ +{ + "description": "timeoutMS behaves correctly during command execution", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.4.7", + "topologies": [ + "single", + "replicaset", + "sharded" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "test", + "documents": [] + }, + { + "collectionName": "timeoutColl", + "databaseName": "test", + "documents": [] + } + ], + "tests": [ + { + "description": "maxTimeMS value in the command is less than timeoutMS", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "reduceMaxTimeMSTest", + "blockConnection": true, + "blockTimeMS": 50 + } + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "uriOptions": { + "appName": "reduceMaxTimeMSTest", + "w": 1, + "timeoutMS": 500, + "heartbeatFrequencyMS": 500 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "test" + } + }, + { + "collection": { + "id": "timeoutCollection", + "database": "database", + "collectionName": "timeoutColl" + } + } + ] + } + }, + { + "name": "insertOne", + "object": "timeoutCollection", + "arguments": { + "document": { + "_id": 1 + }, + "timeoutMS": 100000 + } + }, + { + "name": "wait", + "object": "testRunner", + "arguments": { + "ms": 1500 + } + }, + { + "name": "insertOne", + "object": "timeoutCollection", + "arguments": { + "document": { + "_id": 2 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "databaseName": "test", + "command": { + "insert": "timeoutColl" + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "databaseName": "test", + "command": { + "insert": "timeoutColl", + "maxTimeMS": { + "$$lte": 450 + } + } + } + } + ] + } + ] + } + ] +} From 0488d500da0a1cb24f3432f42267c2f8569cd24a Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:19:53 -0400 Subject: [PATCH 35/52] remove redundant optional chaining --- src/cursor/run_command_cursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 6c5c6fe5448..5f138786feb 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -114,7 +114,7 @@ export class RunCommandCursor extends AbstractCursor { responseType: CursorResponse }); - const response = await executeOperation(this.client, operation, this?.timeoutContext); + const response = await executeOperation(this.client, operation, this.timeoutContext); return { server: operation.server, From 4a6607fffa7ef7d6d0e7c69f5850d785118835fc Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:21:03 -0400 Subject: [PATCH 36/52] remove cursorTimeoutMode from timeoutContext options --- src/timeout.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/timeout.ts b/src/timeout.ts index 4e6bf0a5091..dcefdf6712f 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -1,6 +1,5 @@ import { clearTimeout, setTimeout } from 'timers'; -import { type CursorTimeoutMode } from './cursor/abstract_cursor'; import { MongoInvalidArgumentError, MongoOperationTimeoutError, MongoRuntimeError } from './error'; import { csotMin, noop } from './utils'; @@ -127,7 +126,6 @@ export type CSOTTimeoutContextOptions = { timeoutMS: number; serverSelectionTimeoutMS: number; socketTimeoutMS?: number; - cursorTimeoutMode?: CursorTimeoutMode; }; function isLegacyTimeoutContextOptions(v: unknown): v is LegacyTimeoutContextOptions { @@ -187,7 +185,6 @@ export class CSOTTimeoutContext extends TimeoutContext { clearConnectionCheckoutTimeout: boolean; clearServerSelectionTimeout: boolean; - cursorTimeoutMode?: CursorTimeoutMode; private _serverSelectionTimeout?: Timeout | null; private _connectionCheckoutTimeout?: Timeout | null; @@ -203,7 +200,6 @@ export class CSOTTimeoutContext extends TimeoutContext { this.serverSelectionTimeoutMS = options.serverSelectionTimeoutMS; this.socketTimeoutMS = options.socketTimeoutMS; - this.cursorTimeoutMode = options.cursorTimeoutMode; this.clearServerSelectionTimeout = false; this.clearConnectionCheckoutTimeout = true; From 96ecddca684f43faa03ad3e29874bd751d23333a Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 28 Aug 2024 14:38:55 -0400 Subject: [PATCH 37/52] remove types from expected exports --- test/unit/index.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index f217f2ada40..2766c717712 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -48,8 +48,6 @@ const EXPECTED_EXPORTS = [ 'ConnectionPoolMonitoringEvent', 'ConnectionPoolReadyEvent', 'ConnectionReadyEvent', - 'CursorInitializeOptions', - 'CursorTimeoutMode', 'CURSOR_FLAGS', 'Db', 'DBRef', From 504eec2ee26c203a3579f16fe92dcc8b7f46837d Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 29 Aug 2024 16:39:44 -0400 Subject: [PATCH 38/52] remove cursorTimeoutMode from TimeoutContext.create call --- src/cursor/abstract_cursor.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 24cf28086ba..41f5e071ed7 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -776,8 +776,7 @@ export abstract class AbstractCursor< if (this.cursorOptions.timeoutMS != null) { this.timeoutContext = TimeoutContext.create({ serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, - timeoutMS: this.cursorOptions.timeoutMS, - cursorTimeoutMode: this.cursorOptions.timeoutMode + timeoutMS: this.cursorOptions.timeoutMS }); } try { From cb810e227341754c7c9052440c945867e86eb712 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 3 Sep 2024 17:01:09 -0400 Subject: [PATCH 39/52] review comments --- src/cursor/abstract_cursor.ts | 16 ++++++++++------ src/cursor/aggregation_cursor.ts | 6 ++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 41f5e071ed7..8486204e3dc 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -7,6 +7,7 @@ import { MongoCursorExhaustedError, MongoCursorInUseError, MongoInvalidArgumentError, + MongoOperationTimeoutError, MongoRuntimeError, MongoTailableCursorError } from '../error'; @@ -142,12 +143,11 @@ export type AbstractCursorEvents = { /** @public */ export abstract class AbstractCursor< - TSchema = any, - CursorEvents extends AbstractCursorEvents = AbstractCursorEvents - > + TSchema = any, + CursorEvents extends AbstractCursorEvents = AbstractCursorEvents +> extends TypedEventEmitter - implements AsyncDisposable -{ + implements AsyncDisposable { /** @internal */ private cursorId: Long | null; /** @internal */ @@ -886,7 +886,11 @@ export abstract class AbstractCursor< ); } } catch (error) { - squashError(error); + if (error instanceof MongoOperationTimeoutError) { + throw error; + } else { + squashError(error); + } } finally { if (session?.owner === this) { await session.endSession({ error }); diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index 7f8f354f18b..fbf3fefe3e9 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -12,7 +12,7 @@ import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cu import { AbstractCursor, CursorTimeoutMode } from './abstract_cursor'; /** @public */ -export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} +export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions { } /** * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB @@ -38,10 +38,12 @@ export class AggregationCursor extends AbstractCursor { this.pipeline = pipeline; this.aggregateOptions = options; + const lastStage = this.pipeline[this.pipeline.length - 1]; + if ( this.cursorOptions.timeoutMS != null && this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - this.pipeline.find(stage => stage.$out != null || stage.$merge != null) != null + lastStage.$out != null || lastStage.$merge != null ) throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } From 54286ae7722c321c3960b7b26c6582cc9ce9beda Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 3 Sep 2024 17:06:23 -0400 Subject: [PATCH 40/52] lint --- src/cursor/abstract_cursor.ts | 9 +++++---- src/cursor/aggregation_cursor.ts | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 8486204e3dc..fc5adba59b5 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -143,11 +143,12 @@ export type AbstractCursorEvents = { /** @public */ export abstract class AbstractCursor< - TSchema = any, - CursorEvents extends AbstractCursorEvents = AbstractCursorEvents -> + TSchema = any, + CursorEvents extends AbstractCursorEvents = AbstractCursorEvents + > extends TypedEventEmitter - implements AsyncDisposable { + implements AsyncDisposable +{ /** @internal */ private cursorId: Long | null; /** @internal */ diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index fbf3fefe3e9..f58f3a5542d 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -12,7 +12,7 @@ import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cu import { AbstractCursor, CursorTimeoutMode } from './abstract_cursor'; /** @public */ -export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions { } +export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} /** * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB @@ -41,9 +41,10 @@ export class AggregationCursor extends AbstractCursor { const lastStage = this.pipeline[this.pipeline.length - 1]; if ( - this.cursorOptions.timeoutMS != null && - this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - lastStage.$out != null || lastStage.$merge != null + (this.cursorOptions.timeoutMS != null && + this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + lastStage.$out != null) || + lastStage.$merge != null ) throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } From 9114290f88c2c06613c1d512a48504cf83e2fe6e Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 3 Sep 2024 17:17:57 -0400 Subject: [PATCH 41/52] type annotation and bracket reorganization --- src/cursor/abstract_cursor.ts | 3 +-- src/cursor/aggregation_cursor.ts | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index fc5adba59b5..0c64ce225d2 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -889,9 +889,8 @@ export abstract class AbstractCursor< } catch (error) { if (error instanceof MongoOperationTimeoutError) { throw error; - } else { - squashError(error); } + squashError(error); } finally { if (session?.owner === this) { await session.endSession({ error }); diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index f58f3a5542d..290057d202d 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -38,13 +38,12 @@ export class AggregationCursor extends AbstractCursor { this.pipeline = pipeline; this.aggregateOptions = options; - const lastStage = this.pipeline[this.pipeline.length - 1]; + const lastStage: Document | undefined = this.pipeline[this.pipeline.length - 1]; if ( - (this.cursorOptions.timeoutMS != null && - this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && - lastStage.$out != null) || - lastStage.$merge != null + this.cursorOptions.timeoutMS != null && + this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && + (lastStage?.$merge != null || lastStage?.$out != null) ) throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode'); } From a47337b3a04fdc5324e92743a55f3e918371b694 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 4 Sep 2024 13:41:36 -0400 Subject: [PATCH 42/52] skip flaky test and replace with more consistent one --- .../client_side_operations_timeout.spec.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index 9e00f0a449c..5da1e4bcb96 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -37,7 +37,9 @@ const skippedTests = { 'Non=tailable cursor iteration timeoutMS is refreshed for getMore if timeoutMode is iteration - failure': 'TODO(DRIVERS-2965)', 'Non-tailable cursor lifetime remaining timeoutMS applied to getMore if timeoutMode is unset': - 'TODO(DRIVERS-2965)' + 'TODO(DRIVERS-2965)', + 'maxTimeMS value in the command is less than timeoutMS': + 'TODO(DRIVERS-2970): see modified test in unified-csot-node-specs' }; describe('CSOT spec tests', function () { From 3236c83842336a4399557a1e3e649ec2a5ba5bfd Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 4 Sep 2024 15:24:07 -0400 Subject: [PATCH 43/52] spec test fixes --- src/cursor/abstract_cursor.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 0c64ce225d2..6ea938c6cb4 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -870,6 +870,7 @@ export abstract class AbstractCursor< this.cursorId = Long.ZERO; let timeoutContext: TimeoutContext | undefined; if (timeoutMS != null) { + this.timeoutContext?.clear(); timeoutContext = TimeoutContext.create({ serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, timeoutMS @@ -887,9 +888,6 @@ export abstract class AbstractCursor< ); } } catch (error) { - if (error instanceof MongoOperationTimeoutError) { - throw error; - } squashError(error); } finally { if (session?.owner === this) { From 743b55f9a17192c8a35d9f4db30087bbcc97f584 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 4 Sep 2024 15:24:57 -0400 Subject: [PATCH 44/52] lint --- src/cursor/abstract_cursor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 6ea938c6cb4..bdce942b5bf 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -7,7 +7,6 @@ import { MongoCursorExhaustedError, MongoCursorInUseError, MongoInvalidArgumentError, - MongoOperationTimeoutError, MongoRuntimeError, MongoTailableCursorError } from '../error'; From 122487f26cca82bf5482aacb3e6510181248e50a Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 9 Sep 2024 11:45:34 -0400 Subject: [PATCH 45/52] eslint --- .../client_side_operations_timeout.spec.test.ts | 2 +- .../client-side-operations-timeout/node_csot.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts index f7b6deb7509..99914fa08e7 100644 --- a/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts +++ b/test/integration/client-side-operations-timeout/client_side_operations_timeout.spec.test.ts @@ -53,7 +53,7 @@ describe('CSOT spec tests', function () { } } - runUnifiedSuite(specs, (test, configuration) => { + runUnifiedSuite(specs, (test, configuration) => { const sessionCSOTTests = ['timeoutMS applied to withTransaction']; if ( sessionCSOTTests.includes(test.description) && diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index 54b6818e2dc..b7a84e28fc7 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -578,7 +578,7 @@ describe('CSOT driver tests', metadata, () => { 'TODO(NODE-6305): implement CSOT for Tailable cursors'; describe.skip('Tailable awaitData cursors').skipReason = 'TODO(NODE-6305): implement CSOT for Tailable cursors'; - + describe('when using an explicit session', () => { const metadata: MongoDBMetadataUI = { requires: { topology: ['replicaset'], mongodb: '>=4.4' } From 6a7e900fa1bd08d3ba12e0004a167cfdd80b0344 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 9 Sep 2024 14:30:46 -0400 Subject: [PATCH 46/52] bump timeoutMS --- .../client-side-operations-timeout/node_csot.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index b7a84e28fc7..edf49678fcb 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -352,7 +352,7 @@ describe('CSOT driver tests', metadata, () => { data: { failCommands: ['find', 'getMore'], blockConnection: true, - blockTimeMS: 25 + blockTimeMS: 50 } }; @@ -414,10 +414,10 @@ describe('CSOT driver tests', metadata, () => { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 50 }) + .find({}, { batchSize: 1, timeoutMode: 'iteration', timeoutMS: 100 }) .project({ _id: 0 }); - // Iterating over 3 documents in the collection, each artificially taking ~25 ms due to failpoint. If timeoutMS is not refreshed, then we'd expect to error + // Iterating over 3 documents in the collection, each artificially taking ~50 ms due to failpoint. If timeoutMS is not refreshed, then we'd expect to error for await (const doc of cursor) { expect(doc).to.deep.equal({ x: 1 }); } From 0e14e9868984422be8c4c837c0892e02862dad76 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 9 Sep 2024 15:11:09 -0400 Subject: [PATCH 47/52] bump timeoutMS and failpoint blockTimeMS --- .../client-side-operations-timeout/node_csot.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index edf49678fcb..9d0cd082845 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -466,7 +466,7 @@ describe('CSOT driver tests', metadata, () => { data: { failCommands: ['find', 'getMore'], blockConnection: true, - blockTimeMS: 25 + blockTimeMS: 50 } }; @@ -510,13 +510,13 @@ describe('CSOT driver tests', metadata, () => { const cursor = client .db('db') .collection('coll') - .find({}, { timeoutMode: 'cursorLifetime', timeoutMS: 50 }) + .find({}, { timeoutMode: 'cursorLifetime', timeoutMS: 100 }) .project({ _id: 0 }); const doc = await cursor.next(); expect(doc).to.deep.equal({ x: 1 }); expect(cursor.documents.length).to.be.gt(0); - await setTimeout(50); + await setTimeout(100); const docOrErr = await cursor.next().then( d => d, @@ -533,13 +533,15 @@ describe('CSOT driver tests', metadata, () => { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 50 }) + .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 100 }) + .project({ _id: 0 }); + const doc = await cursor.next(); expect(doc).to.deep.equal({ x: 1 }); expect(cursor.documents.length).to.equal(0); - await setTimeout(50); + await setTimeout(100); const docOrErr = await cursor.next().then( d => d, From c1572b186f307ef522ad7df1e524caa0cda95ed9 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 9 Sep 2024 15:40:59 -0400 Subject: [PATCH 48/52] lint --- .../client-side-operations-timeout/node_csot.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index 9d0cd082845..8c3cd1f472c 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -536,7 +536,7 @@ describe('CSOT driver tests', metadata, () => { .find({}, { batchSize: 1, timeoutMode: 'cursorLifetime', timeoutMS: 100 }) .project({ _id: 0 }); - + const doc = await cursor.next(); expect(doc).to.deep.equal({ x: 1 }); expect(cursor.documents.length).to.equal(0); From 1cbfbebbb86d98deb86abf9669dc7c03063c853e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 10 Sep 2024 13:17:56 -0400 Subject: [PATCH 49/52] chore: bump timeout avg --- .../unified-csot-node-specs/command-execution.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json b/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json index 8300e403749..dd6fcb2cf84 100644 --- a/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json +++ b/test/integration/client-side-operations-timeout/unified-csot-node-specs/command-execution.json @@ -140,7 +140,7 @@ "command": { "insert": "timeoutColl", "maxTimeMS": { - "$$lte": 450 + "$$lte": 500 } } } From 829d4fe6c6f70c3034c1764b903e8d1d1d8dfa7e Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 14:11:23 -0400 Subject: [PATCH 50/52] WIP --- src/collection.ts | 7 ++++--- src/cursor/abstract_cursor.ts | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index c0ec655a03e..ed3c86a8594 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -678,7 +678,8 @@ export class Collection { ); return true; } catch (error) { - if (error instanceof MongoOperationTimeoutError) throw error; + if (error instanceof MongoOperationTimeoutError) throw error; // TODO: Check the spec for index management behaviour/file a drivers ticket for this + // Seems like we should throw all errors return false; } } @@ -1131,8 +1132,8 @@ export class Collection { indexNameOrOptions == null ? null : typeof indexNameOrOptions === 'object' - ? null - : indexNameOrOptions; + ? null + : indexNameOrOptions; return new ListSearchIndexesCursor(this as TODO_NODE_3286, indexName, options); } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index bdce942b5bf..0b63ade0974 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -824,6 +824,7 @@ export abstract class AbstractCursor< // otherwise need to call getMore const batchSize = this.cursorOptions.batchSize || 1000; + // TODO: move calculation of omitMaxTimeMS to here try { const response = await this.getMore(batchSize); From 531865968c7d2a672024e4b1a449d7bc2c8eb85c Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 16:57:09 -0400 Subject: [PATCH 51/52] review comments --- src/cursor/abstract_cursor.ts | 30 ++++++++++++++++----------- src/cursor/aggregation_cursor.ts | 1 - src/cursor/change_stream_cursor.ts | 1 - src/cursor/find_cursor.ts | 1 - src/cursor/list_collections_cursor.ts | 1 - src/cursor/list_indexes_cursor.ts | 1 - src/cursor/run_command_cursor.ts | 2 -- src/timeout.ts | 2 +- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 0b63ade0974..31fcbab4e9a 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -60,13 +60,13 @@ export interface CursorStreamOptions { /** @public */ export type CursorFlag = (typeof CURSOR_FLAGS)[number]; -/** @internal */ +/** @public*/ export const CursorTimeoutMode = Object.freeze({ ITERATION: 'iteration', LIFETIME: 'cursorLifetime' } as const); -/** @internal +/** @public * TODO(NODE-5688): Document and release * */ export type CursorTimeoutMode = (typeof CursorTimeoutMode)[keyof typeof CursorTimeoutMode]; @@ -131,8 +131,7 @@ export type InternalAbstractCursorOptions = Omit extends AbstractCursor { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.aggregateOptions, ...this.cursorOptions, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); diff --git a/src/cursor/change_stream_cursor.ts b/src/cursor/change_stream_cursor.ts index 586a2c7fe06..13f58675552 100644 --- a/src/cursor/change_stream_cursor.ts +++ b/src/cursor/change_stream_cursor.ts @@ -130,7 +130,6 @@ export class ChangeStreamCursor< const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.cursorOptions, ...this.changeStreamCursorOptions, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index bf3cfb8cf5b..dd8a3407d28 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -66,7 +66,6 @@ export class FindCursor extends AbstractCursor { const findOperation = new FindOperation(this.namespace, this.cursorFilter, { ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); diff --git a/src/cursor/list_collections_cursor.ts b/src/cursor/list_collections_cursor.ts index c39b45d553f..9b69de1b935 100644 --- a/src/cursor/list_collections_cursor.ts +++ b/src/cursor/list_collections_cursor.ts @@ -38,7 +38,6 @@ export class ListCollectionsCursor< const operation = new ListCollectionsOperation(this.parent, this.filter, { ...this.cursorOptions, ...this.options, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); diff --git a/src/cursor/list_indexes_cursor.ts b/src/cursor/list_indexes_cursor.ts index 9bdaaa56d39..0f768f3b699 100644 --- a/src/cursor/list_indexes_cursor.ts +++ b/src/cursor/list_indexes_cursor.ts @@ -27,7 +27,6 @@ export class ListIndexesCursor extends AbstractCursor { const operation = new ListIndexesOperation(this.parent, { ...this.cursorOptions, ...this.options, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, session }); diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 5f138786feb..86a883b8eea 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -110,7 +110,6 @@ export class RunCommandCursor extends AbstractCursor { ...this.cursorOptions, session: session, readPreference: this.cursorOptions.readPreference, - omitMaxTimeMS: this.cursorOptions?.omitMaxTimeMSOnInitialCommand, responseType: CursorResponse }); @@ -130,7 +129,6 @@ export class RunCommandCursor extends AbstractCursor { ...this.cursorOptions, session: this.session, ...this.getMoreOptions, - omitMaxTimeMS: this.cursorOptions.omitMaxTimeMSOnGetMore }); return await executeOperation(this.client, getMoreOperation, this.timeoutContext); diff --git a/src/timeout.ts b/src/timeout.ts index f707e10172d..da3e9c32ba6 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -41,7 +41,7 @@ export class Timeout extends Promise { public readonly start: number; public ended: number | null = null; public duration: number; - public timedOut = false; + private timedOut = false; public cleared = false; get remainingTime(): number { From c48ca9f81371dbc0bc914a7df8d47e4514023b45 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 17:01:08 -0400 Subject: [PATCH 52/52] lint --- src/collection.ts | 4 ++-- src/cursor/abstract_cursor.ts | 20 ++++++++++---------- src/cursor/run_command_cursor.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index ed3c86a8594..37ea40db905 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1132,8 +1132,8 @@ export class Collection { indexNameOrOptions == null ? null : typeof indexNameOrOptions === 'object' - ? null - : indexNameOrOptions; + ? null + : indexNameOrOptions; return new ListSearchIndexesCursor(this as TODO_NODE_3286, indexName, options); } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 31fcbab4e9a..cbf226f3035 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -219,7 +219,7 @@ export abstract class AbstractCursor< if (options.timeoutMode != null) throw new MongoInvalidArgumentError('Cannot set timeoutMode without setting timeoutMS'); } - this.cursorOptions.omitMaxTimeMS = + this.cursorOptions.omitMaxTimeMS = this.cursorOptions.timeoutMS != null && ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && !this.cursorOptions.tailable) || @@ -431,9 +431,9 @@ export abstract class AbstractCursor< return false; } - if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { - this.timeoutContext?.refresh(); - } + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); + } try { do { if ((this.documents?.length ?? 0) !== 0) { @@ -455,9 +455,9 @@ export abstract class AbstractCursor< if (this.cursorId === Long.ZERO) { throw new MongoCursorExhaustedError(); } - if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { - this.timeoutContext?.refresh(); - } + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); + } try { do { @@ -485,9 +485,9 @@ export abstract class AbstractCursor< throw new MongoCursorExhaustedError(); } - if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { - this.timeoutContext?.refresh(); - } + if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) { + this.timeoutContext?.refresh(); + } try { let doc = this.documents?.shift(this.cursorOptions); if (doc != null) { diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 86a883b8eea..6b31ce2263a 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -128,7 +128,7 @@ export class RunCommandCursor extends AbstractCursor { const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, { ...this.cursorOptions, session: this.session, - ...this.getMoreOptions, + ...this.getMoreOptions }); return await executeOperation(this.client, getMoreOperation, this.timeoutContext);