[SERVER-58442] Clear _startAfterInvalidate when the resume token is observed in DSCSCheckInvalidate Created: 12/Jul/21  Updated: 29/Oct/23  Resolved: 20/Jul/21

Status: Closed
Project: Core Server
Component/s: None
Affects Version/s: None
Fix Version/s: 5.1.0-rc0

Type: Bug Priority: Major - P3
Reporter: Bernard Gorman Assignee: Rishab Joshi (Inactive)
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Backwards Compatibility: Fully Compatible
Operating System: ALL
Sprint: Query Execution 2021-07-26
Participants:

 Description   

In SERVER-57792 we changed DSCSCheckInvalidate to throw the resume token up to DSCSEnsureResumeTokenPresent when starting after an invalidation event. However, when this happens we do not clear the value of the _startAfterInvalidate field; if the next thing the stream sees is another invalidating command, we will swallow it instead of invalidating the stream.



 Comments   
Comment by Vivian Ge (Inactive) [ 06/Oct/21 ]

Updating the fixversion since branching activities occurred yesterday. This ticket will be in rc0 when it’s been triggered. For more active release information, please keep an eye on #server-release. Thank you!

Comment by Githook User [ 20/Jul/21 ]

Author:

{'name': 'Rishab Joshi', 'email': 'rishab.joshi@mongodb.com', 'username': 'rishvin'}

Message: SERVER-58442 Clear _startAfterInvalidate when the resume token is observed in DSCSCheckInvalidate
Branch: master
https://github.com/mongodb/mongo/commit/9ad1a713dc69c677c2f637604bd04670f7506618

Comment by Bernard Gorman [ 12/Jul/21 ]

rishab.joshi: repro script:

function testSwallowInvalidate() {
  const testDB = db.getSiblingDB("test");
  const testColl = testDB.test;
  testColl.drop();
 
  // Start a change stream on 'testColl', then create and drop the collection.
  let csCursor = testColl.watch();
  testDB.createCollection("test");
  testColl.drop();
 
  // Wait until we see the invalidation, and store the associated resume token.
  assert.soon(() => {
      return csCursor.hasNext() && csCursor.next().operationType === "invalidate";
  });
  const invalidateResumeToken = csCursor.getResumeToken();
 
  // Recreate the collection and drop it again.
  testDB.createCollection("test");
  testColl.drop();
 
  // Start the stream after the first invalidate. We expect to see the new stream be invalidated
  // after the collection is dropped for the second time, but it never happens because we've
  // swallowed the invalidation internally.
  csCursor = testColl.watch([], {startAfter: invalidateResumeToken});
  assert.soon(() => {
      return csCursor.hasNext() && csCursor.next().operationType === "invalidate";
  });
}

This patch should fix the issue:

diff --git a/src/mongo/db/pipeline/document_source_change_stream_check_invalidate.cpp b/src/mongo/db/pipeline/document_source_change_stream_check_invalidate.cpp
index d86393e5e0..e386cc73bb 100644
--- a/src/mongo/db/pipeline/document_source_change_stream_check_invalidate.cpp
+++ b/src/mongo/db/pipeline/document_source_change_stream_check_invalidate.cpp
@@ -124,6 +124,11 @@ DocumentSource::GetNextResult DocumentSourceChangeStreamCheckInvalidate::doGetNe
     // indicating that the token is from an invalidate. This flag is necessary to disambiguate
     // the two tokens, and thus preserve a total ordering on the stream.
     if (isInvalidatingCommand(pExpCtx, operationType)) {
+        // Regardless of whether we generate an invalidation event or, in the case of startAfter,
+        // swallow it, we should clear the _startAfterInvalidate field once this block completes.
+        ON_BLOCK_EXIT([this] { _startAfterInvalidate.reset(); });
+
+        // Extract the resume token from the invalidating command and set the 'fromInvalidate' bit.
         auto resumeTokenData = ResumeToken::parse(doc[DSCS::kIdField].getDocument()).getData();
         resumeTokenData.fromInvalidate = ResumeTokenData::FromInvalidate::kFromInvalidate;
 
@@ -135,7 +140,6 @@ DocumentSource::GetNextResult DocumentSourceChangeStreamCheckInvalidate::doGetNe
         // token. We must re-generate this invalidate, since DSEnsureResumeTokenPresent needs to see
         // (and will take care of swallowing) the event which exactly matches the client's token.
         if (_startAfterInvalidate && resumeTokenData != _startAfterInvalidate) {
-            _startAfterInvalidate.reset();
             return nextInput;
         }

Generated at Thu Feb 08 05:44:32 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.