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

[BUG.v5.2.1] ClientSession cannot be serialized to BSON - Sessions #6663

Closed
GregoryNEUT opened this issue Jul 4, 2018 · 6 comments
Closed
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Milestone

Comments

@GregoryNEUT
Copy link

GregoryNEUT commented Jul 4, 2018

Hello; trying to use simple transaction case as described in the documentation; I'm getting an error

ClientSession cannot be serialized to BSON.
    at ClientSession.toBSON (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/sessions.js:225:11)
    at serializeInto (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:895:23)
    at serializeObject (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:347:18)
    at serializeInto (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:937:17)
    at BSON.serialize (/eliot-local-git/node_modules/bson/lib/bson/bson.js:63:28)
    at Query.toBin (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/commands.js:144:25)
    at serializeCommands (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/pool.js:1044:43)
    at Pool.write (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/pool.js:1260:3)
    at Cursor._find (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:326:22)
    at nextFunction (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:673:10)
    at Cursor.next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:824:3)
    at Cursor._next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/cursor.js:211:36)
    at nextObject (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/operations/cursor_ops.js:186:10)
    at next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/operations/cursor_ops.js:165:3)
    at executeOperation (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/utils.js:420:24)
    at Cursor.next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/cursor.js:253:10)

My code look like :

        const session = await model.startSession();

        const oneCompany = await model.findOne({}, {
          projection: '_id',
          session,
        });

        const oneLanguage = await classes.Language.schema.getModel().findOneAndUpdate({
          _id: 'aaaaa0000000000000000000',
        }, {
          'name.description': 'lelz',
        }, {
          new: true,
          projection: '_id',
          session,
        });

        await session.commitTransaction();

        session.endSession();

Versions I use :

node.js v9.11.1
mongoose@5.2.1
mongodb@3.1.0 (mongo native node driver)
mongodb-core@3.1.0 (mongo native node driver)
mongodb v4.0.0 (database)

Thanks

@lineus
Copy link
Collaborator

lineus commented Jul 4, 2018

I haven't used sessions at all yet, so I'm really not sure if this should work or not. I was able to create a complete repro script that demonstrates the outcome based on some assumptions about @GregoryNEUT's code sample.

6663.js

#!/usr/bin/env node --no-deprecation
'use strict';

const { ATLASSRV } = require('/Users/lineus/.env');
const mongoose = require('mongoose');
mongoose.connect(ATLASSRV.replace(/test/, 'gh-6663'));
const conn = mongoose.connection;
const Schema = mongoose.Schema;

const schemaA = new Schema({
  name: String
});

const schemaB = new Schema({
  name: String
});

const A = mongoose.model('A', schemaA);
const B = mongoose.model('B', schemaB);

const a = new A({ name: 'Andrew' });
const b = new B({ name: 'Billy' });

async function run() {
  await conn.dropDatabase();
  await a.save();
  await b.save();

  const session = await A.startSession();
  const foundA = await A.findOne({}, { session });
  await B.findOneAndUpdate({}, { name: foundA.name }, { session });
  await session.commitTransaction();
  session.endSession();

  let newB = await B.findOne({});
  console.log(`hello from ${newB.name}`);
  return conn.close();
}

run().catch(console.error);

Output:

issues: ./6663.js
Error: ClientSession cannot be serialized to BSON.
    at ClientSession.toBSON (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/sessions.js:225:11)
    at serializeInto (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:895:23)
    at serializeObject (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:347:18)
    at serializeInto (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:937:17)
    at BSON.serialize (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/bson.js:63:28)
    at Query.toBin (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/commands.js:144:25)
    at serializeCommands (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/pool.js:1044:43)
    at Pool.write (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/pool.js:1260:3)
    at Cursor._find (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:326:22)
    at nextFunction (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:673:10)
    at Cursor.next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:824:3)
    at Cursor._next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/cursor.js:211:36)
    at nextObject (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/operations/cursor_ops.js:186:10)
    at next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/operations/cursor_ops.js:165:3)
    at executeOperation (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/utils.js:420:24)
    at Cursor.next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/cursor.js:253:10)
