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

Transaction breaking all following queries when using "findOneAndUpdate". #15197

Open
2 tasks done
SwyfterThanU opened this issue Jan 22, 2025 · 1 comment
Open
2 tasks done
Labels
can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity.

Comments

@SwyfterThanU
Copy link

SwyfterThanU commented Jan 22, 2025

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.9.5

Node.js version

v22.11.0

MongoDB server version

8.0.4

Typescript version (if applicable)

^5.7.2

Description

Hi,

I have made myself a transaction function for handling multiple queries at once with my backend API. My issue is, after the function runs once, it breaks all following queries made with mongoose. I am not sure why and all I can think is that it is an issue out of my hands.

I have found that if I change the second query within the transaction function from "findOneAndUpdate" to "updateOne", everything works as it should. I would rather use findOneAndUpdate, though.

This is the error I receive when it errors:

MongoUnexpectedServerResponseError: BSON element "cursor" is missing
at CursorResponse.get (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:48:19)
at get cursor (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:165:21)                                                                                                                             ... 6 lines matching cause stack trace ...                                                                                                                                                                      
at async FindCursor.next (/api/node_modules/mongodb/lib/cursor/abstract_cursor.js:323:17)                                                                                                                       
 at async Collection.findOne (/api/node_modules/mongodb/lib/collection.js:277:21) {                                                                                                                              
[Symbol(errorLabels)]: Set(0) {},                                                                                                                                                                                 
[cause]: BSONError: BSON element "cursor" is missing                                                                                                                                                              
at CursorResponse.get (/api/node_modules/mongodb/lib/cmap/wire_protocol/on_demand/document.js:171:23)                                                                                                         
at CursorResponse.get (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:45:26)                                                                                                                   
at get cursor (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:165:21)                                                                                                                          
at get batch (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:201:29)                                                                                                                           
at get batchSize (/api/node_modules/mongodb/lib/cmap/wire_protocol/responses.js:211:21)                                                                                                                       
at FindCursor._initialize (/api/node_modules/mongodb/lib/cursor/find_cursor.js:62:37)                                                                                                                         
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)                                                                                                                                
at async FindCursor.cursorInit (/api/node_modules/mongodb/lib/cursor/abstract_cursor.js:607:27)
at async FindCursor.fetchBatch (/api/node_modules/mongodb/lib/cursor/abstract_cursor.js:641:13)                                                                                                               
at async FindCursor.next (/api/node_modules/mongodb/lib/cursor/abstract_cursor.js:323:17)                                                                                                                     
 }   

Thanks for any help.

Steps to Reproduce

The function:

public static executeTransaction = async (requests: ((...args: any[]) => Promise<any>)[]): Promise<any[]> => {
	let session: ClientSession | null = null;
	const results: any[] = [];

	try {
		session = await this.connectionPool.startSession();

		await session.withTransaction(async () => {
			for (const request of requests) {
				const result: any = await request(session);
				results.push(result);
			}
		});

		return results;
	} catch (error: any) {
		throw ErrorHandler.error("The transaction has failed.", "TRANSACTION_FAILED", 500, { error: error.message ?? error });
	} finally {
		if (session) {
			await session.endSession();
		}
	}
};

Here is how I am using the function (within the API endpoint):

await DatabaseHandler.executeTransaction([
	async (session: ClientSession) => {
		await ImageModel.createImages(Object.values(uploadedFiles).map((file: Partial<Image>) => file), session);
	},
	async (session: ClientSession) => {
		const data: Partial<Server> = Object.values(uploadedFiles).reduce((acc: any, file: any) => {
			acc[`${file.type}_id`] = serverData[file.type]?.id ?? file.id;
			return acc;
		}, {});
		await ServerModel.updateServer(serverData.vanity_url, data, session);
	}
]);

How to reproduce:

  • Send the first API request (everything works as normal)
  • When the second request (same request) is sent again, the middleware which fires before it gets to the 'executeTransaction' function and uses 'findOne' errors with the BSON "cursor" missing error.

Expected Behavior

I expect the same result as the first API request.

"message": "Successfully ..."

@vkarpov15 vkarpov15 added the needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue label Jan 30, 2025
@vkarpov15 vkarpov15 added this to the 8.9.7 milestone Jan 30, 2025
@vkarpov15
Copy link
Collaborator

vkarpov15 commented Feb 3, 2025

I'm unable to repro, the following script executes without errors. Can you modify the following script to demonstrate the issue you're seeing?

const mongoose = require('mongoose');

const uri = my connection string here;

mongoose.connect(uri);

const imageSchema = new mongoose.Schema({ name: String });
const serverSchema = new mongoose.Schema({ name: String, type: String });

const ImageModel = mongoose.model('Image', imageSchema);
const ServerModel = mongoose.model('Server', serverSchema);

async function executeTransaction(requests) {
  const session = await mongoose.startSession();
  const results = [];

  await session.withTransaction(async () => {
    for (const request of requests) {
      results.push(await request(session));
    }
  });

  await session.endSession();
  return results;
}

async function main() {
  await ImageModel.deleteMany({});
  await ServerModel.deleteMany({});
  await ImageModel.create({ name: 'Initial Image' });
  await ServerModel.create({ name: 'Initial Server', type: 'A' });

  console.log('First transaction:');
  await executeTransaction([
    async (session) => {
      return ImageModel.create([{ name: 'New Image' }], { session });
    },
    async (session) => {
      return ServerModel.findOneAndUpdate(
        { name: 'Initial Server' },
        { type: 'B' },
        { session, new: true }
      );
    }
  ]);

  console.log('Second transaction:');
  await executeTransaction([
    async (session) => {
      return ImageModel.create([{ name: 'Another Image' }], { session });
    },
    async (session) => {
      return ServerModel.findOneAndUpdate(
        { name: 'Initial Server' },
        { type: 'C' },
        { session, new: true }
      );
    }
  ]);
  console.log('Done');
}

main();

Output:

$ node gh-15197.js 
First transaction:
Second transaction:
Done
^C

@vkarpov15 vkarpov15 added can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. and removed needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue labels Feb 3, 2025
@vkarpov15 vkarpov15 removed this from the 8.9.7 milestone Feb 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity.
Projects
None yet
Development

No branches or pull requests

2 participants