Javascript 多次同时调用'cursor.next()`会使驱动程序崩溃 动机:

Javascript 多次同时调用'cursor.next()`会使驱动程序崩溃 动机:,javascript,node.js,mongodb,Javascript,Node.js,Mongodb,我有一个体系结构,涉及许多“使用”文档的工作人员,如下所示: worker.on('readyForAnotherDoc', () => worker.consume( await cursor.next() )); 这是一种伪代码——我正在实际代码中检查cursor.hasNext()。由于有数百名工作人员,所以cursor.next()可能会同时被突然爆发的200个请求击中 我正试图解决mongodb node.js驱动程序中的一个错误/怪癖,如果我对cursor.next()的请求

我有一个体系结构,涉及许多“使用”文档的工作人员,如下所示:

worker.on('readyForAnotherDoc', () => worker.consume( await cursor.next() ));
这是一种伪代码——我正在实际代码中检查cursor.hasNext()。由于有数百名工作人员,所以cursor.next()可能会同时被突然爆发的200个请求击中

我正试图解决mongodb node.js驱动程序中的一个错误/怪癖,如果我对
cursor.next()的请求过多,就会导致错误。下一步()
由于巧合而彼此“重叠”

背景: MongoDB Node.js驱动程序似乎无法正确处理
游标的情况。接下来,
会向其抛出大量请求。尝试运行以下代码:

(async function() {

  // create a collection for testing:
  let db = await require('mongodb').MongoClient.connect('mongodb://localhost:27017/tester-db-478364');
  await db.collection("test").drop();
  for(let i = 0; i < 1000; i++) {
    await db.collection("test").insertOne({num:i, foo:'bar'});
  }

  let cursor = await db.collection("test").find({});

  async function go() {
    let doc = await cursor.next();
    console.log(doc.num);
  }

  // start 100 simulataneous requests to `cursor.next()`
  for(let i = 0; i < 1000; i++) {
    go();
  }

})();
不知道那里发生了什么

试图解决这个问题: 但假设我们在这一点上刚刚解决了一个问题。假设我们稍微更改一下
go
函数:

let cursorBusy = false;
async function go() {
  if(cursorBusy) await waitForCursor();
  cursorBusy = true;
  let doc = await cursor.next();
  cursorBusy = false;
  console.log(doc.num);
}
function waitForCursor() {
  return new Promise(resolve => {
    let si = setInterval(() => {
      if(!cursorBusy) {
        resolve();
        clearInterval(si);
      }
    }, 50);
  });
}
这会导致一个新的错误,该错误似乎出现在
console.log(doc.num)
s的各处:

...
359
415
466
(node:16259) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 12): MongoError: clientcursor already in use? driver problem?
427
433
459
...
我认为这并不能避免这个bug,因为setInterval中有一种“竞争条件”的东西。有趣的是,这是一个不同的错误消息

问题:是否有办法测试光标当前是否“忙”?在这个bug被修复之前(如果它是一个bug的话),这里还有其他可能的解决方法吗

具有一些类似(但绝对不同)的行为,并且似乎是在第三方node.js libs中出现的。

