MongoDB Node.js原生驱动程序默默地吞噬了`bulkWrite`异常

3
下面的脚本在mongo bulkWrite操作语法中存在一个bug: $setOnInsert: { count:0 },是不必要的,因此mongo会抛出异常:"不能同时更新'count'和'count'"。
问题是,node.js驱动程序似乎没有捕捉到它。这个脚本将"Success!"记录到控制台。
(async () => {

  let db = await require('mongodb').MongoClient.connect('mongodb://localhost:27017/myNewDb');

  let mongoOps = [{
    updateOne: {
      filter: { foo: "bar" },
      update: {
        $setOnInsert: { count:0 },
        $inc: { count:1 },
      },
      upsert: true,
    }
  }];

  try {
    await db.collection("myNewCollection").bulkWrite(mongoOps);
    console.log("Success!");
  } catch(e) {
    console.log("Failed:");
    console.log(e);
  }

})();

使用db.setProfileLevel(2)检查db.system.profile.find({}),我们可以看到异常:

{
    "op" : "update",
    "ns" : "myNewDb.myNewCollection",
    "query" : {
        "foo" : "bar"
    },
    "updateobj" : {
        "$setOnInsert" : {
            "count" : 0
        },
        "$inc" : {
            "count" : 1
        }
    },
    "keyUpdates" : 0,
    "writeConflicts" : 0,
    "numYield" : 0,
    "locks" : {
        "Global" : {
            "acquireCount" : {
                "r" : NumberLong(1),
                "w" : NumberLong(1)
            }
        },
        "Database" : {
            "acquireCount" : {
                "w" : NumberLong(1)
            }
        },
        "Collection" : {
            "acquireCount" : {
                "w" : NumberLong(1)
            }
        }
    },
    "exception" : "Cannot update 'count' and 'count' at the same time",
    "exceptionCode" : 16836,
    "millis" : 0,
    "execStats" : {},
    "ts" : ISODate("2017-10-12T01:57:03.008Z"),
    "client" : "127.0.0.1",
    "allUsers" : [],
    "user" : ""
}

为什么驱动程序会吞噬这样的错误?我肯定这是一个bug,但我想先在这里询问一下以确保。


有趣。实际上错误存在于响应中,但是没有抛出异常。此外,尽管存在“writeErrors”,响应状态仍为ok: 1。这至少给出了在源代码中查找的指示。但这绝对是一个错误。因此,您可以通过检查响应中“writeErrors”的长度来“解决”问题。但您真的不应该这样做。 - Neil Lunn
1个回答

2

正如评论所说,这是一个“bug”。具体而言,这个bug在这里

 // Return a Promise
  return new this.s.promiseLibrary(function(resolve, reject) {
    bulkWrite(self, operations, options, function(err, r) {
      if(err && r == null) return reject(err);
      resolve(r);
    });
  });

问题在于回调函数中被包装在一个Promise中的“响应”(或r)实际上不是null,因此尽管存在错误,条件因此不为true,reject(err)没有被调用,而是发送了resolve(r),因此这不被视为异常。
纠正需要一些分类,但您可以通过检查当前bulkWrite()实现中响应中的writeErrors属性来“解决问题”,或者考虑以下其他替代方案之一:
直接使用Bulk API方法:
const MongoClient = require('mongodb').MongoClient,
      uri  = 'mongodb://localhost:27017/myNewDb';

(async () => {

  let db;

  try {

    db = await MongoClient.connect(uri);

    let bulk = db.collection('myNewCollection').initializeOrderedBulkOp();

    bulk.find({ foo: 'bar' }).upsert().updateOne({
      $setOnInsert: { count: 0 },
      $inc: { count: 0 }
    });

    let result = await bulk.execute();
    console.log(JSON.stringify(result,undefined,2));

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

})();

这很好,但是如果服务器实现不支持批量API,则不能自然地回归到使用传统的API方法。

手动包装Promise

(async () => {

  let db = await require('mongodb').MongoClient.connect('mongodb://localhost:27017/myNewDb');

  let mongoOps = [{
    updateOne: {
      filter: { foo: "bar" },
      update: {
        $setOnInsert: { count:0 },
        $inc: { count:1 },
      },
      upsert: true,
    }
  }];

  try {
    let result = await new Promise((resolve,reject) => {

      db.collection("myNewCollection").bulkWrite(mongoOps, (err,r) => {
        if (err) reject(err);
        resolve(r);
      });
    });
    console.log(JSON.stringify(result,undefined,2));
    console.log("Success!");
  } catch(e) {
    console.log("Failed:");
    console.log(e);
  }

})();

正如所指出的,问题在于bulkWrite()返回为Promise的实现方式。因此,您可以使用callback()形式编码,并自行进行Promise包装,以便按照您的预期进行操作。

同样需要注意的是,需要一个JIRA问题和分类,以确定处理异常的正确方法。但希望能很快得到解决。在此期间,请从上述方法中选择一种方法。


谢谢,尼尔!是的,我目前正在通过检查结果对象来解决问题。我假设你是维护者或者你在报告这个问题,因为你的回答中表现出了这一点,但如果你想让我在某个地方发布一个错误报告,请告诉我,我会立即处理。 - user993683
1
@JoeRocc 实际上已经发布了。NODE-1158 已添加以进行跟踪。 - Neil Lunn
已修复,从3.0.0版本开始生效。 - Juan Lanus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接