-
Type: Bug
-
Resolution: Gone away
-
Priority: Unknown
-
None
-
Affects Version/s: 4.5.0
-
Component/s: Transactions
What problem are you facing?
Using Promise.all with a multi document transactions writes partial data.
What driver and relevant dependency versions are you using?
4.5.0 (happened on version 6.x as well)
Steps to reproduce?
const Mongo = require("mongodb"); const DB_NAME = 'FILL_DB_NAME'; const COLLECTION_ONE = 'tx_collection_one'; const COLLECTION_TWO = 'tx_collection_two'; const CONNECTION_STRING = "FILL_CONNECTION_STRING" const connect = async () => { const client = new Mongo.MongoClient(CONNECTION_STRING); await client.connect(); console.log('connected'); return client; }; const withTransaction = async (session, cb) => { const commit = async () => { try { session.startTransaction({ // readPreference: Mongo.ReadPreference.primary, // readConcern: { level: 'local' }, writeConcern: { w: 'majority', journal: true }, maxCommitTimeMS: 500, }); await cb(); await session.commitTransaction(); } catch (e) { await session.abortTransaction(); throw e; } }; try { // The cb is expected to fail, this commits the transaction 15 times for (let i = 0; i < 15; i += 1) { await commit().catch(e => { if (e instanceof Mongo.MongoError) { console.log(`mongo error -> ${e.constructor.name}: ${e.message} (${e.code})`); } }); await new Promise((r) => setTimeout(r, 100)); } } finally { await session.endSession(); } }; const createDocuments = async () => { const documents = []; for (let i = 0; i < 3; i += 1) { documents.push({ id: i }); } return documents; }; const run = async () => { try { const client = await connect(); const session = client.startSession(); const db = client.db(DB_NAME); const collectionOne = db.collection(COLLECTION_ONE); const collectionTwo = db.collection(COLLECTION_TWO); // Create empty collections (without using the session) await Promise.all([ db.createCollection(COLLECTION_ONE), db.createCollection(COLLECTION_TWO), ]); // Simulate a failure writing to three places await withTransaction(session, async () => { await Promise.all([ collectionOne.insertMany([createDocuments()], { session }), new Promise((_, reject) => reject(new Error())), collectionTwo.insertMany([createDocuments()], { session }) ]); }); await Promise.all([ collectionOne.countDocuments().then((count) => { console.log("collection_one count -> %d", count); }), collectionTwo.countDocuments().then((count) => { console.log("collection_two count -> %d", count); }), ]); await Promise.all([ db.dropCollection(COLLECTION_ONE), db.dropCollection(COLLECTION_TWO), ]); await client.close(); } catch (e) { console.error(e); } }; return run();
Run the attached code snippet, fill in the needed db name / connection string.
You'll see a log printed saying the collections document counts are 2 & 3:
connected collection_one count -> 3 collection_two count -> 2