diff options
-rw-r--r-- | buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml | 58 | ||||
-rw-r--r-- | etc/evergreen.yml | 23 | ||||
-rw-r--r-- | jstests/libs/override_methods/auto_retry_on_network_error.js | 5 | ||||
-rw-r--r-- | jstests/libs/override_methods/retry_writes_at_least_once.js | 81 | ||||
-rw-r--r-- | jstests/libs/retryable_writes_util.js | 16 | ||||
-rw-r--r-- | src/mongo/shell/session.js | 8 | ||||
-rw-r--r-- | src/mongo/shell/utils.js | 1 |
7 files changed, 189 insertions, 3 deletions
diff --git a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml index 6724df8d936..b91af9d9f45 100644 --- a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml @@ -10,14 +10,72 @@ selector: - jstests/core/opcounters_write_cmd.js - jstests/core/read_after_optime.js + # No-op retries are not ignored by top, the profiler, or opcount. + - jstests/core/operation_latency_histogram.js + - jstests/core/profile2.js + - jstests/core/profile3.js + - jstests/core/profile_findandmodify.js + - jstests/core/top.js + - jstests/core/views/views_stats.js + + # TODO SERVER-31249: getLastError should not be affected by no-op retries. + - jstests/core/bulk_legacy_enforce_gle.js + + # TODO SERVER-30532: Add lastErrorObject.upserted to findAndModify no-op retry response. + - jstests/core/find_and_modify_server6582.js + + # TODO SERVER-31242: findAndModify no-op retry should respect the fields option. + - jstests/core/crud_api.js + - jstests/core/find_and_modify2.js + - jstests/core/find_and_modify_server6865.js + + # TODO SERVER-31243: No-op retry of a findAndModify upsert of a new document should return null + # if new=false. + - jstests/core/find_and_modify.js + - jstests/core/find_and_modify4.js + - jstests/core/find_and_modify_empty_coll.js + - jstests/core/find_and_modify_empty_update.js + - jstests/core/find_and_modify_server6226.js + + # TODO SERVER-31245: Inserts to "system.indexes" bypass the check for retryability. + - jstests/core/batch_write_command_insert.js + + # TODO SERVER-31328: Investigate enabling these workloads once performance of retrying completed + # inserts and deletes has improved. + # + # These tests time out because of too many retryable operations: + - jstests/core/bulk_insert.js + - jstests/core/insert1.js + - jstests/core/remove6.js + - jstests/core/removea.js + + # TODO SERVER-29843 / SERVER-31328: Investigate enabling these workloads once performance of + # retrying completed writes has improved. + # + # These tests time out on slower machines because of too many retryable operations: + - jstests/core/explain3.js + - jstests/core/geo2.js + - jstests/core/geo3.js + - jstests/core/geo_polygon3.js + - jstests/core/geo_s2explain.js + - jstests/core/geo_s2sparse.js + - jstests/core/geo_s2twofields.js + - jstests/core/mr1.js + - jstests/core/queryoptimizer3.js + - jstests/core/remove9.js + - jstests/core/removeb.js + - jstests/core/splitvector.js + executor: config: shell_options: eval: >- testingReplication = true; load("jstests/libs/override_methods/enable_sessions.js"); + load("jstests/libs/override_methods/retry_writes_at_least_once.js"); global_vars: TestData: + alwaysInjectTransactionNumber: true sessionOptions: retryWrites: true readMode: commands diff --git a/etc/evergreen.yml b/etc/evergreen.yml index 05331d63a3c..c3e71e8542d 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -3989,6 +3989,17 @@ tasks: run_multiple_jobs: true - <<: *task_template + name: retryable_writes_jscore_passthrough + depends_on: + - name: jsCore + commands: + - func: "do setup" + - func: "run tests" + vars: + resmoke_args: --suites=retryable_writes_jscore_passthrough --storageEngine=mmapv1 + run_multiple_jobs: true + +- <<: *task_template name: retryable_writes_jscore_passthrough_WT depends_on: - name: jsCore_WT @@ -4890,6 +4901,7 @@ buildvariants: - name: replica_sets_resync_static_jscore_passthrough - name: replica_sets_resync_static_jscore_passthrough_WT - name: replica_sets_pv0 + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: master_slave - name: master_slave_WT @@ -6167,6 +6179,9 @@ buildvariants: - name: replica_sets_kill_secondaries_jscore_passthrough_WT distros: - windows-64-vs2015-large + - name: retryable_writes_jscore_passthrough + distros: + - windows-64-vs2015-large - name: retryable_writes_jscore_passthrough_WT distros: - windows-64-vs2015-large @@ -6805,6 +6820,7 @@ buildvariants: - name: replica_sets_jscore_passthrough - name: replica_sets_jscore_passthrough_WT - name: replica_sets_kill_secondaries_jscore_passthrough_WT + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: serial_run - name: serial_run_WT @@ -7195,6 +7211,9 @@ buildvariants: - name: replica_sets_kill_secondaries_jscore_passthrough_WT distros: - rhel62-large + - name: retryable_writes_jscore_passthrough + distros: + - rhel62-large - name: retryable_writes_jscore_passthrough_WT distros: - rhel62-large @@ -7381,6 +7400,7 @@ buildvariants: - name: replica_sets_resync_static_jscore_passthrough - name: replica_sets_resync_static_jscore_passthrough_WT - name: replica_sets_kill_secondaries_jscore_passthrough_WT + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: sasl - name: session_jscore_passthrough_WT @@ -9449,6 +9469,7 @@ buildvariants: - name: replica_sets_auth - name: replica_sets_pv0 - name: replica_sets_jscore_passthrough + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: rlp - name: sasl @@ -9930,6 +9951,7 @@ buildvariants: - name: replica_sets_resync_static_jscore_passthrough - name: replica_sets_resync_static_jscore_passthrough_WT - name: replica_sets_kill_secondaries_jscore_passthrough_WT + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: sasl - name: session_jscore_passthrough_WT @@ -10137,6 +10159,7 @@ buildvariants: - name: replica_sets_resync_static_jscore_passthrough - name: replica_sets_resync_static_jscore_passthrough_WT - name: replica_sets_kill_secondaries_jscore_passthrough_WT + - name: retryable_writes_jscore_passthrough - name: retryable_writes_jscore_passthrough_WT - name: sasl - name: session_jscore_passthrough_WT diff --git a/jstests/libs/override_methods/auto_retry_on_network_error.js b/jstests/libs/override_methods/auto_retry_on_network_error.js index 9509eb372da..3386c468428 100644 --- a/jstests/libs/override_methods/auto_retry_on_network_error.js +++ b/jstests/libs/override_methods/auto_retry_on_network_error.js @@ -11,8 +11,7 @@ (function() { "use strict"; - const retryableWriteCommands = - new Set(["delete", "findandmodify", "findAndModify", "insert", "update"]); + load("jstests/libs/retryable_writes_util.js"); // Store a session to access ServerSession#canRetryWrites. let _serverSession; @@ -48,7 +47,7 @@ cmdName = Object.keys(cmdObj)[0]; } - const isRetryableWriteCmd = retryableWriteCommands.has(cmdName); + const isRetryableWriteCmd = RetryableWritesUtil.isRetryableWriteCmdName(cmdName); const canRetryWrites = _serverSession.canRetryWrites(cmdObj); let numRetries = !jsTest.options().skipRetryOnNetworkError ? 1 : 0; diff --git a/jstests/libs/override_methods/retry_writes_at_least_once.js b/jstests/libs/override_methods/retry_writes_at_least_once.js new file mode 100644 index 00000000000..f0562c958d1 --- /dev/null +++ b/jstests/libs/override_methods/retry_writes_at_least_once.js @@ -0,0 +1,81 @@ +/** + * Overrides Mongo.prototype.runCommand and Mongo.prototype.runCommandWithMetadata to retry all + * retryable writes at least once, randomly more than that, regardless of the outcome of the + * command. Returns the result of the latest attempt. + */ +(function() { + "use strict"; + + load("jstests/libs/retryable_writes_util.js"); + + Random.setRandomSeed(); + + const kExtraRetryProbability = 0.2; + + // Store a session to access ServerSession#canRetryWrites. + let _serverSession; + + const mongoRunCommandOriginal = Mongo.prototype.runCommand; + const mongoRunCommandWithMetadataOriginal = Mongo.prototype.runCommandWithMetadata; + + Mongo.prototype.runCommand = function runCommand(dbName, cmdObj, options) { + if (typeof _serverSession === "undefined") { + _serverSession = this.startSession()._serverSession; + } + + return runWithRetries(this, cmdObj, mongoRunCommandOriginal, arguments); + }; + + Mongo.prototype.runCommandWithMetadata = function runCommandWithMetadata( + dbName, metadata, cmdObj) { + if (typeof _serverSession === "undefined") { + _serverSession = this.startSession()._serverSession; + } + + return runWithRetries(this, cmdObj, mongoRunCommandWithMetadataOriginal, arguments); + }; + + function runWithRetries(mongo, cmdObj, clientFunction, clientFunctionArguments) { + let 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. + if (cmdName === "query" || cmdName === "$query") { + cmdObj = cmdObj[cmdName]; + cmdName = Object.keys(cmdObj)[0]; + } + + const isRetryableWriteCmd = RetryableWritesUtil.isRetryableWriteCmdName(cmdName); + const canRetryWrites = _serverSession.canRetryWrites(cmdObj); + + let res = clientFunction.apply(mongo, clientFunctionArguments); + + if (isRetryableWriteCmd && canRetryWrites) { + let retryAttempt = 1; + do { + print("*** Retry attempt: " + retryAttempt + ", for command: " + cmdName + + " with txnNumber: " + tojson(cmdObj.txnNumber) + ", and lsid: " + + tojson(cmdObj.lsid)); + ++retryAttempt; + res = clientFunction.apply(mongo, clientFunctionArguments); + } while (Random.rand() <= kExtraRetryProbability); + } + + return res; + } + + const startParallelShellOriginal = startParallelShell; + + startParallelShell = function(jsCode, port, noConnect) { + let newCode; + const overridesFile = "jstests/libs/override_methods/retry_writes_at_least_once.js"; + if (typeof(jsCode) === "function") { + // Load the override file and immediately invoke the supplied function. + newCode = `load("${overridesFile}"); (${jsCode})();`; + } else { + newCode = `load("${overridesFile}"); ${jsCode};`; + } + + return startParallelShellOriginal(newCode, port, noConnect); + }; +})(); diff --git a/jstests/libs/retryable_writes_util.js b/jstests/libs/retryable_writes_util.js new file mode 100644 index 00000000000..ba3cd37afe0 --- /dev/null +++ b/jstests/libs/retryable_writes_util.js @@ -0,0 +1,16 @@ +/** + * Utilities for testing retryable writes. + */ +var RetryableWritesUtil = (function() { + const retryableWriteCommands = + new Set(["delete", "findandmodify", "findAndModify", "insert", "update"]); + + /** + * Returns true if the command name is that of a retryable write command. + */ + function isRetryableWriteCmdName(cmdName) { + return retryableWriteCommands.has(cmdName); + } + + return {isRetryableWriteCmdName: isRetryableWriteCmdName}; +})(); diff --git a/src/mongo/shell/session.js b/src/mongo/shell/session.js index 2df6468c729..9b55a26051c 100644 --- a/src/mongo/shell/session.js +++ b/src/mongo/shell/session.js @@ -70,6 +70,7 @@ var { function SessionAwareClient(client) { const kWireVersionSupportingLogicalSession = 6; const kWireVersionSupportingCausalConsistency = 6; + const kWireVersionSupportingRetryableWrites = 6; this.getReadPreference = function getReadPreference(driverSession) { const sessionOptions = driverSession.getOptions(); @@ -180,6 +181,13 @@ var { cmdObj = injectAfterClusterTime(cmdObj, driverSession._operationTime); } + if (jsTest.options().alwaysInjectTransactionNumber && + serverSupports(kWireVersionSupportingRetryableWrites) && + driverSession.getOptions().shouldRetryWrites() && + driverSession._serverSession.canRetryWrites(cmdObj)) { + cmdObj = driverSession._serverSession.assignTransactionNumber(cmdObj); + } + return cmdObj; } diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js index 63b41528e49..7bdc79411fc 100644 --- a/src/mongo/shell/utils.js +++ b/src/mongo/shell/utils.js @@ -257,6 +257,7 @@ jsTestOptions = function() { TestData.skipCheckingUUIDsConsistentAcrossCluster || false, jsonSchemaTestFile: TestData.jsonSchemaTestFile, excludedDBsFromDBHash: TestData.excludedDBsFromDBHash, + alwaysInjectTransactionNumber: TestData.alwaysInjectTransactionNumber, }); } return _jsTestOptions; |