summaryrefslogtreecommitdiff
path: root/jstests/concurrency/fsm_workload_helpers
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2018-04-16 19:27:18 -0400
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2018-04-16 19:27:18 -0400
commit239f4fac258a3b973b3cb3187d2b175f757f84df (patch)
tree21ec075d8f14696bfa37a5236df1527aadd0227a /jstests/concurrency/fsm_workload_helpers
parent9a27a0fd9668231601d4d6cdb324eece306b91d1 (diff)
downloadmongo-239f4fac258a3b973b3cb3187d2b175f757f84df.tar.gz
SERVER-34293 Add test for atomicity and isolation of transactions.
Also adds a helper function for running a function inside of a transaction and automatically retrying until it either succeeds or the server returns a non-WriteConflict error response.
Diffstat (limited to 'jstests/concurrency/fsm_workload_helpers')
-rw-r--r--jstests/concurrency/fsm_workload_helpers/auto_retry_transaction.js68
1 files changed, 68 insertions, 0 deletions
diff --git a/jstests/concurrency/fsm_workload_helpers/auto_retry_transaction.js b/jstests/concurrency/fsm_workload_helpers/auto_retry_transaction.js
new file mode 100644
index 00000000000..f98e7d796e9
--- /dev/null
+++ b/jstests/concurrency/fsm_workload_helpers/auto_retry_transaction.js
@@ -0,0 +1,68 @@
+'use strict';
+
+var {withTxnAndAutoRetryOnWriteConflict} = (function() {
+
+ /**
+ * Calls 'func' with the print() function overridden to be a no-op.
+ *
+ * This function is useful for silencing JavaScript backtraces that would otherwise be logged
+ * from doassert() being called, even when the JavaScript exception is ultimately caught and
+ * handled.
+ */
+ function quietly(func) {
+ const printOriginal = print;
+ try {
+ print = Function.prototype;
+ func();
+ } finally {
+ print = printOriginal;
+ }
+ }
+
+ /**
+ * Runs 'func' inside of a transaction started with 'txnOptions', and automatically retries
+ * until it either succeeds or the server returns a non-WriteConflict error response.
+ *
+ * The caller should take care to ensure 'func' doesn't modify any captured variables in a
+ * speculative fashion where calling it multiple times would lead to unintended behavior. The
+ * transaction started by the withTxnAndAutoRetryOnWriteConflict() function is only known to
+ * have committed after the withTxnAndAutoRetryOnWriteConflict() function returns.
+ */
+ function withTxnAndAutoRetryOnWriteConflict(
+ session, func, {txnOptions: txnOptions = {readConcern: {level: 'snapshot'}}} = {}) {
+ let hasWriteConflict;
+
+ do {
+ session.startTransaction(txnOptions);
+ hasWriteConflict = false;
+
+ try {
+ func();
+
+ // commitTransaction() calls assert.commandWorked(), which may fail with a
+ // WriteConflict error response. We therefore suppress its doassert() output.
+ quietly(() => session.commitTransaction());
+ } catch (e) {
+ try {
+ // abortTransaction() calls assert.commandWorked(), which may fail with a
+ // WriteConflict error response. We therefore suppress its doassert() output.
+ quietly(() => session.abortTransaction());
+ } catch (e) {
+ // We ignore the error from abortTransaction() because the transaction may have
+ // implicitly been aborted by the server already and will therefore return a
+ // NoSuchTransaction error response. We need to call abortTransaction() in order
+ // to update the mongo shell's state such that it agrees no transaction is
+ // currently in progress on this session.
+ }
+
+ if (e.code !== ErrorCodes.WriteConflict) {
+ throw e;
+ }
+
+ hasWriteConflict = true;
+ }
+ } while (hasWriteConflict);
+ }
+
+ return {withTxnAndAutoRetryOnWriteConflict};
+})();