Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Promise.all inside transactions errors with "Cannot pin multiple connections to the same session" #14603

Closed
2 tasks done
fiws opened this issue May 17, 2024 · 8 comments
Closed
2 tasks done
Labels
underlying library issue This issue is a bug with an underlying library, like the MongoDB driver or mongodb-core

Comments

@fiws
Copy link
Contributor

fiws commented May 17, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

8.4.0

Node.js version

22.0.0

MongoDB server version

7.3.2

Typescript version (if applicable)

No response

Description

Using running mongo queries/commands in Promise.all wrapped in a transaction throws the following error:

TypeError: Cannot pin multiple connections to the same session

I tested this using an atlas serverless database. Is it not allowed to run queries in parallel when using a transaction?

Steps to Reproduce

import mongoose from 'mongoose';
mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost/transaction-test');

const userSchema = new mongoose.Schema({
  name: String,
});

const User = mongoose.model('User', userSchema);

test('user transaction test', async () => {
  await Instance.db.transaction(async session => {
    return Promise.all([
      User.findOne({}, null, { session }),
      User.findOne({}, null, { session }),
    ]);
  });
});

Expected Behavior

No error.

@fiws
Copy link
Contributor Author

fiws commented May 17, 2024

I don't know if this is related but in a more complex application we did also get worrisome side-effects from these kind of transactions, where a findById or findOne looking up a document that has been deleted in the previous transactions suddenly returns something like this:

{
  ok: 1,
  '$clusterTime': {
    clusterTime: new Timestamp({ t: 1715964070, i: 18 }),
    signature: {
      hash: Binary.createFromBase64('QvEPV74zpqEMIRD5lLDYPkfxsV8=', 0),
      keyId: new Long('7330005257113567234')
    }
  },
  operationTime: new Timestamp({ t: 1715964070, i: 18 })
}

This looks like some mongo internals that should never be returned by findById.

Might be another bug tho, and I'm still trying to get this into a small reproducible snippet.

@IslandRhythms
Copy link
Collaborator

I noticed in your repo script you're using a local connection. Transactions can only be used on replica sets. Just want to confirm that you tested on a replica set before creating the issue.

@IslandRhythms IslandRhythms added the needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity label May 21, 2024
@fiws
Copy link
Contributor Author

fiws commented May 21, 2024 via email

@vkarpov15
Copy link
Collaborator

It seems like this issue only occurs with Atlas serverless instances, the following script executes fine against a local mongodb replica set:

const mongoose = require('mongoose');

mongoose.connect('mongodb://127.0.0.1:27017,127.0.0.1:27018/mongoose_test');

const userSchema = new mongoose.Schema({
  name: String,
});


void async function main() {

  const User = mongoose.model('User', userSchema);
  await User.create({ name: 'test' });

  await mongoose.connection.transaction(async session => {
    const docs = await Promise.all([
      User.findOne({}, null, { session }),
      User.findOne({}, null, { session }),
    ]);
    console.log(docs);
  });

  console.log('Done');
}();

Output:

$ node gh-14603.js 
[
  {
    _id: new ObjectId('6650a6d44061785454c11001'),
    name: 'test',
    __v: 0
  },
  {
    _id: new ObjectId('6650a6d44061785454c11001'),
    name: 'test',
    __v: 0
  }
]
Done

@vkarpov15
Copy link
Collaborator

I opened issue in MongoDB Jira here: https://jira.mongodb.org/browse/NODE-6190

@vkarpov15
Copy link
Collaborator

As a temporary workaround, you can use the following:

  await mongoose.connection.transaction(async session => {
    const docs = await Promise.all([
      User.findOne({}, null, { session }),
      sleep(0).then(() => User.findOne({}, null, { session })),
    ]);
    console.log(docs);
  });

async function sleep(ms) {
  await new Promise(resolve => setTimeout(resolve, ms));
}

@vkarpov15 vkarpov15 added underlying library issue This issue is a bug with an underlying library, like the MongoDB driver or mongodb-core and removed needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity labels May 24, 2024
@vkarpov15
Copy link
Collaborator

It looks like this is expected behavior. From the JIRA ticket:

image

Here's the docs link. I'm unfortunately going to have to close this issue: Mongoose can't support parallel requests with the same transaction if the MongoDB server doesn't support it.

@fiws
Copy link
Contributor Author

fiws commented Jun 3, 2024

Thanks for researching this and creating the upstream issue.

Maybe some documentation (similar to the updated mongodb doc) on mongoose for this would be nice. The big problem is that this is undefined behaviour. It does not throw the error every time. We have Promise.all calls that seemingly work (in production). We noticed super weird behaviour recently though, which is the whole reason why i created this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
underlying library issue This issue is a bug with an underlying library, like the MongoDB driver or mongodb-core
Projects
None yet
Development

No branches or pull requests

3 participants