diff options
-rw-r--r-- | jstests/change_streams/error_label.js | 33 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 2 | ||||
-rw-r--r-- | src/mongo/db/handle_request_response.cpp | 29 |
3 files changed, 50 insertions, 14 deletions
diff --git a/jstests/change_streams/error_label.js b/jstests/change_streams/error_label.js new file mode 100644 index 00000000000..1c9a00db356 --- /dev/null +++ b/jstests/change_streams/error_label.js @@ -0,0 +1,33 @@ +/** + * Test that an erroneous Change Stream pipeline responds with an error that includes the + * "NonResumableChangeStreamError" label. + */ + +(function() { + "use strict"; + + load("jstests/libs/collection_drop_recreate.js"); // For assertDropAndRecreateCollection. + + // Drop and recreate the collections to be used in this set of tests. + const coll = assertDropAndRecreateCollection(db, "change_stream_error_label"); + + // Attaching a projection to the Change Stream that filters out the resume token (stored in the + // _id field) guarantees a ChangeStreamFatalError as soon as we get the first change. + const changeStream = coll.watch([{$project: {_id: 0}}], {batchSize: 1}); + assert.commandWorked(coll.insert({a: 1})); + + const err = assert.throws(function() { + // Call hasNext() until it throws an error or unexpectedly returns true. We need the + // assert.soon() to keep trying here, because the above insert command isn't immediately + // observable to the change stream in sharded configurations. + assert.soon(function() { + return changeStream.hasNext(); + }); + }); + + // The hasNext() sends a getMore command, which should generate a ChangeStreamFatalError reply + // that includes the NonResumableChangeStreamError errorLabel. + assert.commandFailedWithCode(err, ErrorCodes.ChangeStreamFatalError); + assert("errorLabels" in err, err); + assert.contains("NonResumableChangeStreamError", err.errorLabels, err); +}());
\ No newline at end of file diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index fe682f8eb62..837d4090b37 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -357,4 +357,4 @@ error_class("SnapshotError", ["SnapshotTooOld", "SnapshotUnavailable", "StaleChu error_class("VoteAbortError", ["NoSuchTransaction", "TransactionTooOld"]) -error_class("NonRetryableChangeStreamError", ["ChangeStreamFatalError", "ChangeStreamHistoryLost"]) +error_class("NonResumableChangeStreamError", ["ChangeStreamFatalError", "ChangeStreamHistoryLost"]) diff --git a/src/mongo/db/handle_request_response.cpp b/src/mongo/db/handle_request_response.cpp index 062075b82db..6628c60e8bc 100644 --- a/src/mongo/db/handle_request_response.cpp +++ b/src/mongo/db/handle_request_response.cpp @@ -36,23 +36,26 @@ BSONObj getErrorLabels(const OperationSessionInfoFromClient& sessionOptions, const std::string& commandName, ErrorCodes::Error code, bool hasWriteConcernError) { + BSONArrayBuilder labelArray; - // By specifying "autocommit", the user indicates they want to run a transaction. - // It is always false when set. - if (!sessionOptions.getAutocommit()) { - return {}; + // Note that we only apply the TransientTxnError label if the "autocommit" field is present in + // the session options. When present, "autocommit" will always be false, so we don't check its + // value. + if (sessionOptions.getAutocommit() && + isTransientTransactionError(code, + hasWriteConcernError, + commandName == "commitTransaction" || + commandName == "coordinateCommitTransaction")) { + // An error code for which isTransientTransactionError() is true indicates a transaction + // failure with no persistent side effects. + labelArray << txn::TransientTxnErrorFieldName; } - // The errors that indicate the transaction fails without any persistent side-effect. - bool isTransient = isTransientTransactionError( - code, - hasWriteConcernError, - commandName == "commitTransaction" || commandName == "coordinateCommitTransaction"); - - if (isTransient) { - return BSON("errorLabels" << BSON_ARRAY(txn::TransientTxnErrorFieldName)); + if (ErrorCodes::isNonResumableChangeStreamError(code)) { + labelArray << "NonResumableChangeStreamError"; } - return {}; + + return (labelArray.arrSize() > 0) ? BSON("errorLabels" << labelArray.arr()) : BSONObj(); } } // namespace mongo |