简体   繁体   中英

Possible race condition with cursor when using Promise.all

In the project that I am working on, built using nodejs & mongo, there is a function that takes in a query and returns set of data based on limit & offset provided to it. Along with this data the function returns a total count stating all the matched objects present in the database. Below is the function:

// options carry the limit & offset values
// mongoQuery carries a mongo matching query
function findMany(query, options, collectionId) {
    const cursor = getCursorForCollection(collectionId).find(query, options);
    return Promise.all([findManyQuery(cursor), countMany(cursor)]);
}

Now the problem with this is sometime when I give a large limit size I get an error saying:

Uncaught exception:  TypeError: Cannot read property '_killCursor' of undefined

At first I thought I might have to increase the pool size in order to fix this issue but after digging around a little bit more I was able to find out that the above code is resulting in a race condition. When I changed the code to:

function findMany(query, options, collectionId) {
  const cursor = getCursorForCollection(collectionId).find(query, options);
  return findManyQuery(cursor).then((dataSet) => {
    return countMany(cursor).then((count)=> {
      return Promise.resolve([dataSet, count]);
    });
  );
}

Everything started working perfectly fine. Now, from what I understand with regard to Promise.all was that it takes an array of promises and resolves them one after the other. If the promises are executed one after the other how can the Promise.all code result in race condition and the chaining of the promises don't result in that.

I am not able to wrap my head around it. Why is this happening?

Since I have very little information to work with, I made an assumption of what you want to achieve and came up with the following using Promise.all() just to demonstrate how you should use Promise.all (which will resolve the array of promises passed to it in no particular order. For this reason, there must be no dependency in any Promise on the order of execution of the Promises. Read more about it here ).

// A simple function to sumulate findManyQuery for demo purposes

function findManyQuery(cursors) {
    return new Promise((resolve, reject) => {
        // Do your checks and run your code (for example)
        if (cursors) {
            resolve({ dataset: cursors });
        } else {
            reject({ error: 'No cursor in findManyQuery function' });
        }

    });
}


// A simple function to sumulate countMany for demo purposes

function countMany(cursors) {
    return new Promise((resolve, reject) => {
        // Do your checks and run your code (for example)
        if (cursors) {
            resolve({ count: cursors.length });
        } else {
            reject({ error: 'No cursor in countMany' });
        }
    });
}

// A simple function to sumulate getCursorForCollection for demo purposes

function getCursorForCollection(collectionId) {
   /* 
        Simulating the returned cursor using an array of objects 
        and the Array filter function
    */

    return [{
        id: 1,
        language: 'Javascript',
        collectionId: 99
    }, {
        id: 2,
        language: 'Dart',
        collectionId: 100
    },
    {
        id: 3,
        language: 'Go',
        collectionId: 100
    }, {
        id: 4,
        language: 'Swift',
        collectionId: 99
    }, {
        id: 5,
        language: 'Kotlin',
        collectionId: 101
    },
    {
        id: 6,
        language: 'Python',
        collectionId: 100
    }].filter((row) =>  row.collectionId === collectionId)
}

function findMany(query = { id: 1 }, options = [], collectionId = 0) {

    /*  
         First I create a function to simulate the assumed use of 
         query and options parameters just for demo purposes
     */

    const filterFunction = function (collectionDocument) {
        return collectionDocument.collectionId === query.id && options.indexOf(collectionDocument.language) !== -1;
    };

    /*  
         Since I am working with arrays, I replaced find function 
         with filter function just for demo purposes
     */

    const cursors = getCursorForCollection(collectionId).filter(filterFunction);

    /* 
       Using Promise.all([]). NOTE: You should pass the result of the
       findManyQuery() to countMany() if you want to get the total 
       count of the resulting dataset
    */

    return Promise.all([findManyQuery(cursors), countMany(cursors)]);
}


// Consuming the findMany function with test parameters

const query = { id: 100 };
const collectionId = 100;
const options = ['Javascript', 'Python', 'Go'];

findMany(query, options, collectionId).then(result => {
    console.log(result); // Result would be [ { dataset: [ [Object], [Object] ] }, { count: 2 } ]
}).catch((error) => {
    console.log(error);
});

There are ways to write this function in a "pure" way for scalability and testing.

So here's your concern: In the project that I am working on, built using nodejs & mongo, there is a function that takes in a query and returns set of data based on limit & offset provided to it. Along with this data the function returns a total count stating all the matched objects present in the database.

Note : You'll need to take care of edge case.

const Model = require('path/to/model');

function findManyUsingPromise(model, query = {}, offset = 0, limit = 10) {

    return new Promise((resolve, reject) => {
        model.find(query, (error, data) => {
            if(error) {
                reject(error);
            }
            resolve({
                data,
                total: data.length || 0
            });
        }).skip(offset).limit(limit);
    });

}

// Call function

findManyUsingPromise(Model, {}, 0, 40).then((result) => {
    // Do something with result  {data: [object array], total: value }
}).catch((err) => {
    // Do something with the error
}); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM