# Test that all the operations go to the same mongos. # # In tests that don't include command-started events the assertion is implicit: # that all the read operations succeed. If the driver does not properly pin to # a single mongos then one of the operations in a transaction will eventually # be sent to a different mongos, which is unaware of the transaction, and the # mongos will return a command error. An example of such an error is: # { # 'ok': 0.0, # 'errmsg': 'cannot continue txnId -1 for session 28938f50-9d29-4ca5-8de5-ddaf261267c4 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= with txnId 1', # 'code': 251, # 'codeName': 'NoSuchTransaction', # 'errorLabels': ['TransientTransactionError'] # } runOn: - minServerVersion: "4.1.8" topology: ["sharded"] database_name: &database_name "transaction-tests" collection_name: &collection_name "test" data: &data - {_id: 1} - {_id: 2} tests: - description: countDocuments useMultipleMongoses: true operations: - &startTransaction name: startTransaction object: session0 - &countDocuments name: countDocuments object: collection arguments: filter: _id: 2 session: session0 result: 1 - *countDocuments - *countDocuments - *countDocuments - *countDocuments - *countDocuments - *countDocuments - *countDocuments - &commitTransaction name: commitTransaction object: session0 outcome: collection: data: *data - description: distinct useMultipleMongoses: true operations: - *startTransaction - &distinct name: distinct object: collection arguments: fieldName: _id session: session0 result: [1, 2] - *distinct - *distinct - *distinct - *distinct - *distinct - *distinct - *distinct - *commitTransaction outcome: collection: data: *data - description: find useMultipleMongoses: true operations: - name: startTransaction object: session0 - &find name: find object: collection arguments: filter: _id: 2 session: session0 result: - {_id: 2} - *find - *find - *find - *find - *find - *find - *find - *commitTransaction outcome: collection: data: *data - description: insertOne useMultipleMongoses: true operations: - name: startTransaction object: session0 - name: insertOne object: collection arguments: document: _id: 3 session: session0 result: insertedId: 3 - name: insertOne object: collection arguments: document: _id: 4 session: session0 result: insertedId: 4 - name: insertOne object: collection arguments: document: _id: 5 session: session0 result: insertedId: 5 - name: insertOne object: collection arguments: document: _id: 6 session: session0 result: insertedId: 6 - name: insertOne object: collection arguments: document: _id: 7 session: session0 result: insertedId: 7 - name: insertOne object: collection arguments: document: _id: 8 session: session0 result: insertedId: 8 - name: insertOne object: collection arguments: document: _id: 9 session: session0 result: insertedId: 9 - name: insertOne object: collection arguments: document: _id: 10 session: session0 result: insertedId: 10 - *commitTransaction outcome: collection: data: - {_id: 1} - {_id: 2} - {_id: 3} - {_id: 4} - {_id: 5} - {_id: 6} - {_id: 7} - {_id: 8} - {_id: 9} - {_id: 10} - description: mixed read write operations useMultipleMongoses: true operations: - name: startTransaction object: session0 - name: insertOne object: collection arguments: document: _id: 3 session: session0 result: insertedId: 3 - &countDocuments name: countDocuments object: collection arguments: filter: _id: 3 session: session0 result: 1 - *countDocuments - *countDocuments - *countDocuments - *countDocuments - name: insertOne object: collection arguments: document: _id: 4 session: session0 result: insertedId: 4 - name: insertOne object: collection arguments: document: _id: 5 session: session0 result: insertedId: 5 - name: insertOne object: collection arguments: document: _id: 6 session: session0 result: insertedId: 6 - name: insertOne object: collection arguments: document: _id: 7 session: session0 result: insertedId: 7 - *commitTransaction outcome: collection: data: - {_id: 1} - {_id: 2} - {_id: 3} - {_id: 4} - {_id: 5} - {_id: 6} - {_id: 7} - description: multiple commits useMultipleMongoses: true operations: - name: startTransaction object: session0 - name: insertMany object: collection arguments: documents: - _id: 3 - _id: 4 session: session0 result: insertedIds: {0: 3, 1: 4} # Session is pinned and remains pinned after successful commits. - &assertSessionPinned name: assertSessionPinned object: testRunner arguments: session: session0 - *commitTransaction - *assertSessionPinned - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *commitTransaction - *assertSessionPinned outcome: collection: data: - {_id: 1} - {_id: 2} - {_id: 3} - {_id: 4} - description: remain pinned after non-transient error on commit useMultipleMongoses: true operations: - name: startTransaction object: session0 - name: insertMany object: collection arguments: documents: - _id: 3 - _id: 4 session: session0 result: insertedIds: {0: 3, 1: 4} # Session is pinned. - *assertSessionPinned # Fail the commit with a non-transient error. - name: targetedFailPoint object: testRunner arguments: session: session0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: failCommands: ["commitTransaction"] errorCode: 51 # ManualInterventionRequired - name: commitTransaction object: session0 result: errorLabelsOmit: ["TransientTransactionError"] errorCode: 51 - *assertSessionPinned # The next commit should succeed. - name: commitTransaction object: session0 - *assertSessionPinned outcome: collection: data: - {_id: 1} - {_id: 2} - {_id: 3} - {_id: 4} - description: unpin after transient error within a transaction useMultipleMongoses: true operations: - name: startTransaction object: session0 - name: insertOne object: collection arguments: session: session0 document: _id: 3 result: insertedId: 3 # Enable the fail point only on the Mongos that session0 is pinned to. - name: targetedFailPoint object: testRunner arguments: session: session0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: failCommands: ["insert"] closeConnection: true - name: insertOne object: collection arguments: session: session0 document: _id: 4 result: errorLabelsContain: ["TransientTransactionError"] errorLabelsOmit: ["UnknownTransactionCommitResult"] # Session unpins from the first mongos after the insert error and # abortTransaction succeeds immediately on any mongos. - name: abortTransaction object: session0 expectations: - command_started_event: command: insert: *collection_name documents: - _id: 3 ordered: true readConcern: lsid: session0 txnNumber: $numberLong: "1" startTransaction: true autocommit: false writeConcern: command_name: insert database_name: *database_name - command_started_event: command: insert: *collection_name documents: - _id: 4 ordered: true readConcern: lsid: session0 txnNumber: $numberLong: "1" startTransaction: autocommit: false writeConcern: command_name: insert database_name: *database_name - command_started_event: command: abortTransaction: 1 lsid: session0 txnNumber: $numberLong: "1" startTransaction: autocommit: false writeConcern: recoveryToken: 42 command_name: abortTransaction database_name: admin outcome: collection: data: - _id: 1 - _id: 2 # Applications should not run commitTransaction after transient errors but # the transactions API allows it and this test confirms unpinning behavior. # In a sharded cluster, a transient error within a transaction unpins the # session. This way a subsequent abort can "succeed" immediately instead of # blocking for serverSelectionTimeoutMS in the case the mongos went down. # However since the abortTransaction helper ignores errors, this test uses # commitTransaction to prove the session was unpinned. - description: unpin after transient error within a transaction and commit useMultipleMongoses: true clientOptions: # Increase heartbeatFrequencyMS to avoid the race condition where an in # flight heartbeat refreshes the first mongoes' SDAM state in between # the insert connection error and the single commit attempt. heartbeatFrequencyMS: 30000 operations: - name: startTransaction object: session0 - name: insertOne object: collection arguments: session: session0 document: _id: 3 result: insertedId: 3 # Enable the fail point only on the Mongos that session0 is pinned to. # Fail isMaster to prevent the heartbeat requested directly after the # insert error from racing with server selection for the commit. # Note: times: 7 is slightly artbitrary but it accounts for one failed # insert and some SDAM heartbeats. A test runner will have multiple # clients connected to this server so this fail point configuration # is also racy. - name: targetedFailPoint object: testRunner arguments: session: session0 failPoint: configureFailPoint: failCommand mode: { times: 7 } data: failCommands: ["insert", "isMaster"] closeConnection: true - name: insertOne object: collection arguments: session: session0 document: _id: 4 result: errorLabelsContain: ["TransientTransactionError"] errorLabelsOmit: ["UnknownTransactionCommitResult"] # Session unpins from the first mongos after the insert error and # commitTransaction selects the second mongos which is unaware of the # transaction and therefore fails with NoSuchTransaction error. If this # commit succeeds it indicates a bug, either: # - the driver mistakenly remained pinned even after the insert error, or # - the test client was initialized with a single mongos seed # # Note that the commit attempt should not select the original mongos # because that server's SDAM state is reset by the connection error, # heartbeatFrequencyMS is high, and subsequent isMaster heartbeats # should fail. - name: commitTransaction object: session0 result: errorLabelsContain: ["TransientTransactionError"] errorLabelsOmit: ["UnknownTransactionCommitResult"] errorCodeName: NoSuchTransaction expectations: - command_started_event: command: insert: *collection_name documents: - _id: 3 ordered: true readConcern: lsid: session0 txnNumber: $numberLong: "1" startTransaction: true autocommit: false writeConcern: command_name: insert database_name: *database_name - command_started_event: command: insert: *collection_name documents: - _id: 4 ordered: true readConcern: lsid: session0 txnNumber: $numberLong: "1" startTransaction: autocommit: false writeConcern: command_name: insert database_name: *database_name - command_started_event: command: commitTransaction: 1 lsid: session0 txnNumber: $numberLong: "1" startTransaction: autocommit: false writeConcern: recoveryToken: 42 command_name: commitTransaction database_name: admin outcome: collection: data: - _id: 1 - _id: 2