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();
}
})();
然后循环直到光标结束
关于“并发性”需要注意的事项这也不是您在这里所期望的。如果您确实希望并行发出多个请求,那么您仍然需要等待当前的游标提取。如果您不这样做,那么您要求服务器返回所有请求的相同的数据,而不是“迭代”中的顺序数据光标
您似乎混淆了这一点的是一些实用程序函数(特别是mongooseasyncEach()
在实现并行“获取”时所做的工作。代码(从内存中)基本上是人为地插入一个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();
}
})();