summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2017-11-08 19:29:28 -0500
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2017-11-08 19:29:28 -0500
commit6d8e6b9cce052cdd442e207a27df10e698b2bb00 (patch)
tree5a7bb7b5cf93902bf00a681dccb0f54adb2f1ab0 /src/mongo
parentf7756c41c5ed54e8e546461395bc8898d885af0c (diff)
downloadmongo-6d8e6b9cce052cdd442e207a27df10e698b2bb00.tar.gz
SERVER-31296 Update sessions, causal, and retryable in the mongo shell.
* Removes the initialClusterTime and initialOperationTime session options. * Enables causal consistency by default when using an explicit session. * Adds a --retryWrites command line option to the mongo shell for enabling retryable writes in the mongo shell. The retryWrites options to SessionOptions is left for convenience with testing. * Renames setClusterTime() to advanceClusterTime(), and adds a corresponding advanceOperationTime() method to DriverSession. * Enables assigning transaction numbers for write commands where ordered=false. * Prevents the mongo shell from sending afterClusterTime or assigning transaction numbers when talking to a stand-alone mongod. * Prevents the mongo shell from assigning transaction numbers when using an unacknowledged (w=0) writeConcern. * Changes DBClientRS to re-discover the current primary of the replica set when it receives an error code representing "not master" in addition to an error message representing "not master". * Adds a shellPrint() pretty-printer for SessionOptions and DriverSession instances so they no longer print out their entire object definition.
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/client/dbclient.cpp15
-rw-r--r--src/mongo/client/dbclientinterface.h8
-rw-r--r--src/mongo/db/service_entry_point_mongod.cpp7
-rw-r--r--src/mongo/scripting/mozjs/mongo.cpp31
-rw-r--r--src/mongo/scripting/mozjs/mongo.h8
-rw-r--r--src/mongo/shell/dbshell.cpp8
-rw-r--r--src/mongo/shell/session.js332
-rw-r--r--src/mongo/shell/shell_options.cpp9
-rw-r--r--src/mongo/shell/shell_options.h1
-rw-r--r--src/mongo/shell/shell_utils.cpp5
-rw-r--r--src/mongo/shell/utils.js29
11 files changed, 294 insertions, 159 deletions
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index e3869b773df..8028cc1b3bc 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -998,7 +998,7 @@ std::pair<rpc::UniqueReply, DBClientBase*> DBClientConnection::runCommandWithTar
if (!_parentReplSetName.empty()) {
const auto replyBody = out.first->getCommandReply();
if (!isOk(replyBody)) {
- handleNotMasterResponse(replyBody["errmsg"]);
+ handleNotMasterResponse(replyBody, "errmsg");
}
}
@@ -1011,7 +1011,7 @@ std::pair<rpc::UniqueReply, std::shared_ptr<DBClientBase>> DBClientConnection::r
if (!_parentReplSetName.empty()) {
const auto replyBody = out.first->getCommandReply();
if (!isOk(replyBody)) {
- handleNotMasterResponse(replyBody["errmsg"]);
+ handleNotMasterResponse(replyBody, "errmsg");
}
}
@@ -1474,7 +1474,7 @@ void DBClientConnection::checkResponse(const std::vector<BSONObj>& batch,
*host = _serverAddress.toString();
if (!_parentReplSetName.empty() && !batch.empty()) {
- handleNotMasterResponse(getErrField(batch[0]));
+ handleNotMasterResponse(batch[0], "$err");
}
}
@@ -1482,8 +1482,13 @@ void DBClientConnection::setParentReplSetName(const string& replSetName) {
_parentReplSetName = replSetName;
}
-void DBClientConnection::handleNotMasterResponse(const BSONElement& elemToCheck) {
- if (!isNotMasterErrorString(elemToCheck)) {
+void DBClientConnection::handleNotMasterResponse(const BSONObj& replyBody,
+ StringData errorMsgFieldName) {
+ const BSONElement errorMsgElem = replyBody[errorMsgFieldName];
+ const BSONElement codeElem = replyBody["code"];
+
+ if (!isNotMasterErrorString(errorMsgElem) &&
+ !ErrorCodes::isNotMasterError(ErrorCodes::Error(codeElem.numberInt()))) {
return;
}
diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h
index fdfc816b126..de81dabdd92 100644
--- a/src/mongo/client/dbclientinterface.h
+++ b/src/mongo/client/dbclientinterface.h
@@ -1084,11 +1084,11 @@ protected:
private:
/**
- * Checks the BSONElement for the 'not master' keyword and if it does exist,
- * try to inform the replica set monitor that the host this connects to is
- * no longer primary.
+ * Inspects the contents of 'replyBody' and informs the replica set monitor that the host 'this'
+ * is connected with is no longer the primary if a "not master" error message or error code was
+ * returned.
*/
- void handleNotMasterResponse(const BSONElement& elemToCheck);
+ void handleNotMasterResponse(const BSONObj& replyBody, StringData errorMsgFieldName);
// Contains the string for the replica set name of the host this is connected to.
// Should be empty if this connection is not pointing to a replica set member.
diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp
index 3814ce52919..bae7d227be6 100644
--- a/src/mongo/db/service_entry_point_mongod.cpp
+++ b/src/mongo/db/service_entry_point_mongod.cpp
@@ -87,6 +87,7 @@
namespace mongo {
MONGO_FP_DECLARE(rsStopGetMore);
+MONGO_FP_DECLARE(respondWithNotPrimaryInCommandDispatch);
namespace {
using logger::LogComponent;
@@ -658,7 +659,11 @@ void execCommandDatabase(OperationContext* opCtx,
uasserted(ErrorCodes::NotMasterNoSlaveOk, "not master and slaveOk=false");
}
- uassert(ErrorCodes::NotMaster, "not master", canRunHere);
+ if (MONGO_FAIL_POINT(respondWithNotPrimaryInCommandDispatch)) {
+ uassert(ErrorCodes::NotMaster, "not primary", canRunHere);
+ } else {
+ uassert(ErrorCodes::NotMaster, "not master", canRunHere);
+ }
if (!command->maintenanceOk() &&
replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet &&
diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp
index debede5bee7..c740e3acadc 100644
--- a/src/mongo/scripting/mozjs/mongo.cpp
+++ b/src/mongo/scripting/mozjs/mongo.cpp
@@ -88,7 +88,10 @@ const JSFunctionSpec MongoBase::methods[] = {
isReplicaSetMember, MongoLocalInfo, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isMongos, MongoLocalInfo, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getClusterTime, MongoLocalInfo, MongoExternalInfo),
- MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(setClusterTime, MongoLocalInfo, MongoExternalInfo),
+ MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(
+ advanceClusterTime, MongoLocalInfo, MongoExternalInfo),
+ MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(
+ resetClusterTime_forTesting, MongoLocalInfo, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(_startSession, MongoLocalInfo, MongoExternalInfo),
JS_FS_END,
};
@@ -194,9 +197,14 @@ BSONObj MongoBase::getClusterTime() {
return latestlogicalTime;
}
-void MongoBase::setClusterTime(const BSONObj& newTime) {
+void MongoBase::resetClusterTime_forTesting() {
+ stdx::lock_guard<stdx::mutex> lk(logicalTimeMutex);
+ latestlogicalTime = BSONObj();
+}
+
+void MongoBase::advanceClusterTime(const BSONObj& newTime) {
if (newTime["clusterTime"].eoo())
- uasserted(ErrorCodes::BadValue, "missing clusterTime field in setClusterTime argument");
+ uasserted(ErrorCodes::BadValue, "missing clusterTime field in advanceClusterTime argument");
stdx::lock_guard<stdx::mutex> lk(logicalTimeMutex);
if (latestlogicalTime.isEmpty() ||
@@ -791,17 +799,26 @@ void MongoBase::Functions::getClusterTime::call(JSContext* cx, JS::CallArgs args
args.rval().setUndefined();
}
-void MongoBase::Functions::setClusterTime::call(JSContext* cx, JS::CallArgs args) {
+void MongoBase::Functions::advanceClusterTime::call(JSContext* cx, JS::CallArgs args) {
if (args.length() != 1)
- uasserted(ErrorCodes::BadValue, "setClusterTime takes 1 argument");
+ uasserted(ErrorCodes::BadValue, "advanceClusterTime takes 1 argument");
if (!args.get(0).isObject())
- uasserted(ErrorCodes::BadValue, "the parameter to setClusterTime must be an object");
+ uasserted(ErrorCodes::BadValue, "the parameter to advanceClusterTime must be an object");
auto newTime = ObjectWrapper(cx, args.get(0)).toBSON();
- MongoBase::setClusterTime(newTime);
+ MongoBase::advanceClusterTime(newTime);
+
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::resetClusterTime_forTesting::call(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 0) {
+ uasserted(ErrorCodes::BadValue, "resetClusterTime_forTesting takes no arguments");
+ }
+ MongoBase::resetClusterTime_forTesting();
args.rval().setUndefined();
}
diff --git a/src/mongo/scripting/mozjs/mongo.h b/src/mongo/scripting/mozjs/mongo.h
index f6c6a535c1c..4d7a0200bd7 100644
--- a/src/mongo/scripting/mozjs/mongo.h
+++ b/src/mongo/scripting/mozjs/mongo.h
@@ -65,17 +65,19 @@ struct MongoBase : public BaseInfo {
MONGO_DECLARE_JS_FUNCTION(isReplicaSetMember);
MONGO_DECLARE_JS_FUNCTION(isMongos);
MONGO_DECLARE_JS_FUNCTION(getClusterTime);
- MONGO_DECLARE_JS_FUNCTION(setClusterTime);
+ MONGO_DECLARE_JS_FUNCTION(advanceClusterTime);
+ MONGO_DECLARE_JS_FUNCTION(resetClusterTime_forTesting);
MONGO_DECLARE_JS_FUNCTION(_startSession);
};
- static const JSFunctionSpec methods[24];
+ static const JSFunctionSpec methods[25];
static const char* const className;
static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
static BSONObj getClusterTime();
- static void setClusterTime(const BSONObj& newTime);
+ static void advanceClusterTime(const BSONObj& newTime);
+ static void resetClusterTime_forTesting();
};
/**
diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp
index 03ca476e744..f2b635d7883 100644
--- a/src/mongo/shell/dbshell.cpp
+++ b/src/mongo/shell/dbshell.cpp
@@ -750,7 +750,13 @@ int _main(int argc, char* argv[], char** envp) {
ss << "db = connect( \""
<< getURIFromArgs(
shellGlobalParams.url, shellGlobalParams.dbhost, shellGlobalParams.port)
- << "\")";
+ << "\");";
+
+ if (shellGlobalParams.shouldRetryWrites) {
+ // If the user specified --retryWrites to the mongo shell, then we replace the global
+ // `db` object with a DB object that has retryable writes enabled.
+ ss << "db = db.getMongo().startSession({retryWrites: true}).getDatabase(db.getName());";
+ }
mongo::shell_utils::_dbConnect = ss.str();
diff --git a/src/mongo/shell/session.js b/src/mongo/shell/session.js
index ae49db63f72..48e3eab554a 100644
--- a/src/mongo/shell/session.js
+++ b/src/mongo/shell/session.js
@@ -6,14 +6,31 @@ var {
} = (function() {
"use strict";
+ const kShellDefaultShouldRetryWrites =
+ typeof _shouldRetryWrites === "function" ? _shouldRetryWrites() : false;
+
+ function isNonNullObject(obj) {
+ return typeof obj === "object" && obj !== null;
+ }
+
function SessionOptions(rawOptions = {}) {
+ if (!(this instanceof SessionOptions)) {
+ return new SessionOptions(rawOptions);
+ }
+
let _readPreference = rawOptions.readPreference;
let _readConcern = rawOptions.readConcern;
let _writeConcern = rawOptions.writeConcern;
- const _initialClusterTime = rawOptions.initialClusterTime;
- const _initialOperationTime = rawOptions.initialOperationTime;
- let _causalConsistency = rawOptions.causalConsistency;
- let _retryWrites = rawOptions.retryWrites;
+
+ // Causal consistency is implicitly enabled when a session is explicitly started.
+ const _causalConsistency =
+ rawOptions.hasOwnProperty("causalConsistency") ? rawOptions.causalConsistency : true;
+
+ // If the user specified --retryWrites to the mongo shell, then we enable retryable
+ // writes automatically.
+ const _retryWrites = rawOptions.hasOwnProperty("retryWrites")
+ ? rawOptions.retryWrites
+ : kShellDefaultShouldRetryWrites;
this.getReadPreference = function getReadPreference() {
return _readPreference;
@@ -42,34 +59,30 @@ var {
_writeConcern = writeConcern;
};
- this.getInitialClusterTime = function getInitialClusterTime() {
- return _initialClusterTime;
- };
-
- this.getInitialOperationTime = function getInitialOperationTime() {
- return _initialOperationTime;
- };
-
this.isCausalConsistency = function isCausalConsistency() {
return _causalConsistency;
};
- this.setCausalConsistency = function setCausalConsistency(causalConsistency = true) {
- _causalConsistency = causalConsistency;
- };
-
this.shouldRetryWrites = function shouldRetryWrites() {
return _retryWrites;
};
- this.setRetryWrites = function setRetryWrites(retryWrites = true) {
- _retryWrites = retryWrites;
+ this.shellPrint = function _shellPrint() {
+ return this.toString();
+ };
+
+ this.tojson = function _tojson(...args) {
+ return tojson(rawOptions, ...args);
+ };
+
+ this.toString = function toString() {
+ return "SessionOptions(" + this.tojson() + ")";
};
}
function SessionAwareClient(client) {
- const kWireVersionSupportingLogicalSession = 6;
const kWireVersionSupportingCausalConsistency = 6;
+ const kWireVersionSupportingLogicalSession = 6;
const kWireVersionSupportingRetryableWrites = 6;
this.getReadPreference = function getReadPreference(driverSession) {
@@ -104,6 +117,8 @@ var {
wireVersion <= client.getMaxWireVersion();
}
+ // TODO: Update this whitelist, or convert it to a blacklist depending on the outcome of
+ // SERVER-31743.
const kCommandsThatSupportReadConcern = new Set([
"aggregate",
"count",
@@ -163,32 +178,26 @@ var {
function injectAfterClusterTime(cmdObj, operationTime) {
cmdObj = Object.assign({}, cmdObj);
- if (operationTime !== undefined) {
- const cmdName = Object.keys(cmdObj)[0];
+ const cmdName = Object.keys(cmdObj)[0];
- // If the command is in a wrapped form, then we look for the actual command object
- // inside the query/$query object.
- let cmdObjUnwrapped = cmdObj;
- if (cmdName === "query" || cmdName === "$query") {
- cmdObj[cmdName] = Object.assign({}, cmdObj[cmdName]);
- cmdObjUnwrapped = cmdObj[cmdName];
- }
+ // If the command is in a wrapped form, then we look for the actual command object
+ // inside the query/$query object.
+ let cmdObjUnwrapped = cmdObj;
+ if (cmdName === "query" || cmdName === "$query") {
+ cmdObj[cmdName] = Object.assign({}, cmdObj[cmdName]);
+ cmdObjUnwrapped = cmdObj[cmdName];
+ }
- cmdObjUnwrapped.readConcern = Object.assign({}, cmdObjUnwrapped.readConcern);
- const readConcern = cmdObjUnwrapped.readConcern;
+ cmdObjUnwrapped.readConcern = Object.assign({}, cmdObjUnwrapped.readConcern);
+ const readConcern = cmdObjUnwrapped.readConcern;
- if (!readConcern.hasOwnProperty("afterClusterTime")) {
- readConcern.afterClusterTime = operationTime;
- }
+ if (!readConcern.hasOwnProperty("afterClusterTime")) {
+ readConcern.afterClusterTime = operationTime;
}
return cmdObj;
}
- function isNonNullObject(obj) {
- return typeof obj === "object" && obj !== null;
- }
-
function prepareCommandRequest(driverSession, cmdObj) {
if (serverSupports(kWireVersionSupportingLogicalSession)) {
cmdObj = driverSession._serverSession.injectSessionId(cmdObj);
@@ -230,15 +239,24 @@ var {
}
}
+ // TODO SERVER-31868: A user should get back an error if they attempt to advance the
+ // DriverSession's operationTime manually when talking to a stand-alone mongod. Removing
+ // the `(client.isReplicaSetMember() || client.isMongos())` condition will also involve
+ // calling resetOperationTime_forTesting() in JavaScript tests that start different
+ // cluster types.
if (serverSupports(kWireVersionSupportingCausalConsistency) &&
+ (client.isReplicaSetMember() || client.isMongos()) &&
(driverSession.getOptions().isCausalConsistency() ||
client.isCausalConsistency()) &&
canUseReadConcern(cmdObj)) {
- // `driverSession._operationTime` is the smallest time needed for performing a
+ // `driverSession.getOperationTime()` is the smallest time needed for performing a
// causally consistent read using the current session. Note that
// `client.getClusterTime()` is no smaller than the operation time and would
// therefore only be less efficient to wait until.
- cmdObj = injectAfterClusterTime(cmdObj, driverSession._operationTime);
+ const operationTime = driverSession.getOperationTime();
+ if (operationTime !== undefined) {
+ cmdObj = injectAfterClusterTime(cmdObj, driverSession.getOperationTime());
+ }
}
if (jsTest.options().alwaysInjectTransactionNumber &&
@@ -252,13 +270,13 @@ var {
}
function processCommandResponse(driverSession, res) {
- if (res.hasOwnProperty("operationTime") &&
- bsonWoCompare({_: res.operationTime}, {_: driverSession._operationTime}) > 0) {
- driverSession._operationTime = res.operationTime;
+ if (res.hasOwnProperty("operationTime")) {
+ driverSession.advanceOperationTime(res.operationTime);
}
if (res.hasOwnProperty("$clusterTime")) {
- client.setClusterTime(res.$clusterTime);
+ driverSession.advanceClusterTime(res.$clusterTime);
+ client.advanceClusterTime(res.$clusterTime);
}
}
@@ -280,13 +298,34 @@ var {
do {
try {
- return clientFunction.apply(client, clientFunctionArguments);
+ const res = clientFunction.apply(client, clientFunctionArguments);
+ if (res.ok === 1 || numRetries === 0 ||
+ !ErrorCodes.isNotMasterError(res.code)) {
+ return res;
+ }
} catch (e) {
- // TODO: Should we run an explicit "isMaster" command in order to compare the
- // wire version of the server after we reconnect to it?
if (!isNetworkError(e) || numRetries === 0) {
throw e;
}
+
+ // We run an "isMaster" command explicitly to force the underlying DBClient to
+ // reconnect to the server.
+ const res = client.adminCommand({isMaster: 1});
+ if (res.ok !== 1) {
+ throw e;
+ }
+
+ // It's possible that the server we're connected with after re-establishing our
+ // connection doesn't support retryable writes. If that happens, then we just
+ // return the original network error back to the user.
+ const serverSupportsRetryableWrites = res.hasOwnProperty("minWireVersion") &&
+ res.hasOwnProperty("maxWireVersion") &&
+ res.minWireVersion <= kWireVersionSupportingRetryableWrites &&
+ kWireVersionSupportingRetryableWrites <= res.maxWireVersion;
+
+ if (!serverSupportsRetryableWrites) {
+ throw e;
+ }
}
--numRetries;
@@ -388,6 +427,18 @@ var {
cmdName = Object.keys(cmdObj)[0];
}
+ if (isNonNullObject(cmdObj.writeConcern)) {
+ const writeConcern = cmdObj.writeConcern;
+
+ // We use bsonWoCompare() in order to handle cases where the "w" field is specified
+ // as a NumberInt() or NumberLong() instance.
+ if (writeConcern.hasOwnProperty("w") &&
+ bsonWoCompare({_: writeConcern.w}, {_: 0}) === 0) {
+ // Unacknowledged writes cannot be retried.
+ return false;
+ }
+ }
+
if (cmdName === "insert") {
if (!Array.isArray(cmdObj.documents)) {
// The command object is malformed, so we'll just leave it as-is and let the
@@ -395,14 +446,10 @@ var {
return false;
}
- if (cmdObj.documents.length === 1) {
- // Single-statement operations (e.g. insertOne()) can be retried.
- return true;
- }
-
- // Multi-statement operations (e.g. insertMany()) can be retried if they are
+ // Both single-statement operations (e.g. insertOne()) and multi-statement
+ // operations (e.g. insertMany()) can be retried regardless of whether they are
// executed in order by the server.
- return cmdObj.ordered ? true : false;
+ return true;
} else if (cmdName === "update") {
if (!Array.isArray(cmdObj.updates)) {
// The command object is malformed, so we'll just leave it as-is and let the
@@ -417,15 +464,10 @@ var {
return false;
}
- if (cmdObj.updates.length === 1) {
- // Single-statement operations that modify a single document (e.g. updateOne())
- // can be retried.
- return true;
- }
-
- // Multi-statement operations that each modify a single document (e.g. bulkWrite())
- // can be retried if they are executed in order by the server.
- return cmdObj.ordered ? true : false;
+ // Both single-statement operations (e.g. updateOne()) and multi-statement
+ // operations (e.g. bulkWrite()) can be retried regardless of whether they are
+ // executed in order by the server.
+ return true;
} else if (cmdName === "delete") {
if (!Array.isArray(cmdObj.deletes)) {
// The command object is malformed, so we'll just leave it as-is and let the
@@ -443,15 +485,10 @@ var {
return false;
}
- if (cmdObj.deletes.length === 1) {
- // Single-statement operations that modify a single document (e.g. deleteOne())
- // can be retried.
- return true;
- }
-
- // Multi-statement operations that each modify a single document (e.g. bulkWrite())
- // can be retried if they are executed in order by the server.
- return cmdObj.ordered ? true : false;
+ // Both single-statement operations (e.g. deleteOne()) and multi-statement
+ // operations (e.g. bulkWrite()) can be retried regardless of whether they are
+ // executed in order by the server.
+ return true;
} else if (cmdName === "findAndModify" || cmdName === "findandmodify") {
// Operations that modify a single document (e.g. findOneAndUpdate()) can be
// retried.
@@ -462,21 +499,19 @@ var {
};
}
- function makeDriverSessionConstructor(implMethods) {
- return function(client, options = {}) {
+ function makeDriverSessionConstructor(implMethods, defaultOptions = {}) {
+ return function(client, options = defaultOptions) {
let _options = options;
let _hasEnded = false;
+ let _operationTime;
+ let _clusterTime;
+
if (!(_options instanceof SessionOptions)) {
_options = new SessionOptions(_options);
}
this._serverSession = implMethods.createServerSession(client);
- this._operationTime = _options.getInitialOperationTime();
-
- if (_options.getInitialClusterTime() !== undefined) {
- client.setClusterTime(_options.getInitialClusterTime());
- }
this.getClient = function getClient() {
return client;
@@ -490,12 +525,42 @@ var {
return _options;
};
+ this.getSessionId = function getSessionId() {
+ if (!this._serverSession.hasOwnProperty("handle")) {
+ return null;
+ }
+ return this._serverSession.handle.getId();
+ };
+
this.getOperationTime = function getOperationTime() {
- return this._operationTime;
+ return _operationTime;
+ };
+
+ this.advanceOperationTime = function advanceOperationTime(operationTime) {
+ if (!isNonNullObject(_operationTime) ||
+ bsonWoCompare({_: operationTime}, {_: _operationTime}) > 0) {
+ _operationTime = operationTime;
+ }
+ };
+
+ this.resetOperationTime_forTesting = function resetOperationTime_forTesting() {
+ _operationTime = undefined;
};
this.getClusterTime = function getClusterTime() {
- return client.getClusterTime();
+ return _clusterTime;
+ };
+
+ this.advanceClusterTime = function advanceClusterTime(clusterTime) {
+ if (!isNonNullObject(_clusterTime) ||
+ bsonWoCompare({_: clusterTime.clusterTime}, {_: _clusterTime.clusterTime}) >
+ 0) {
+ _clusterTime = clusterTime;
+ }
+ };
+
+ this.resetClusterTime_forTesting = function resetClusterTime_forTesting() {
+ _clusterTime = undefined;
};
this.getDatabase = function getDatabase(dbName) {
@@ -516,6 +581,22 @@ var {
this._hasEnded = true;
implMethods.endSession(this._serverSession);
};
+
+ this.shellPrint = function() {
+ return this.toString();
+ };
+
+ this.tojson = function _tojson(...args) {
+ return tojson(this.getSessionId(), ...args);
+ };
+
+ this.toString = function toString() {
+ const sessionId = this.getSessionId();
+ if (sessionId === null) {
+ return "dummy session";
+ }
+ return "session " + tojson(sessionId);
+ };
};
}
@@ -530,15 +611,6 @@ var {
});
function DelegatingDriverSession(client, originalSession) {
- this._serverSession = originalSession._serverSession;
- Object.defineProperty(this, "_operationTime", {
- get: function() {
- return originalSession._operationTime;
- },
- set: function(operationTime) {
- originalSession._operationTime = operationTime;
- },
- });
const sessionAwareClient = new SessionAwareClient(client);
this.getClient = function() {
@@ -549,54 +621,56 @@ var {
return sessionAwareClient;
};
- this.getOptions = function() {
- return originalSession.getOptions();
- };
-
- this.getOperationTime = function getOperationTime() {
- return this._operationTime;
- };
-
- this.getClusterTime = function getClusterTime() {
- return originalSession.getClusterTime();
- };
-
this.getDatabase = function(dbName) {
const db = client.getDB(dbName);
db._session = this;
return db;
};
- this.hasEnded = function() {
- return originalSession.hasEnded();
- };
-
- this.endSession = function() {
- return originalSession.endSession();
- };
+ return new Proxy(this, {
+ get: function get(target, property, receiver) {
+ // If the property is defined on the DelegatingDriverSession instance itself, then
+ // return it. Otherwise, get the value of the property from the `originalSession`
+ // instance.
+ if (target.hasOwnProperty(property)) {
+ return target[property];
+ }
+ return originalSession[property];
+ },
+ });
}
- const DummyDriverSession = makeDriverSessionConstructor({
- createServerSession: function createServerSession(client) {
- return {
- client: new SessionAwareClient(client),
-
- injectSessionId: function injectSessionId(cmdObj) {
- return cmdObj;
- },
-
- assignTransactionNumber: function assignTransactionNumber(cmdObj) {
- return cmdObj;
- },
-
- canRetryWrites: function canRetryWrites(cmdObj) {
- return false;
- },
- };
- },
-
- endSession: function endSession(serverSession) {},
- });
+ // The default session on the Mongo connection object should report that causal consistency
+ // isn't enabled when interrogating the SessionOptions since it must be enabled on the Mongo
+ // connection object.
+ //
+ // The default session on the Mongo connection object should also report that retryable
+ // writes isn't enabled when interrogating the SessionOptions since `DummyDriverSession` won't
+ // ever assign a transaction number.
+ const DummyDriverSession =
+ makeDriverSessionConstructor( // Force clang-format to break this line.
+ {
+ createServerSession: function createServerSession(client) {
+ return {
+ client: new SessionAwareClient(client),
+
+ injectSessionId: function injectSessionId(cmdObj) {
+ return cmdObj;
+ },
+
+ assignTransactionNumber: function assignTransactionNumber(cmdObj) {
+ return cmdObj;
+ },
+
+ canRetryWrites: function canRetryWrites(cmdObj) {
+ return false;
+ },
+ };
+ },
+
+ endSession: function endSession(serverSession) {},
+ },
+ {causalConsistency: false, retryWrites: false});
// We don't actually put anything on DriverSession.prototype, but this way
// `session instanceof DriverSession` will work for DummyDriverSession instances.
diff --git a/src/mongo/shell/shell_options.cpp b/src/mongo/shell/shell_options.cpp
index 7898fc6fa1b..f14798a2273 100644
--- a/src/mongo/shell/shell_options.cpp
+++ b/src/mongo/shell/shell_options.cpp
@@ -208,6 +208,12 @@ Status addMongoShellOptions(moe::OptionSection* options) {
" commands, compatibility, legacy")
.hidden();
+ options->addOptionChaining(
+ "retryWrites",
+ "retryWrites",
+ moe::Switch,
+ "automatically retry write operations upon transient network errors");
+
options
->addOptionChaining(
"rpcProtocols", "rpcProtocols", moe::String, " none, opQueryOnly, opCommandOnly, all")
@@ -363,6 +369,9 @@ Status storeMongoShellOptions(const moe::Environment& params,
}
shellGlobalParams.readMode = mode;
}
+ if (params.count("retryWrites")) {
+ shellGlobalParams.shouldRetryWrites = true;
+ }
if (params.count("rpcProtocols")) {
std::string protos = params["rpcProtocols"].as<string>();
auto parsedRPCProtos = rpc::parseProtocolSet(protos);
diff --git a/src/mongo/shell/shell_options.h b/src/mongo/shell/shell_options.h
index 52b02ce1d34..04a66ed7fec 100644
--- a/src/mongo/shell/shell_options.h
+++ b/src/mongo/shell/shell_options.h
@@ -71,6 +71,7 @@ struct ShellGlobalParams {
std::string writeMode = "commands";
std::string readMode = "compatibility";
+ bool shouldRetryWrites = false;
boost::optional<rpc::ProtocolSet> rpcProtocols = boost::none;
diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp
index 3699a8baa47..ad3b441d7c7 100644
--- a/src/mongo/shell/shell_utils.cpp
+++ b/src/mongo/shell/shell_utils.cpp
@@ -213,6 +213,10 @@ BSONObj readMode(const BSONObj&, void*) {
return BSON("" << shellGlobalParams.readMode);
}
+BSONObj shouldRetryWrites(const BSONObj&, void* data) {
+ return BSON("" << shellGlobalParams.shouldRetryWrites);
+}
+
BSONObj interpreterVersion(const BSONObj& a, void* data) {
uassert(16453, "interpreterVersion accepts no arguments", a.nFields() == 0);
return BSON("" << getGlobalScriptEngine()->getInterpreterVersionString());
@@ -240,6 +244,7 @@ void initScope(Scope& scope) {
scope.injectNative("_useWriteCommandsDefault", useWriteCommandsDefault);
scope.injectNative("_writeMode", writeMode);
scope.injectNative("_readMode", readMode);
+ scope.injectNative("_shouldRetryWrites", shouldRetryWrites);
scope.externalSetup();
mongo::shell_utils::installShellUtils(scope);
scope.execSetup(JSFiles::servers);
diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js
index d626f4e600d..359a543075c 100644
--- a/src/mongo/shell/utils.js
+++ b/src/mongo/shell/utils.js
@@ -471,27 +471,38 @@ isMasterStatePrompt = function(isMasterResponse) {
return state + '> ';
};
-if (typeof(_useWriteCommandsDefault) == 'undefined') {
- // This is for cases when the v8 engine is used other than the mongo shell, like map reduce.
- _useWriteCommandsDefault = function() {
+if (typeof _useWriteCommandsDefault === "undefined") {
+ // We ensure the _useWriteCommandsDefault() function is always defined, in case the JavaScript
+ // engine is being used from someplace other than the mongo shell (e.g. map-reduce).
+ _useWriteCommandsDefault = function _useWriteCommandsDefault() {
return false;
};
}
-if (typeof(_writeMode) == 'undefined') {
- // This is for cases when the v8 engine is used other than the mongo shell, like map reduce.
- _writeMode = function() {
+if (typeof _writeMode === "undefined") {
+ // We ensure the _writeMode() function is always defined, in case the JavaScript engine is being
+ // used from someplace other than the mongo shell (e.g. map-reduce).
+ _writeMode = function _writeMode() {
return "commands";
};
}
-if (typeof(_readMode) == 'undefined') {
- // This is for cases when the v8 engine is used other than the mongo shell, like map reduce.
- _readMode = function() {
+if (typeof _readMode === "undefined") {
+ // We ensure the _readMode() function is always defined, in case the JavaScript engine is being
+ // used from someplace other than the mongo shell (e.g. map-reduce).
+ _readMode = function _readMode() {
return "legacy";
};
}
+if (typeof _shouldRetryWrites === 'undefined') {
+ // We ensure the _shouldRetryWrites() function is always defined, in case the JavaScript engine
+ // is being used from someplace other than the mongo shell (e.g. map-reduce).
+ _shouldRetryWrites = function _shouldRetryWrites() {
+ return false;
+ };
+}
+
shellPrintHelper = function(x) {
if (typeof(x) == "undefined") {
// Make sure that we have a db var before we use it