@@ -5201,6 +5201,146 @@ TEST_P(SingleThreadedActiveStreamTest,
52015201 EXPECT_EQ (DcpResponse::Event::Mutation, readyQ.back ()->getEvent ());
52025202}
52035203
5204+ TEST_P (SingleThreadedActiveStreamTest, MB_58961) {
5205+ // No OSOBackfill in Ephemeral
5206+ if (ephemeral ()) {
5207+ GTEST_SKIP ();
5208+ }
5209+
5210+ // Create a collection
5211+ CollectionsManifest manifest;
5212+ const auto & cFruit = CollectionEntry::fruit;
5213+ manifest.add (
5214+ cFruit, cb::NoExpiryLimit, true /* history*/ , ScopeEntry::defaultS);
5215+ auto & vb = *store->getVBucket (vbid);
5216+ vb.updateFromManifest (Collections::Manifest{std::string{manifest}});
5217+ ASSERT_EQ (1 , vb.getHighSeqno ());
5218+
5219+ // seqno:2 in cFruit
5220+ const std::string value (" value" );
5221+ store_item (vbid, makeStoredDocKey (" keyF" , cFruit), value);
5222+ ASSERT_EQ (2 , vb.getHighSeqno ());
5223+
5224+ // Add some data to the default collection
5225+ const auto & cDefault = CollectionEntry::defaultC;
5226+ store_item (vbid, makeStoredDocKey (" keyD" , cDefault), value);
5227+ ASSERT_EQ (3 , vb.getHighSeqno ());
5228+
5229+ // [e:1 cs:1 se:1 m(keyF):2 m(keyD):3)
5230+
5231+ // Ensure new stream will backfill
5232+ stream->public_getOutstandingItems (vb);
5233+ removeCheckpoint (vb);
5234+
5235+ // [e:4 cs:4)
5236+
5237+ // Ensure OSOBackfill is triggered
5238+ engine->getConfiguration ().setDcpOsoBackfill (" enabled" );
5239+ producer->setOutOfOrderSnapshots (OutOfOrderSnapshots::YesWithSeqnoAdvanced);
5240+
5241+ // Stream filters on cFruit
5242+ recreateStream (
5243+ vb,
5244+ true ,
5245+ fmt::format (R"( {{"collections":["{:x}"]}})" , uint32_t (cFruit.uid )));
5246+ ASSERT_TRUE (stream);
5247+ // Pushed to backfill
5248+ ASSERT_TRUE (stream->isBackfilling ());
5249+
5250+ // [e:4 cs:4)
5251+ // ^
5252+
5253+ auto & readyQ = stream->public_readyQ ();
5254+ ASSERT_EQ (0 , readyQ.size ());
5255+
5256+ // Run the OSO backfill
5257+ runBackfill (); // push markers and data
5258+ ASSERT_EQ (5 , readyQ.size ());
5259+
5260+ auto resp = stream->public_nextQueuedItem (*producer);
5261+ EXPECT_EQ (DcpResponse::Event::OSOSnapshot, resp->getEvent ());
5262+
5263+ resp = stream->public_nextQueuedItem (*producer);
5264+ EXPECT_EQ (DcpResponse::Event::SystemEvent, resp->getEvent ());
5265+ EXPECT_EQ (1 , resp->getBySeqno ());
5266+
5267+ resp = stream->public_nextQueuedItem (*producer);
5268+ EXPECT_EQ (DcpResponse::Event::Mutation, resp->getEvent ());
5269+ EXPECT_EQ (2 , resp->getBySeqno ());
5270+
5271+ // Note: seqno:3 is in cDefault, filtered out, SeqnoAdvance(3) sent
5272+ resp = stream->public_nextQueuedItem (*producer);
5273+ EXPECT_EQ (DcpResponse::Event::SeqnoAdvanced, resp->getEvent ());
5274+ EXPECT_EQ (3 , resp->getBySeqno ());
5275+
5276+ resp = stream->public_nextQueuedItem (*producer);
5277+ EXPECT_EQ (DcpResponse::Event::OSOSnapshot, resp->getEvent ());
5278+
5279+ EXPECT_EQ (0 , stream->getLastSentSnapEndSeqno ());
5280+ EXPECT_EQ (2 , stream->getLastBackfilledSeqno ());
5281+ EXPECT_EQ (3 , stream->getLastSentSeqno ());
5282+ EXPECT_EQ (3 , stream->getLastReadSeqno ());
5283+ EXPECT_EQ (4 , stream->getCurChkSeqno ());
5284+
5285+ ASSERT_FALSE (stream->public_nextQueuedItem (*producer));
5286+ GMockDcpMsgProducers producers;
5287+ EXPECT_EQ (cb::engine_errc::would_block, producer->step (producers));
5288+ ASSERT_TRUE (stream->isInMemory ());
5289+
5290+ // [e:4 cs:4)
5291+ // ^
5292+
5293+ store_item (vbid, makeStoredDocKey (" keyD4" , cDefault), value);
5294+ ASSERT_EQ (4 , vb.getHighSeqno ());
5295+ store_item (vbid, makeStoredDocKey (" keyD5" , cDefault), value);
5296+ ASSERT_EQ (5 , vb.getHighSeqno ());
5297+
5298+ // [e:4 cs:4 m(keyD4):4 m(keyD5):5)
5299+ // ^
5300+
5301+ // Move inMemory stream.
5302+ // Note: Before the fix we do move the cursor but we miss to advance the
5303+ // stream
5304+ ASSERT_EQ (0 , readyQ.size ());
5305+ runCheckpointProcessor (*producer, producers);
5306+ // Note: We don't send any snapshot when all items are filtrered out
5307+ EXPECT_EQ (0 , readyQ.size ());
5308+
5309+ // [e:4 cs:4 m(keyD4):4 m(keyD5):5)
5310+ // ^
5311+
5312+ EXPECT_EQ (0 , stream->getLastSentSnapEndSeqno ());
5313+ EXPECT_EQ (2 , stream->getLastBackfilledSeqno ());
5314+ EXPECT_EQ (3 , stream->getLastSentSeqno ());
5315+ EXPECT_EQ (5 , stream->getLastReadSeqno ()); // Before the fix: 3
5316+ EXPECT_EQ (5 , stream->getCurChkSeqno ());
5317+
5318+ // CursorDrop + backfill
5319+ ASSERT_TRUE (stream->handleSlowStream ());
5320+ ASSERT_TRUE (stream->isInMemory ());
5321+
5322+ // [e:4 cs:4 m(keyD4):4 m(keyD5):5)
5323+ // x
5324+
5325+ // Before the fix for MB-58961 this step triggers:
5326+ //
5327+ // libc++abi: terminating due to uncaught exception of type
5328+ // boost::exception_detail::error_info_injector<std::logic_error>:Monotonic<y>
5329+ // (ActiveStream(test_producer->test_consumer (vb:0))::curChkSeqno)
5330+ // invariant failed: new value (4) breaks invariant on current value (5)
5331+ //
5332+ // The reason for the failure in MB-58961 is that we miss to update
5333+ // AS::lastReadSeqno when we process the "empty-by-filter" snapshot before
5334+ // CursorDrop.
5335+ // By that:
5336+ // (a) lastReadSeqno stays at 3
5337+ // (b) In the subsequent AS::scheduleBackfill(lastReadSeqno:3) call we
5338+ // re-register the cursor at cs:4 and we try to reset curChkSeqno (5) by
5339+ // (4).
5340+ EXPECT_EQ (cb::engine_errc::would_block, producer->step (producers));
5341+ EXPECT_FALSE (stream->isBackfilling ());
5342+ }
5343+
52045344INSTANTIATE_TEST_SUITE_P (AllBucketTypes,
52055345 SingleThreadedActiveStreamTest,
52065346 STParameterizedBucketTest::allConfigValues (),
0 commit comments