description: cursors are correctly pinned to connections for load-balanced clusters schemaVersion: '1.3' runOnRequirements: - topologies: [ load-balanced ] createEntities: - client: id: &client0 client0 useMultipleMongoses: true observeEvents: - commandStartedEvent - commandSucceededEvent - commandFailedEvent - connectionReadyEvent - connectionClosedEvent - connectionCheckedOutEvent - connectionCheckedInEvent - database: id: &database0 database0 client: *client0 databaseName: &database0Name database0Name - collection: id: &collection0 collection0 database: *database0 collectionName: &collection0Name coll0 - collection: id: &collection1 collection1 database: *database0 collectionName: &collection1Name coll1 - collection: id: &collection2 collection2 database: *database0 collectionName: &collection2Name coll2 initialData: - collectionName: *collection0Name databaseName: *database0Name documents: - { _id: 1 } - { _id: 2 } - { _id: 3 } - collectionName: *collection1Name databaseName: *database0Name documents: [] - collectionName: *collection2Name databaseName: *database0Name documents: [] tests: - description: no connection is pinned if all documents are returned in the initial batch operations: - name: createFindCursor object: *collection0 arguments: filter: {} saveResultAsEntity: &cursor0 cursor0 - &assertConnectionNotPinned name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 0 expectEvents: - client: *client0 events: - commandStartedEvent: command: find: *collection0Name filter: {} commandName: find - commandSucceededEvent: reply: cursor: id: 0 firstBatch: { $$type: array } ns: { $$type: string } commandName: find - client: *client0 eventType: cmap events: - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - description: pinned connections are returned when the cursor is drained operations: - &createAndSaveCursor name: createFindCursor object: *collection0 arguments: filter: {} batchSize: 2 saveResultAsEntity: &cursor0 cursor0 - &assertConnectionPinned name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 1 - name: iterateUntilDocumentOrError object: *cursor0 expectResult: { _id: 1 } - name: iterateUntilDocumentOrError object: *cursor0 expectResult: { _id: 2 } - name: iterateUntilDocumentOrError object: *cursor0 expectResult: { _id: 3 } - *assertConnectionNotPinned - &closeCursor name: close object: *cursor0 expectEvents: - client: *client0 events: - &findWithBatchSizeStarted commandStartedEvent: command: find: *collection0Name filter: {} batchSize: 2 commandName: find - &findWithBatchSizeSucceeded commandSucceededEvent: reply: cursor: id: { $$type: long } firstBatch: { $$type: array } ns: { $$type: string } commandName: find - &getMoreStarted commandStartedEvent: command: getMore: { $$type: long } collection: *collection0Name commandName: getMore - &getMoreSucceeded commandSucceededEvent: reply: cursor: id: 0 ns: { $$type: string } nextBatch: { $$type: array } commandName: getMore - client: *client0 eventType: cmap events: - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - description: pinned connections are returned to the pool when the cursor is closed operations: - *createAndSaveCursor - *assertConnectionPinned - *closeCursor - *assertConnectionNotPinned expectEvents: - client: *client0 events: - *findWithBatchSizeStarted - *findWithBatchSizeSucceeded - &killCursorsStarted commandStartedEvent: commandName: killCursors - &killCursorsSucceeded commandSucceededEvent: commandName: killCursors - client: *client0 eventType: cmap events: - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # If a network error occurs during a getMore request, the connection must remain pinned. and drivers must not # attempt to send a killCursors command when the cursor is closed because the connection is no longer valid. - description: pinned connections are not returned after an network error during getMore operations: - name: failPoint object: testRunner arguments: client: *client0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: failCommands: [ getMore ] closeConnection: true - *createAndSaveCursor - *assertConnectionPinned - name: iterateUntilDocumentOrError object: *cursor0 expectResult: _id: 1 - name: iterateUntilDocumentOrError object: *cursor0 expectResult: _id: 2 # Third next() call should perform a getMore. - name: iterateUntilDocumentOrError object: *cursor0 expectError: # Network errors are considered client-side errors per the unified test format spec. isClientError: true - *assertConnectionPinned - *closeCursor # Execute a close operation to actually release the connection. - *assertConnectionNotPinned expectEvents: - client: *client0 events: - *findWithBatchSizeStarted - *findWithBatchSizeSucceeded - *getMoreStarted - &getMoreFailed commandFailedEvent: commandName: getMore - client: *client0 eventType: cmap events: # Events to set the failpoint. - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Events for the find command + getMore. - connectionCheckedOutEvent: {} # Events for the close() operation. - connectionCheckedInEvent: {} - connectionClosedEvent: reason: error - description: pinned connections are returned after a network error during a killCursors request operations: - name: failPoint object: testRunner arguments: client: *client0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: failCommands: [ killCursors ] closeConnection: true - *createAndSaveCursor - *assertConnectionPinned - *closeCursor - *assertConnectionNotPinned expectEvents: - client: *client0 events: - *findWithBatchSizeStarted - *findWithBatchSizeSucceeded - *killCursorsStarted - commandFailedEvent: commandName: killCursors - client: *client0 eventType: cmap events: # Events to set the failpoint. - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Events for the find command + killCursors. - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - connectionClosedEvent: reason: error - description: pinned connections are not returned to the pool after a non-network error on getMore operations: - name: failPoint object: testRunner arguments: client: *client0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: failCommands: [ getMore ] errorCode: &hostNotFoundCode 7 # This is not a state change error code, so it should not cause SDAM changes. - *createAndSaveCursor - name: iterateUntilDocumentOrError object: *cursor0 expectResult: _id: 1 - name: iterateUntilDocumentOrError object: *cursor0 expectResult: _id: 2 - name: iterateUntilDocumentOrError object: *cursor0 expectError: errorCode: *hostNotFoundCode - *assertConnectionPinned - *closeCursor - *assertConnectionNotPinned expectEvents: - client: *client0 events: - *findWithBatchSizeStarted - *findWithBatchSizeSucceeded - *getMoreStarted - *getMoreFailed - *killCursorsStarted - *killCursorsSucceeded - client: *client0 eventType: cmap events: # Events to set the failpoint. - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Events for the find command + getMore + killCursors. - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Basic tests for cursor-creating commands besides "find". We don't need to replicate the full set of tests defined # above for each such command. Instead, only one test is needed per command to ensure that the pinned connection is # correctly passed down to the server. # # Each test creates a cursor with a small batch size and fully iterates it. Because drivers do not publish CMAP # events when using pinned connections, each test asserts that only one set of ready/checkout/checkin events are # published. - description: aggregate pins the cursor to a connection operations: - name: aggregate object: *collection0 arguments: pipeline: [] batchSize: 2 - name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 0 expectEvents: - client: *client0 events: - commandStartedEvent: command: aggregate: *collection0Name cursor: batchSize: 2 commandName: aggregate - commandSucceededEvent: commandName: aggregate - *getMoreStarted - *getMoreSucceeded - client: *client0 eventType: cmap events: - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - description: listCollections pins the cursor to a connection runOnRequirements: - serverless: forbid # CLOUDP-98562 listCollections batchSize is ignored on serverless. operations: - name: listCollections object: *database0 arguments: filter: {} batchSize: 2 - name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 0 expectEvents: - client: *client0 events: - commandStartedEvent: command: listCollections: 1 cursor: batchSize: 2 commandName: listCollections databaseName: *database0Name - commandSucceededEvent: commandName: listCollections # Write out the event for getMore rather than using the getMoreStarted anchor because the "collection" field # is not equal to *collection0Name as the command is not executed against a collection. - commandStartedEvent: command: getMore: { $$type: long } collection: { $$type: string } commandName: getMore - *getMoreSucceeded - client: *client0 eventType: cmap events: - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - description: listIndexes pins the cursor to a connection operations: # There is an automatic index on _id so we create two more indexes to force multiple batches with batchSize=2. - name: createIndex object: *collection0 arguments: keys: &x1IndexSpec { x: 1 } name: &x1IndexName x_1 - name: createIndex object: *collection0 arguments: keys: &y1IndexSpec { y: 1 } name: &y1IndexName y_1 - name: listIndexes object: *collection0 arguments: batchSize: 2 - name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 0 expectEvents: - client: *client0 events: - commandStartedEvent: command: createIndexes: *collection0Name indexes: - name: *x1IndexName key: *x1IndexSpec commandName: createIndexes - commandSucceededEvent: commandName: createIndexes - commandStartedEvent: command: createIndexes: *collection0Name indexes: - name: *y1IndexName key: *y1IndexSpec commandName: createIndexes - commandSucceededEvent: commandName: createIndexes - commandStartedEvent: command: listIndexes: *collection0Name cursor: batchSize: 2 commandName: listIndexes databaseName: *database0Name - commandSucceededEvent: commandName: listIndexes - *getMoreStarted - *getMoreSucceeded - client: *client0 eventType: cmap events: # Events for first createIndexes. - connectionReadyEvent: {} - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Events for second createIndexes. - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} # Events for listIndexes and getMore. - connectionCheckedOutEvent: {} - connectionCheckedInEvent: {} - description: change streams pin to a connection runOnRequirements: - serverless: forbid # Serverless does not support change streams. operations: - name: createChangeStream object: *collection0 arguments: pipeline: [] saveResultAsEntity: &changeStream0 changeStream0 - name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 1 - name: close object: *changeStream0 - name: assertNumberConnectionsCheckedOut object: testRunner arguments: client: *client0 connections: 0 expectEvents: - client: *client0 events: - commandStartedEvent: commandName: aggregate - commandSucceededEvent: commandName: aggregate - commandStartedEvent: commandName: killCursors - commandSucceededEvent: commandName: killCursors - client: *client0 eventType: cmap events: # Events for creating the change stream. - connectionReadyEvent: {} - connectionCheckedOutEvent: {} # Events for closing the change stream. - connectionCheckedInEvent: {}