Uploaded image for project: 'Node.js Driver'
  1. Node.js Driver
  2. NODE-5711

Using Promise.all with multi-document transactions writes partial data

    • 2
    • Not Needed
    • Not Needed

      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

            Assignee:
            warren.james@mongodb.com Warren James
            Reporter:
            elad.chen@lusha.com Elad Chen
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: