Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mongoose model has no access to connections in-progress transactions

In my current express application I want to use the new ability of mongodb multi-doc transactions.

First of all it is important to point out how I connect and handle the models

My app.js (server) firstly connects to the db by using db.connect().

I require all models in my db.index file. Since the models will be initiated with the same mongoose reference, I assume that future requires of the models in different routes point to the connected and same connection. Please correct me if I'm wrong with any of these assumptions.

I save the connection reference inside the state object and returning it when needed for my transaction later

./db/index.ts

const fs       = require('fs');
const path     = require('path');
const mongoose = require('mongoose');

const state = {
 connection = null,
}

// require all models
const modelFiles = fs.readdirSync(path.join(__dirname, 'models'));
modelFiles
.filter(fn => fn.endsWith('.js') && fn !== 'index.js')
.forEach(fn => require(path.join(__dirname, 'models', fn)));

const connect = async () => {
  state.connection = await mongoose.connect(.....);
  return;
}

const get = () => state.connection; 

module.exports = {
 connect,
 get,
}

my model files are containing my required schemas

./db/models/example.model.ts

const mongoose   = require('mongoose');
const Schema     = mongoose.Schema;
const ExampleSchema = new Schema({...);

const ExampleModel = mongoose.model('Example', ExampleSchema);
module.exports  = ExampleModel;

Now the route where I try to do a basic transaction. F

./routes/item.route.ts

const ExampleModel = require('../db/models/example.model');

router.post('/changeQty', async (req,res,next) => {

  const connection = db.get().connection;
  const session = await connection.startSession(); // works fine

  // start a transaction
  session.startTransaction(); // also fine

  const {someData} = req.body.data;

  try{

     // jsut looping that data and preparing the promises
     let promiseArr = [];
     someData.forEach(data => {
        // !!! THIS TRHOWS ERROR !!!
        let p = ExampleModel.findOneAndUpdate(
          {_id : data.id},
          {$incr : {qty : data.qty}},
          {new : true, runValidators : true}
        ).session(session).exec();
        promiseArr.push(p);
     })        

     // running the promises parallel
     await Promise.all(promiseArr);

     await session.commitTransaction();

    return res.status(..)....;

  }catch(err){
     await session.abortTransaction();
    // MongoError : Given transaction number 1 does not match any in-progress transactions.
    return res.status(500).json({err : err}); 
  }finally{
    session.endSession();
  }

})

But I always get the following error, which probably has to do sth with the connection reference of my models. I assume, that they don't have access to the connection which started the session, so they are not aware of the session.

MongoError: Given transaction number 1 does not match any in-progress transactions.

Maybe I somehow need to initiate the models inside db.connect with the direct connection reference ?

There is a big mistake somewhere and I hope you can lead me to the correct path. I appreciate Any help, Thanks in advance

like image 691
sami_analyst Avatar asked Oct 17 '22 06:10

sami_analyst


1 Answers

This is because you're doing operations in parallel:

So you've got a bunch of race conditions. Just use async/await

and make your life easier.

let p = await ExampleModel.findOneAndUpdate(
              {_id : data.id},
              {$incr : {qty : data.qty}},
              {new : true, runValidators : true}
            ).session(session).exec();

Reference : https://github.com/Automattic/mongoose/issues/7311

If that does not work try to execute promises one by one rather than promise.all().

like image 167
Thamaraiselvam Avatar answered Oct 21 '22 01:10

Thamaraiselvam