From 09fdd6cd55bfaca9ac525f6a0668ed97a7f3d048 Mon Sep 17 00:00:00 2001 From: "A. Jesse Jiryu Davis" Date: Wed, 12 Dec 2018 11:16:13 -0500 Subject: SERVER-38510 Set moreToCome flag with OP_MSG writes from shell --- jstests/core/batch_write_command_delete.js | 19 +++++++-- jstests/core/batch_write_command_insert.js | 12 +++++- jstests/core/batch_write_command_update.js | 16 ++++++-- jstests/core/crud_api.js | 12 ++++-- jstests/noPassthrough/shell_can_retry_writes.js | 4 -- ...data_commands_require_majority_write_concern.js | 1 - src/mongo/scripting/mozjs/mongo.cpp | 48 ++++++++++++++++++++-- 7 files changed, 93 insertions(+), 19 deletions(-) diff --git a/jstests/core/batch_write_command_delete.js b/jstests/core/batch_write_command_delete.js index f83a9984936..bb87fa4876d 100644 --- a/jstests/core/batch_write_command_delete.js +++ b/jstests/core/batch_write_command_delete.js @@ -33,6 +33,16 @@ function resultNOK(result) { return !result.ok && typeof(result.code) == 'number' && typeof(result.errmsg) == 'string'; } +function countEventually(collection, n) { + assert.soon( + function() { + return collection.count() === n; + }, + function() { + return "unacknowledged write timed out"; + }); +} + // EACH TEST BELOW SHOULD BE SELF-CONTAINED, FOR EASIER DEBUGGING // @@ -70,7 +80,7 @@ request = { }; result = coll.runCommand(request); assert(resultOK(result), tojson(result)); -assert.eq(0, coll.count()); +countEventually(coll, 0); var fields = ['ok']; assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); @@ -218,7 +228,7 @@ request = { }; result = coll.runCommand(request); assert.commandWorked(result); -assert.eq(0, coll.count()); +countEventually(coll, 0); assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); @@ -235,7 +245,7 @@ request = { }; result = coll.runCommand(request); assert.commandWorked(result); -assert.eq(1, coll.count()); +countEventually(coll, 1); assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); @@ -250,4 +260,5 @@ request = { ordered: false }; result = coll.runCommand(request); -assert(resultNOK(result), tojson(result)); +// Unacknowledged writes are always OK +assert.commandWorked(result); diff --git a/jstests/core/batch_write_command_insert.js b/jstests/core/batch_write_command_insert.js index 5d0a85fa763..feddcb50ea6 100644 --- a/jstests/core/batch_write_command_insert.js +++ b/jstests/core/batch_write_command_insert.js @@ -36,6 +36,16 @@ function resultNOK(result) { return !result.ok && typeof(result.code) == 'number' && typeof(result.errmsg) == 'string'; } +function countEventually(collection, n) { + assert.soon( + function() { + return collection.count() === n; + }, + function() { + return "unacknowledged write timed out"; + }); +} + // EACH TEST BELOW SHOULD BE SELF-CONTAINED, FOR EASIER DEBUGGING // @@ -69,7 +79,7 @@ request = { }; result = coll.runCommand(request); assert(resultOK(result), tojson(result)); -assert.eq(coll.count(), 1); +countEventually(coll, 1); var fields = ['ok']; assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); diff --git a/jstests/core/batch_write_command_update.js b/jstests/core/batch_write_command_update.js index 59c327db233..1c124bfadb7 100644 --- a/jstests/core/batch_write_command_update.js +++ b/jstests/core/batch_write_command_update.js @@ -32,6 +32,16 @@ function resultNOK(result) { return !result.ok && typeof(result.code) == 'number' && typeof(result.errmsg) == 'string'; } +function countEventually(collection, n) { + assert.soon( + function() { + return collection.count() === n; + }, + function() { + return "unacknowledged write timed out"; + }); +} + // EACH TEST BELOW SHOULD BE SELF-CONTAINED, FOR EASIER DEBUGGING // @@ -114,7 +124,7 @@ request = { }; result = coll.runCommand(request); assert(resultOK(result), tojson(result)); -assert.eq(1, coll.count({})); +countEventually(coll, 1); var fields = ['ok']; assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); @@ -133,7 +143,7 @@ request = { }; result = coll.runCommand(request); assert(resultOK(result), tojson(result)); -assert.eq(2, coll.count()); +countEventually(coll, 2); assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); @@ -272,7 +282,7 @@ request = { }; result = coll.runCommand(request); assert(result.ok, tojson(result)); -assert.eq(1, coll.count()); +countEventually(coll, 1); assert.hasFields(result, fields, 'fields in result do not match: ' + tojson(fields)); diff --git a/jstests/core/crud_api.js b/jstests/core/crud_api.js index 7a7790fa673..e0f099fb09a 100644 --- a/jstests/core/crud_api.js +++ b/jstests/core/crud_api.js @@ -33,9 +33,15 @@ verifyResult(args.result, r); // Get all the results - var results = coll.find({}).sort({_id: 1}).toArray(); - - assert.docEq(args.expected, results); + assert.soonNoExcept( + function() { + var results = coll.find({}).sort({_id: 1}).toArray(); + assert.docEq(args.expected, results); + return true; + }, + function() { + return "collection never contained expected documents"; + }); }; } diff --git a/jstests/noPassthrough/shell_can_retry_writes.js b/jstests/noPassthrough/shell_can_retry_writes.js index 2a7deb83818..60ef8df9cd1 100644 --- a/jstests/noPassthrough/shell_can_retry_writes.js +++ b/jstests/noPassthrough/shell_can_retry_writes.js @@ -112,10 +112,6 @@ coll.insertOne({_id: "majority"}, {w: "majority"}); }); - testCommandCanBeRetried(function() { - coll.insertOne({_id: 0}, {w: 0}); - }, false); - // // Tests for bulkWrite(). // diff --git a/jstests/sharding/configsvr_metadata_commands_require_majority_write_concern.js b/jstests/sharding/configsvr_metadata_commands_require_majority_write_concern.js index 3e93574ca18..d4be74ed8bc 100644 --- a/jstests/sharding/configsvr_metadata_commands_require_majority_write_concern.js +++ b/jstests/sharding/configsvr_metadata_commands_require_majority_write_concern.js @@ -14,7 +14,6 @@ // Commands sent directly to the config server should fail with WC < majority. const unacceptableWCsForConfig = [ - {writeConcern: {w: 0}}, {writeConcern: {w: 1}}, {writeConcern: {w: 2}}, {writeConcern: {w: 3}}, diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index 734e6b562b7..5188c0f3888 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -110,6 +110,23 @@ DBClientBase* getConnection(JS::CallArgs& args) { return getConnectionRef(args).get(); } +bool isUnacknowledged(const BSONObj& cmdObj) { + auto wc = cmdObj["writeConcern"]; + return wc && wc["w"].isNumber() && wc["w"].safeNumberLong() == 0; +} + +void returnOk(JSContext* cx, JS::CallArgs& args) { + ValueReader(cx, args.rval()).fromBSON(BSON("ok" << 1), nullptr, false); +} + +void runFireAndForgetCommand(const std::shared_ptr& conn, + const std::string& database, + BSONObj body, + const BSONObj& extraFields = {}) { + auto request = OpMsgRequest::fromDBAndBody(database, body, extraFields); + conn->runFireAndForgetCommand(request); +} + void setCursor(MozJSImplScope* scope, JS::HandleObject target, std::unique_ptr cursor, @@ -135,6 +152,17 @@ void setCursorHandle(MozJSImplScope* scope, scope->trackedNew(std::move(ns), cursorId, *client)); } +void setHiddenMongo(JSContext* cx, JS::HandleValue value, JS::CallArgs& args) { + ObjectWrapper o(cx, args.rval()); + if (!o.hasField(InternedString::_mongo)) { + o.defineProperty(InternedString::_mongo, value, JSPROP_READONLY | JSPROP_PERMANENT); + } +} + +void setHiddenMongo(JSContext* cx, JS::CallArgs& args) { + setHiddenMongo(cx, args.thisv(), args); +} + void setHiddenMongo(JSContext* cx, std::shared_ptr resPtr, DBClientBase* origConn, @@ -143,7 +171,7 @@ void setHiddenMongo(JSContext* cx, // If the connection that ran the command is the same as conn, then we set a hidden "_mongo" // property on the returned object that is just "this" Mongo object. if (resPtr.get() == origConn) { - o.defineProperty(InternedString::_mongo, args.thisv(), JSPROP_READONLY | JSPROP_PERMANENT); + setHiddenMongo(cx, args.thisv(), args); } else { JS::RootedObject newMongo(cx); @@ -168,8 +196,7 @@ void setHiddenMongo(JSContext* cx, JS::RootedValue value(cx); value.setObjectOrNull(newMongo); - - o.defineProperty(InternedString::_mongo, value, JSPROP_READONLY | JSPROP_PERMANENT); + setHiddenMongo(cx, value, args); } } } // namespace @@ -212,6 +239,13 @@ void MongoBase::Functions::runCommand::call(JSContext* cx, JS::CallArgs args) { BSONObj cmdObj = ValueWriter(cx, args.get(1)).toBSON(); + if (isUnacknowledged(cmdObj)) { + runFireAndForgetCommand(conn, database, cmdObj); + setHiddenMongo(cx, args); + returnOk(cx, args); + return; + } + int queryOptions = ValueWriter(cx, args.get(2)).toInt32(); BSONObj cmdRes; auto resTuple = conn->runCommandWithTarget(database, cmdObj, cmdRes, conn, queryOptions); @@ -244,6 +278,14 @@ void MongoBase::Functions::runCommandWithMetadata::call(JSContext* cx, JS::CallA BSONObj commandArgs = ValueWriter(cx, args.get(2)).toBSON(); const auto& conn = getConnectionRef(args); + + if (isUnacknowledged(commandArgs)) { + runFireAndForgetCommand(conn, database, commandArgs, metadata); + setHiddenMongo(cx, args); + returnOk(cx, args); + return; + } + auto resTuple = conn->runCommandWithTarget( OpMsgRequest::fromDBAndBody(database, commandArgs, metadata), conn); auto res = std::move(std::get<0>(resTuple)); -- cgit v1.2.1