diff options
author | Cheahuychou Mao <cheahuychou.mao@mongodb.com> | 2020-02-24 22:48:22 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-02-28 19:54:40 +0000 |
commit | 0219d8e3489f7934c3eda10a18ac70de81d1e7b4 (patch) | |
tree | 9b29a85adfdf1470b84f72e8fdd2f03ca667403d | |
parent | bd05da2372a83bdbe2efa1f488a893a9ea40530c (diff) | |
download | mongo-0219d8e3489f7934c3eda10a18ac70de81d1e7b4.tar.gz |
SERVER-9391 tailable cursors should throw an exception in DBClientCursor when a cursor is not found on the server
16 files changed, 96 insertions, 8 deletions
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml index d1ceab9830c..4912a169df9 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml @@ -29,6 +29,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml index 8101d1f00ce..11ee49f8abb 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml @@ -29,6 +29,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml index d9852dc4dc0..3a78d304f6b 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml @@ -29,6 +29,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml index 5ac171b9af9..46d2c6deab2 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml @@ -29,6 +29,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml index 0ae74afdd75..eeda0ca589f 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml @@ -162,6 +162,7 @@ selector: # SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern. - jstests/core/awaitdata_getmore_cmd.js - jstests/core/getmore_cmd_maxtimems.js + - jstests/core/invalidated_legacy_cursors.js - jstests/core/tailable_cursor_invalidation.js - jstests/core/tailable_getmore_batch_size.js diff --git a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml index 8b6e3ecdab3..0c0fe099f80 100644 --- a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml @@ -24,6 +24,7 @@ selector: # These test run commands using legacy queries, which are not supported on sessions. - jstests/core/comment_field.js - jstests/core/exhaust.js + - jstests/core/invalidated_legacy_cursors.js - jstests/core/validate_cmd_ns.js # TODO SERVER-31249: getLastError should not be affected by no-op retries. diff --git a/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml index f7956032b07..877425132e5 100644 --- a/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml @@ -10,6 +10,7 @@ selector: # These test run commands using legacy queries, which are not supported on sessions. - jstests/core/comment_field.js - jstests/core/exhaust.js + - jstests/core/invalidated_legacy_cursors.js - jstests/core/validate_cmd_ns.js # Unacknowledged writes prohibited in an explicit session. diff --git a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml index 49e12b91a40..aa0a3605ba3 100644 --- a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml @@ -31,6 +31,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml index 4f4ebba5e8c..bf04f752de4 100644 --- a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml @@ -31,6 +31,7 @@ selector: - jstests/core/geo_update_btree2.js # notablescan. - jstests/core/index_id_options.js # "local" database. - jstests/core/index9.js # "local" database. + - jstests/core/invalidated_legacy_cursors.js # capped collections. - jstests/core/queryoptimizera.js # "local" database. - jstests/core/rename*.js # renameCollection. - jstests/core/stages*.js # stageDebug. diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml index b3b6898667d..0e079adc52d 100644 --- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml @@ -195,6 +195,7 @@ selector: # SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern. - jstests/core/awaitdata_getmore_cmd.js - jstests/core/getmore_cmd_maxtimems.js + - jstests/core/invalidated_legacy_cursors.js - jstests/core/tailable_cursor_invalidation.js - jstests/core/tailable_getmore_batch_size.js diff --git a/jstests/core/invalidated_legacy_cursors.js b/jstests/core/invalidated_legacy_cursors.js new file mode 100644 index 00000000000..95cb1a0802f --- /dev/null +++ b/jstests/core/invalidated_legacy_cursors.js @@ -0,0 +1,58 @@ +/* + * Test that all DBClientCursor cursor types throw an exception when the server returns + * CursorNotFound. + * @tags: [requires_getmore, requires_non_retryable_commands, assumes_balancer_off] + */ +(function() { +'use strict'; + +const testDB = db.getSiblingDB("invalidated_legacy_cursors"); +const coll = testDB.test; +const nDocs = 10; +const batchSize = 2; // The minimum DBClientCursor batch size is 2. + +function setupCollection(isCapped) { + coll.drop(); + if (isCapped) { + assert.commandWorked(testDB.createCollection(coll.getName(), {capped: true, size: 4096})); + } + const bulk = coll.initializeUnorderedBulkOp(); + for (let i = 0; i < nDocs; ++i) { + bulk.insert({_id: i, x: i}); + } + assert.commandWorked(bulk.execute()); + assert.commandWorked(coll.createIndex({x: 1})); +} + +function testLegacyCursorThrowsCursorNotFound(isTailable) { + coll.getMongo().forceReadMode("legacy"); + setupCollection(isTailable); + + // Create a cursor and consume the docs in the first batch. + let cursor = coll.find().batchSize(batchSize); + if (isTailable) { + cursor = cursor.tailable(); + } + for (let i = 0; i < batchSize; i++) { + cursor.next(); + } + + // Kill the cursor and assert that the cursor throws CursorNotFound on the first next() call. + // Use killCursors instead of cursor.close() since we still want to send getMore requests + // through the existing cursor. + assert.commandWorked( + testDB.runCommand({killCursors: coll.getName(), cursors: [cursor.getId()]})); + const error = assert.throws(() => cursor.next()); + assert.eq(error.code, ErrorCodes.CursorNotFound); + + // Check the state of the cursor. + assert(!cursor.hasNext()); + assert.eq(0, cursor.getId()); + assert.throws(() => cursor.next()); +} + +testLegacyCursorThrowsCursorNotFound(false); +if (!jsTest.options().mixedBinVersions) { + testLegacyCursorThrowsCursorNotFound(true); +} +}()); diff --git a/src/mongo/client/dbclient_cursor.cpp b/src/mongo/client/dbclient_cursor.cpp index 7af7d314769..77d5c802eb7 100644 --- a/src/mongo/client/dbclient_cursor.cpp +++ b/src/mongo/client/dbclient_cursor.cpp @@ -359,13 +359,11 @@ void DBClientCursor::dataReceived(const Message& reply, bool& retry, string& hos // cursor id no longer valid at the server. invariant(qr.getCursorId() == 0); - if (!(opts & QueryOption_CursorTailable)) { - uasserted(ErrorCodes::CursorNotFound, - str::stream() << "cursor id " << cursorId << " didn't exist on server."); - } - - // 0 indicates no longer valid (dead) + // 0 indicates no longer valid (dead). cursorId = 0; + + uasserted(ErrorCodes::CursorNotFound, + str::stream() << "cursor id " << cursorId << " didn't exist on server."); } if (cursorId == 0 || !(opts & QueryOption_CursorTailable)) { diff --git a/src/mongo/scripting/mozjs/cursor.cpp b/src/mongo/scripting/mozjs/cursor.cpp index dc6d65dfb1d..f90bcb924db 100644 --- a/src/mongo/scripting/mozjs/cursor.cpp +++ b/src/mongo/scripting/mozjs/cursor.cpp @@ -41,11 +41,12 @@ namespace mongo { namespace mozjs { -const JSFunctionSpec CursorInfo::methods[7] = { +const JSFunctionSpec CursorInfo::methods[8] = { MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(close, CursorInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(hasNext, CursorInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(next, CursorInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(objsLeftInBatch, CursorInfo), + MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getId, CursorInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(readOnly, CursorInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isClosed, CursorInfo), JS_FS_END, @@ -119,6 +120,17 @@ void CursorInfo::Functions::readOnly::call(JSContext* cx, JS::CallArgs args) { args.rval().set(args.thisv()); } +void CursorInfo::Functions::getId::call(JSContext* cx, JS::CallArgs args) { + auto cursor = getCursor(args); + + if (!cursor) { + ValueReader(cx, args.rval()).fromInt64(0); + return; + } + + ValueReader(cx, args.rval()).fromInt64(cursor->getCursorId()); +} + void CursorInfo::Functions::close::call(JSContext* cx, JS::CallArgs args) { auto cursor = getCursor(args); diff --git a/src/mongo/scripting/mozjs/cursor.h b/src/mongo/scripting/mozjs/cursor.h index 110653747e7..6aa610f80b6 100644 --- a/src/mongo/scripting/mozjs/cursor.h +++ b/src/mongo/scripting/mozjs/cursor.h @@ -51,9 +51,10 @@ struct CursorInfo : public BaseInfo { MONGO_DECLARE_JS_FUNCTION(next); MONGO_DECLARE_JS_FUNCTION(objsLeftInBatch); MONGO_DECLARE_JS_FUNCTION(readOnly); + MONGO_DECLARE_JS_FUNCTION(getId); }; - static const JSFunctionSpec methods[7]; + static const JSFunctionSpec methods[8]; static const char* const className; static const unsigned classFlags = JSCLASS_HAS_PRIVATE; diff --git a/src/mongo/shell/query.js b/src/mongo/shell/query.js index bd451166e9d..cbdbeda2929 100644 --- a/src/mongo/shell/query.js +++ b/src/mongo/shell/query.js @@ -328,6 +328,11 @@ DBQuery.prototype.readOnly = function() { return this; }; +DBQuery.prototype.getId = function() { + this._exec(); + return this._cursor.getId(); +}; + DBQuery.prototype.toArray = function() { if (this._arr) return this._arr; @@ -876,6 +881,9 @@ DBCommandCursor.prototype.objsLeftInBatch = function() { return this._cursor.objsLeftInBatch(); } }; +DBCommandCursor.prototype.getId = function() { + return this._cursorid; +}; DBCommandCursor.prototype.getResumeToken = function() { // Return the most recent recorded resume token, if such a token exists. return this._resumeToken; diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js index dcfface166e..9ae58f3910c 100644 --- a/src/mongo/shell/utils.js +++ b/src/mongo/shell/utils.js @@ -301,6 +301,7 @@ jsTestOptions = function() { // Note: does not support the array version mongosBinVersion: TestData.mongosBinVersion || "", shardMixedBinVersions: TestData.shardMixedBinVersions || false, + mixedBinVersions: TestData.mixedBinVersions || false, networkMessageCompressors: TestData.networkMessageCompressors, replSetFeatureCompatibilityVersion: TestData.replSetFeatureCompatibilityVersion, skipRetryOnNetworkError: TestData.skipRetryOnNetworkError, |