^C
issues:

something about creating the session on the first model, and passing it to a query on the second model feels wrong

@vkarpov15
Copy link
Collaborator

This is a common gotcha that we should add docs for, but your issue is that the 2nd arg to Model.findOne() is always a projection. You need to do await A.findOne({}, null, { session });

@vkarpov15 vkarpov15 added this to the 5.2.2 milestone Jul 4, 2018
@vkarpov15 vkarpov15 added the docs This issue is due to a mistake or omission in the mongoosejs.com documentation label Jul 4, 2018
@lineus
Copy link
Collaborator

lineus commented Jul 4, 2018

@vkarpov15 LOL 🤕 I can't believe I missed that.

I had to make a couple of adjustments to my example that were wrong beyond the missing projection. In case it might help someone in the future here is the working code:

6663.js

#!/usr/bin/env node --no-deprecation
'use strict';

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017,localhost:27018,localhost:27019/gh-6663?replicaSet=rs');
const conn = mongoose.connection;
const Schema = mongoose.Schema;

const schemaA = new Schema({
  name: String
});

const schemaB = new Schema({
  name: String
});

const A = mongoose.model('A', schemaA);
const B = mongoose.model('B', schemaB);

const a = new A({ name: 'Andrew' });
const b = new B({ name: 'Billy' });

async function run() {
  await conn.dropDatabase();
  await a.save();
  await b.save();

  const session = await A.startSession();
  await session.startTransaction();
  const foundA = await A.findOne({}, {}, { session });
  await B.findOneAndUpdate({}, { name: foundA.name }, { session });

  await session.commitTransaction();
  session.endSession();

  let newB = await B.findOne({});
  console.log(`hello from ${newB.name}`);
  return conn.close();
}

run().catch(console.error);

Output:

issues: ./6663.js
hello from Andrew
issues:

@vkarpov15 vkarpov15 modified the milestones: 5.2.2, 5.2.4 Jul 8, 2018
@vylan
Copy link

vylan commented Jul 9, 2018

It is a real bug in debug environment.

mongoose.set('debug', true);

@vkarpov15
Copy link
Collaborator

@vylan that's a very good point. Opened up #6712 to track and will fix asap

@zinzinday
Copy link

zinzinday commented Jul 13, 2018

const {MongoClient} = require('mongodb');
const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/test-mongodb';
MongoClient.connect(uri, {useNewUrlParser: true, replicaSet: 'rs'}).then(async (client) => {
    // console.log(client);
    const db = client.db();
    await db.dropDatabase();

    await db.collection('Account').insertMany([
        {name: 'A', balance: 5},
        {name: 'B', balance: 10}
    ]);

    await transfer('A', 'B', 4); // Success
    try {
        await transfer('A', 'B', 6);
    } catch (error) {
        console.log(error);
    }
    // The actual transfer logic
    async function transfer(from, to, amount) {
        const session = client.startSession();
        session.startTransaction();
        try {
            const opts = {session, returnOriginal: false};
            const B = await db.collection('Account').findOneAndUpdate({name: to}, {$inc: {balance: amount}}, opts).then(res => res.value);
            const A = await db.collection('Account').findOneAndUpdate({name: from}, {$inc: {balance: -amount}}, opts).then(res => res.value);
            if (A.balance < 0) {
                // If A would have negative balance, fail and abort the transaction
                // `session.abortTransaction()` will undo the above `findOneAndUpdate()`
                throw new Error('Insufficient funds: ' + (A.balance + amount));
            }
            await session.commitTransaction();
            session.endSession();
            return {from: A, to: B};
        } catch (error) {
            // If an error occurred, abort the whole transaction and
            // undo any changes that might have happened
            await session.abortTransaction();
            session.endSession();
            throw error; // Rethrow so calling function sees error
        }
    }
}).catch(console.log);

@vkarpov15 your example worked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Projects
None yet
Development

No branches or pull requests

5 participants