Skip to content

Commit 33e8248

Browse files
authored
test(NODE-3188): backport transaction pinning tests (#2839)
1 parent 8c8b4c3 commit 33e8248

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+543
-26
lines changed

lib/core/sessions.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,17 +472,20 @@ function endTransaction(session, commandName, callback) {
472472
if (commandName === 'commitTransaction') {
473473
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
474474

475-
if (
476-
e &&
477-
(e instanceof MongoNetworkError ||
475+
if (e) {
476+
if (
477+
e instanceof MongoNetworkError ||
478478
e instanceof MongoWriteConcernError ||
479479
isRetryableError(e) ||
480-
isMaxTimeMSExpiredError(e))
481-
) {
482-
if (isUnknownTransactionCommitResult(e)) {
483-
e.addErrorLabel('UnknownTransactionCommitResult');
480+
isMaxTimeMSExpiredError(e)
481+
) {
482+
if (isUnknownTransactionCommitResult(e)) {
483+
e.addErrorLabel('UnknownTransactionCommitResult');
484484

485-
// per txns spec, must unpin session in this case
485+
// per txns spec, must unpin session in this case
486+
session.transaction.unpinServer();
487+
}
488+
} else if (e.hasErrorLabel('TransientTransactionError')) {
486489
session.transaction.unpinServer();
487490
}
488491
}

lib/core/transactions.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@ class Transaction {
150150
const nextStates = stateMachine[this.state];
151151
if (nextStates && nextStates.indexOf(nextState) !== -1) {
152152
this.state = nextState;
153-
if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
153+
if (
154+
this.state === TxnState.NO_TRANSACTION ||
155+
this.state === TxnState.STARTING_TRANSACTION ||
156+
this.state === TxnState.TRANSACTION_ABORTED
157+
) {
154158
this.unpinServer();
155159
}
156160
return;

lib/core/wireprotocol/command.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ function _command(server, ns, cmd, options, callback) {
6060
clusterTime = sessionClusterTime;
6161
}
6262

63+
// We need to unpin any read or write commands that happen outside of a pinned
64+
// transaction, so we check if we have a pinned transaction that is no longer
65+
// active, and unpin for all except start or commit.
66+
if (
67+
!session.transaction.isActive &&
68+
session.transaction.isPinned &&
69+
!finalCmd.startTransaction &&
70+
!finalCmd.commitTransaction
71+
) {
72+
session.transaction.unpinServer();
73+
}
74+
6375
const err = applySession(session, finalCmd, options);
6476
if (err) {
6577
return callback(err);

lib/cursor.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ class Cursor extends CoreCursor {
165165
return this.cmd.sort;
166166
}
167167

168+
set session(clientSession) {
169+
this.cursorState.session = clientSession;
170+
}
171+
168172
_initializeCursor(callback) {
169173
if (this.operation && this.operation.session != null) {
170174
this.cursorState.session = this.operation.session;

test/functional/transactions.test.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use strict';
22

3+
const path = require('path');
34
const chai = require('chai');
45
const expect = chai.expect;
56
const core = require('../../lib/core');
67
const sessions = core.Sessions;
78
const TestRunnerContext = require('./spec-runner').TestRunnerContext;
89
const loadSpecTests = require('../spec').loadSpecTests;
10+
const runUnifiedTest = require('./unified-spec-runner/runner').runUnifiedTest;
911
const generateTopologyTests = require('./spec-runner').generateTopologyTests;
1012
const MongoNetworkError = require('../../lib/core').MongoNetworkError;
1113
const semver = require('semver');
@@ -82,14 +84,30 @@ class TransactionsRunnerContext extends TestRunnerContext {
8284
}
8385
}
8486

87+
describe('Transactions Spec Unified Tests', function() {
88+
for (const transactionTest of loadSpecTests(path.join('transactions', 'unified'))) {
89+
expect(transactionTest).to.exist;
90+
context(String(transactionTest.description), function() {
91+
for (const test of transactionTest.tests) {
92+
it(String(test.description), {
93+
metadata: { sessions: { skipLeakTests: true } },
94+
test() {
95+
return runUnifiedTest(this, transactionTest, test);
96+
}
97+
});
98+
}
99+
});
100+
}
101+
});
102+
85103
describe('Transactions', function() {
86104
const testContext = new TransactionsRunnerContext();
87105

88106
[
89-
{ name: 'spec tests', specPath: 'transactions' },
107+
{ name: 'spec tests', specPath: path.join('transactions', 'legacy') },
90108
{
91109
name: 'withTransaction spec tests',
92-
specPath: 'transactions/convenient-api'
110+
specPath: path.join('transactions', 'convenient-api')
93111
}
94112
].forEach(suiteSpec => {
95113
describe(suiteSpec.name, function() {

test/functional/unified-spec-runner/operations.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
22
import { expect } from 'chai';
33
import { Collection, Db, GridFSFile, MongoClient, ObjectId } from '../../../index';
4+
import { CoreCursor } from '../../../lib/core/cursor';
45
import ReadConcern from '../../../lib/read_concern';
56
import ReadPreference from '../../../lib/core/topologies/read_preference';
67
import WriteConcern from '../../../lib/write_concern';
@@ -79,6 +80,18 @@ interface OperationFunctionParams {
7980
type RunOperationFn = (p: OperationFunctionParams) => Promise<Document | boolean | number | void>;
8081
export const operations = new Map<string, RunOperationFn>();
8182

83+
function executeWithPotentialSession(
84+
entities: EntitiesMap,
85+
operation: OperationDescription,
86+
cursor: CoreCursor
87+
) {
88+
const session = entities.getEntity('session', operation.arguments.session, false);
89+
if (session) {
90+
cursor.session = session;
91+
}
92+
return cursor.toArray();
93+
}
94+
8295
operations.set('abortTransaction', async ({ entities, operation }) => {
8396
const session = entities.getEntity('session', operation.object);
8497
return session.abortTransaction();
@@ -89,18 +102,17 @@ operations.set('aggregate', async ({ entities, operation }) => {
89102
if (!(dbOrCollection instanceof Db || dbOrCollection instanceof Collection)) {
90103
throw new Error(`Operation object '${operation.object}' must be a db or collection`);
91104
}
92-
return dbOrCollection
93-
.aggregate(operation.arguments.pipeline, {
94-
allowDiskUse: operation.arguments.allowDiskUse,
95-
batchSize: operation.arguments.batchSize,
96-
bypassDocumentValidation: operation.arguments.bypassDocumentValidation,
97-
maxTimeMS: operation.arguments.maxTimeMS,
98-
maxAwaitTimeMS: operation.arguments.maxAwaitTimeMS,
99-
collation: operation.arguments.collation,
100-
hint: operation.arguments.hint,
101-
out: operation.arguments.out
102-
})
103-
.toArray();
105+
const cursor = dbOrCollection.aggregate(operation.arguments.pipeline, {
106+
allowDiskUse: operation.arguments.allowDiskUse,
107+
batchSize: operation.arguments.batchSize,
108+
bypassDocumentValidation: operation.arguments.bypassDocumentValidation,
109+
maxTimeMS: operation.arguments.maxTimeMS,
110+
maxAwaitTimeMS: operation.arguments.maxAwaitTimeMS,
111+
collation: operation.arguments.collation,
112+
hint: operation.arguments.hint,
113+
out: operation.arguments.out
114+
});
115+
return executeWithPotentialSession(entities, operation, cursor);
104116
});
105117

106118
operations.set('assertCollectionExists', async ({ operation, client }) => {
@@ -288,7 +300,8 @@ operations.set('endSession', async ({ entities, operation }) => {
288300
operations.set('find', async ({ entities, operation }) => {
289301
const collection = entities.getEntity('collection', operation.object);
290302
const { filter, sort, batchSize, limit } = operation.arguments;
291-
return collection.find(filter, { sort, batchSize, limit }).toArray();
303+
const cursor = collection.find(filter, { sort, batchSize, limit });
304+
return executeWithPotentialSession(entities, operation, cursor);
292305
});
293306

294307
operations.set('findOneAndReplace', async ({ entities, operation }) => {

test/manual/atlas_connectivity.test.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@ describe('Atlas Connectivity', function() {
2929
context(configName, function() {
3030
CONFIGS[configName].forEach(connectionString => {
3131
const name = connectionString.indexOf('mongodb+srv') >= 0 ? 'mongodb+srv' : 'normal';
32-
it(`${name} (unified)`, makeConnectionTest(connectionString, { useUnifiedTopology: true }));
33-
it(`${name} (legacy)`, makeConnectionTest(connectionString, { useUnifiedTopology: false }));
32+
// TODO: Skipping until NODE-3308 is merged.
33+
it.skip(
34+
`${name} (unified)`,
35+
makeConnectionTest(connectionString, { useUnifiedTopology: true })
36+
);
37+
it.skip(
38+
`${name} (legacy)`,
39+
makeConnectionTest(connectionString, { useUnifiedTopology: false })
40+
);
3441
});
3542
});
3643
});

0 commit comments

Comments
 (0)