编辑:虽然这个答案确实有效,但它是解决这个问题的更好的方法。把这个答案留给繁荣编辑2:其他答案错误:(


好的,所以我整理了
waitForCursor
函数,这样它就没有竞争条件的东西,因此看起来工作得很好:

let cursorBusy = false;
async function go() {
  await waitForCursorLock();
  let doc = await cursor.next();
  cursorBusy = false;
  console.log(doc.num);
}
function waitForCursorLock() {
  return new Promise(resolve => {
    let si = setInterval(() => {
      if(!cursorBusy) {
        cursorBusy = true;
        resolve();
        clearInterval(si);
      }
    }, 50);
  });
}

这太恶心了,所以我可能不会接受这个答案。如果你能想出一个更好的答案,请发布它!

你的列表中有一些错误。所以真的只是稍微整理一下:

const MongoClient = require('mongodb').MongoClient;

(async function() {

  let db;

  try {
    db = await MongoClient.connect('mongodb://localhost/test');

    await db.collection('test').drop();

    await db.collection('test').insertMany(
      Array(1000).fill(1).map((e,num) => ({ num, foo: 'bar' }))
    );

    // This is not async. It returns immediately
    let cursor = db.collection('test').find();

    async function go() {
      let doc = await cursor.next();   // This awaits before continuing. Not concurrent.
      console.log(doc.num);
    }

    for ( let i = 0; i < 100; i++ ) {
      go();  // Note that these "await" internally
    }

  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();
按顺序正确打印。缩短,但实际按预期转到
99

0
1
2
3
4
5
6
7
8
9
10
(etc..)
解释主要在代码的注释中,您似乎缺少了哪些内容是
async
,哪些内容是而不是

因此,返回as-from不是一个
异步
方法,并立即返回。这是因为它只是一个操作句柄,此时不做任何事情。MongoDB驱动程序(所有驱动程序)在发出“获取”数据的实际请求之前不会联系服务器或在该端建立游标

当您调用时,是指与服务器进行实际通信并返回“批处理”结果。“批处理”实际上只影响后续调用是否实际返回服务器以检索数据,因为“批处理”可能已经有“更多”结果,可以在另一个“批处理”请求之前“清空”无论如何,无论是否存在外部I/O,对的每次调用都被认为是
异步的

通常,您通过包装每次迭代来调用(这也是
异步的
),因为在没有更多结果的情况下调用是一个错误。这通常也是“循环控制”的一种方式,如下所示:

(async function() {

  let db;

  try {
    db = await MongoClient.connect('mongodb://localhost/test');

    await db.collection('test').drop();

    await db.collection('test').insertMany(
      Array(1000).fill(1).map((e,num) => ({ num, foo: 'bar' }))
    );

    let cursor = db.collection('test').find();

    async function go() {
      let doc = await cursor.next();
      console.log(doc.num);
    }

    //for ( let i = 0; i < 100; i++ ) {
    while( await cursor.hasNext() ) {  // Check the cursor still has results
      go();
    }

  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();
(异步函数(){
让db;
试一试{
db=等待MongoClient.connect('mongodb://localhost/test');
等待db.collection('test').drop();
等待db.collection('test').insertMany(
数组(1000).fill(1).map((e,num)=>({num,foo:'bar'}))
);
让cursor=db.collection('test').find();
异步函数go(){
让doc=wait cursor.next();
console.log(doc.num);
}
//for(设i=0;i<100;i++){
while(wait cursor.hasNext()){//检查光标是否仍有结果
go();
}
}捕获(e){
控制台错误(e);
}最后{
db.close();
}
})();
然后循环直到光标结束

关于“并发性”需要注意的事项这也不是您在这里所期望的。如果您确实希望并行发出多个请求,那么您仍然需要等待当前的游标提取。如果您不这样做,那么您要求服务器返回所有请求的相同的数据,而不是“迭代”中的顺序数据光标

您似乎混淆了这一点的是一些实用程序函数(特别是mongoose
asyncEach()
在实现并行“获取”时所做的工作。代码(从内存中)基本上是人为地插入一个
setTimeout()
,以等待“下一个勾号”,这基本上意味着每个
。下一个()
必须仍然开火

如前所述,它是“人工的”,因为批处理实际上是
.map()
(在底层代码的某个地方)到一个更大的批处理中

但正如所演示的。基本的预期用途确实如预期的那样工作,因为实际上“等待”了每个
.next()
。就像您应该做的那样。

编辑:这不起作用。(请参阅注释) 受@NeilLunn解释的启发,我们只需在创建光标后添加一个
wait cursor.hasNext();
,即可修复原始代码:

(async function() {

  // create a collection for testing:
  let db = await require('mongodb').MongoClient.connect('mongodb://localhost:27017/tester-db-478364');

  await db.collection("test").drop();

  await db.collection('test').insertMany(
    Array(1000).fill(1).map((e,num) => ({ num, foo: 'bar' }))
  );

  let cursor = db.collection("test").find({});
  await cursor.hasNext(); // <-- add this line to "pre-instantiate" cursor

  async function go() {
    let doc = await cursor.next();
    console.log(doc.num);
  }

  // start 100 simulataneous requests to `cursor.next()`
  for(let i = 0; i < 100; i++) {
    go();
  }

})();
(异步函数(){
//创建用于测试的集合:
让db=await require('mongodb')。MongoClient.connect('mongodb://localhost:27017/tester-db-478364’;
等待db.collection(“test”).drop();
等待db.collection('test').insertMany(
数组(1000).fill(1).map((e,num)=>({num,foo:'bar'}))
);
让cursor=db.collection(“test”).find({});

wait cursor.hasNext();//嗯。无法复制。第一个列表应该完全按照预期工作,因为您实际上在函数中执行了
wait
。next()
“内部”。因此它没有任何“并发性”。您实际上不需要
wait
0
1
2
3
4
5
6
7
8
9
10
(etc..)
(async function() {

  let db;

  try {
    db = await MongoClient.connect('mongodb://localhost/test');

    await db.collection('test').drop();

    await db.collection('test').insertMany(
      Array(1000).fill(1).map((e,num) => ({ num, foo: 'bar' }))
    );

    let cursor = db.collection('test').find();

    async function go() {
      let doc = await cursor.next();
      console.log(doc.num);
    }

    //for ( let i = 0; i < 100; i++ ) {
    while( await cursor.hasNext() ) {  // Check the cursor still has results
      go();
    }

  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();
(async function() {

  // create a collection for testing:
  let db = await require('mongodb').MongoClient.connect('mongodb://localhost:27017/tester-db-478364');

  await db.collection("test").drop();

  await db.collection('test').insertMany(
    Array(1000).fill(1).map((e,num) => ({ num, foo: 'bar' }))
  );

  let cursor = db.collection("test").find({});
  await cursor.hasNext(); // <-- add this line to "pre-instantiate" cursor

  async function go() {
    let doc = await cursor.next();
    console.log(doc.num);
  }

  // start 100 simulataneous requests to `cursor.next()`
  for(let i = 0; i < 100; i++) {
    go();
  }

})();