Charles de Jager
Charles de Jager

Reputation: 41

NodeJS and mongoose - UnhandledPromiseRejectionWarning: MongoError: Transaction 1 has been aborted

I am using mongoose in a Node & Mongoose application where I would like to use transactions. But get an unhandled exception. Any ideas on how to fix this?

Versions:
Node: 12.18.1.
Mongoose: 5.9.20

The code I am trying to execute:

const mongoose = require('mongoose');
const config = require('../config');

const database = config.database;
const uri = `mongodb+srv://${database.user}:${database.password}@${database.uri}`;

mongoose
  .connect(uri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: true,
    useCreateIndex: true,
    promiseLibrary: global.Promise
  });

let parentSchema = new mongoose.Schema({
  name: String
});

let childSchema = new mongoose.Schema({
  parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "Parent"
  },
  name: String,
  requiredField: {
    type: String,
    required: true
  }
});

let Parent = mongoose.model("Parent", parentSchema);
let Child = mongoose.model("Child", childSchema);

let session;
// Parent.startSession()
mongoose.startSession()
  .then(_session => {
    session = _session;
    session.startTransaction();
    let aParent = new Parent({ name: 'Parent 1'});
    console.log('created parent');

    return aParent.save({ session: session })

  })
  .then(_aParent => {
    console.log('Parent saved');
    let aChild = new Child({ parent: _aParent._id, name: 'Child 1' });
    console.log('Child created');
    return aChild.save({ session: session });
  })
  .then(_aChild => {
    console.log('child saved')
    session.commitTransaction();
  })
  .catch(_err => {
    console.log('catch error');
    session.abortTransaction();
  })
  .finally(() => {
    session.endSession();
  })

I am getting the following error:

throw new MongoError( ^
MongoError: Attempted illegal state transition from [TRANSACTION_ABORTED] to [TRANSACTION_ABORTED] 
at Transaction.transition (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\transactions.js:159:11)
at commandHandler (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sessions.js:508:27)
at C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sessions.js:545:5
at cb (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sdam\topology.js:670:18)
at C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection_pool.js:354:13
at handleOperationResult (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sdam\server.js:493:5)
at _command (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\wireprotocol\command.js:60:14)
at Object.command (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\wireprotocol\command.js:28:5)
at Connection.command (C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection.js:164:8)
at C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sdam\server.js:281:12
at Object.callback (C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection_pool.js:351:7)
at processWaitQueue (C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection_pool.js:474:23)
at ConnectionPool.checkIn (C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection_pool.js:267:5)
at C:\Users\chdejager\dev\node_modules\mongodb\lib\cmap\connection_pool.js:361:16
at handleOperationResult (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\sdam\server.js:493:5)
at commandResponseHandler 
   (C:\Users\chdejager\dev\node_modules\mongodb\lib\core\wireprotocol\command.js:123:25)

Upvotes: 1

Views: 1920

Answers (1)

Charles de Jager
Charles de Jager

Reputation: 41

So after a few iterations I have figured out that both session.abortTransaction() and session.commitTransaction() return a Promise. The modified code works perfectly.

const mongoose = require('mongoose');
const config = require('../config');

const database = config.database;
const uri = `mongodb+srv://${database.user}:${database.password}@${database.uri}`;

mongoose
  .connect(uri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: true,
    useCreateIndex: true,
    promiseLibrary: global.Promise
  });

let parentSchema = new mongoose.Schema({
  name: String
});

let childSchema = new mongoose.Schema({
  parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "Parent"
  },
  name: String,
  requiredField: {
    type: String,
    required: true
  }
});

let Parent = mongoose.model("Parent", parentSchema);
let Child = mongoose.model("Child", childSchema);

let session;
mongoose.startSession()
  .then(_session => {
    session = _session;
    session.startTransaction();
    let aParent = new Parent({ name: 'Parent 1'});
    console.log('created parent');

    return aParent.save({ session: session })

  })
  .then(_aParent => {
    console.log('Parent saved');
    let aChild = new Child({ parent: _aParent._id, name: 'Child 1' });
    console.log('Child created');
    return aChild.save({ session: session });
  })
  .then(_aChild => {
    console.log('child saved', session.transaction.state);
    return session.commitTransaction();
  })
  .catch(_err => {
    console.log(_err, session.transaction.state);
    return session.abortTransaction();
  })
  .finally(() => {
    console.log(session.transaction.state)
    session.endSession();
    mongoose.disconnect();
  })

Upvotes: 1

Related Questions