@@ -100,7 +100,7 @@ void VBucketDurabilityTest::storeSyncWrites(
100100
101101void VBucketDurabilityTest::simulateLocalAck (uint64_t seqno) {
102102 vbucket->setPersistenceSeqno (seqno);
103- vbucket->getActiveDM (). notifyLocalPersistence ();
103+ vbucket->notifyPersistenceToDurabilityMonitor ();
104104}
105105
106106void VBucketDurabilityTest::testAddPrepare (
@@ -1896,6 +1896,188 @@ TEST_P(VBucketDurabilityTest,
18961896 testConvertPassiveDMToActiveDMNoPrepares (vbucket_state_replica);
18971897}
18981898
1899+ // Test that conversion from ActiveDM to PassiveDM with in-flight trackedWrites
1900+ // calculated HPS correctly.
1901+ // MB-35332: During rebalance a node may go from being active to replica for
1902+ // a vBucket, resulting in ActiveDM -> PassiveDM conversion. If the ActiveDM
1903+ // had prepares which were already locally ack'd, after PassiveDM conversion
1904+ // the highPreparedSeqno iterator was in the wrong position; resulting in
1905+ // the highPreparedSeqno.lastWriteSeqno going backwards (breaking Monotonic
1906+ // invariant).
1907+ TEST_P (VBucketDurabilityTest, ConvertActiveDMToPassiveDMPreparedSyncWrites) {
1908+ // Setup: queue two Prepares into the ADM (needs >1 to expose the bug
1909+ // where HPS.iterator has incorrect position, resulting in attempting to
1910+ // set HPS.lastWriteSeqno to a lower value then it was (2 -> 1).
1911+ auto & adm = VBucketTestIntrospector::public_getActiveDM (*vbucket);
1912+ ASSERT_EQ (0 , adm.getNumTracked ());
1913+ const std::vector<SyncWriteSpec> seqnos{1 , 2 };
1914+ testAddPrepare (seqnos);
1915+ // checkForCommit will be called after every normal vBucket op and will
1916+ // set the HPS for us
1917+ adm.checkForCommit ();
1918+
1919+ // Test: Convert to PassiveDM (via dead as ns_server can do).
1920+ vbucket->setState (vbucket_state_dead);
1921+ vbucket->setState (vbucket_state_replica);
1922+
1923+ // Check: seqnos on newly-created PassiveDM.
1924+ auto & pdm = VBucketTestIntrospector::public_getPassiveDM (*vbucket);
1925+ EXPECT_EQ (2 , pdm.getHighPreparedSeqno ());
1926+ EXPECT_EQ (0 , pdm.getHighCompletedSeqno ());
1927+
1928+ // Test(2): Simulate a new snapshot being received (i.e. from a DcpConsumer)
1929+ // and the snapshot end being reached which triggers
1930+ // updateHighPreparedSeqno()
1931+ // - which shouldn't move HPS backwards.
1932+ auto key = makeStoredDocKey (" key3" );
1933+ auto pending = makePendingItem (key, " replica_value" s);
1934+ VBQueueItemCtx ctx;
1935+ using namespace cb ::durability;
1936+ ctx.durability = DurabilityItemCtx{
1937+ Requirements{Level::Majority, Timeout::Infinity ()}, cookie};
1938+ ASSERT_EQ (MutationStatus::WasClean,
1939+ public_processSet (*pending, 0 /* cas*/ , ctx));
1940+
1941+ pdm.notifySnapshotEndReceived (3 );
1942+ EXPECT_EQ (3 , pdm.getHighPreparedSeqno ());
1943+ EXPECT_EQ (0 , pdm.getHighCompletedSeqno ());
1944+
1945+ // Test(3): Now commit the prepared items.
1946+ auto resolutionCommit = PassiveDurabilityMonitor::Resolution::Commit;
1947+ pdm.completeSyncWrite (makeStoredDocKey (" key1" ), resolutionCommit, 1 );
1948+ EXPECT_EQ (1 , pdm.getHighCompletedSeqno ());
1949+ pdm.completeSyncWrite (makeStoredDocKey (" key2" ), resolutionCommit, 2 );
1950+ EXPECT_EQ (2 , pdm.getHighCompletedSeqno ());
1951+ pdm.completeSyncWrite (makeStoredDocKey (" key3" ), resolutionCommit, 3 );
1952+ EXPECT_EQ (3 , pdm.getHighCompletedSeqno ());
1953+ }
1954+
1955+ // Test that conversion from ActiveDM to PassiveDM with in-flight trackedWrites
1956+ // including at least one completed is handled correctly.
1957+ TEST_P (VBucketDurabilityTest, ConvertActiveDMToPassiveDMCompletedSyncWrites) {
1958+ // Setup: queue three Prepares into the ADM, then complete the seqno:1.
1959+ // (We want to end up with at least two SyncWrites in PDM - it only
1960+ // contains uncompleted SyncWrites (i.e. seqno 2 & 3).
1961+ auto & adm = VBucketTestIntrospector::public_getActiveDM (*vbucket);
1962+ ASSERT_EQ (0 , adm.getNumTracked ());
1963+ const std::vector<SyncWriteSpec> seqnos{1 , 2 , 3 };
1964+ testAddPrepare (seqnos);
1965+ ASSERT_EQ (seqnos.size (), adm.getNumTracked ());
1966+ // checkForCommit will be called after every normal vBucket op and will
1967+ // set the HPS for us
1968+ adm.checkForCommit ();
1969+
1970+ // Setup: Commit the first Prepare (so we can advance HCS to non-zero and
1971+ // test it below).
1972+ adm.seqnoAckReceived (replica1, 1 );
1973+ ASSERT_EQ (2 , adm.getNumTracked ());
1974+ ASSERT_EQ (3 , adm.getHighPreparedSeqno ());
1975+ ASSERT_EQ (1 , adm.getHighCompletedSeqno ());
1976+
1977+ // Test: Convert to PassiveDM (via dead as ns_server can do).
1978+ vbucket->setState (vbucket_state_dead);
1979+ vbucket->setState (vbucket_state_replica);
1980+
1981+ // Check: state on newly created PassiveDM.
1982+ auto & pdm = VBucketTestIntrospector::public_getPassiveDM (*vbucket);
1983+ EXPECT_EQ (2 , pdm.getNumTracked ());
1984+ EXPECT_EQ (3 , pdm.getHighPreparedSeqno ());
1985+ EXPECT_EQ (1 , pdm.getHighCompletedSeqno ());
1986+
1987+ // Test(2): Commit the remaining outstanding prepares.
1988+ auto resolutionCommit = PassiveDurabilityMonitor::Resolution::Commit;
1989+ pdm.completeSyncWrite (makeStoredDocKey (" key2" ), resolutionCommit, 2 );
1990+ EXPECT_EQ (2 , pdm.getHighCompletedSeqno ());
1991+ pdm.completeSyncWrite (makeStoredDocKey (" key3" ), resolutionCommit, 3 );
1992+ EXPECT_EQ (3 , pdm.getHighCompletedSeqno ());
1993+ EXPECT_EQ (0 , pdm.getNumTracked ());
1994+ }
1995+
1996+ /* *
1997+ * Check that converting from Replica to Active back to Replica correctly
1998+ * preserves completed SyncWrites in trackedWrites which cannot yet be removed
1999+ * (if persistMajority and not locally persisted).
2000+ *
2001+ * Scenario:
2002+ * Replica (PassiveDM):
2003+ * 1:prepare(persistToMajority)
2004+ * 2:prepare(majority)
2005+ * 3:commit(1) -> cannot locally remove 1 as not persisted yet.
2006+ * 4:commit(2) -> cannot locally remove 2 as seqno:1 not persisted yet
2007+ * (in-order commit).
2008+ * -> trackedWrites=[1,2]
2009+ * HPS=0
2010+ * HCS=2 (want HCS higher than the first element in the trackedWrites)
2011+ *
2012+ * Convert to ADM (null topology):
2013+ * -> trackedWrites=[1,2]
2014+ * HPS=0
2015+ * HCS=2
2016+ * (i.e. same as previous PassiveDM)
2017+ *
2018+ * Convert to PDM:
2019+ * State should be same as it was:
2020+ * -> trackedWrites=[1,2]
2021+ * HPS=0
2022+ * HCS=2
2023+ * notifyLocalPersistence -> can remove 1 and 2:
2024+ * -> trackedWrites=[]
2025+ * HPS=2
2026+ * HCS=2
2027+ */
2028+ TEST_P (EPVBucketDurabilityTest, ReplicaToActiveToReplica) {
2029+ // Setup: PassiveDM with
2030+ // 1:PRE(persistMajority), 2:PRE(majority), 3:COMMIT(1), 4:COMMIT(2)
2031+ vbucket->setState (vbucket_state_replica);
2032+ using namespace cb ::durability;
2033+ std::vector<SyncWriteSpec> seqnos{{1 , false , Level::PersistToMajority}, 2 };
2034+ testAddPrepare (seqnos);
2035+ auto & pdm = VBucketTestIntrospector::public_getPassiveDM (*vbucket);
2036+ pdm.completeSyncWrite (makeStoredDocKey (" key1" ),
2037+ PassiveDurabilityMonitor::Resolution::Commit,
2038+ 1 );
2039+ pdm.completeSyncWrite (makeStoredDocKey (" key2" ),
2040+ PassiveDurabilityMonitor::Resolution::Commit,
2041+ 2 );
2042+ pdm.notifySnapshotEndReceived (4 );
2043+
2044+ // Sanity: Check PassiveDM state as expected - HPS is still zero as haven't
2045+ // locally prepared the persistMajority, but globally that's been
2046+ // committed (HCS=1).
2047+ ASSERT_EQ (2 , pdm.getNumTracked ());
2048+ ASSERT_EQ (0 , pdm.getHighPreparedSeqno ());
2049+ ASSERT_EQ (2 , pdm.getHighCompletedSeqno ());
2050+
2051+ // Setup(2): Convert to ActiveDM (null topology).
2052+ vbucket->setState (vbucket_state_active);
2053+ auto & adm = VBucketTestIntrospector::public_getActiveDM (*vbucket);
2054+ EXPECT_EQ (2 , adm.getNumTracked ());
2055+ EXPECT_EQ (0 , adm.getHighPreparedSeqno ());
2056+ EXPECT_EQ (2 , adm.getHighCompletedSeqno ());
2057+
2058+ // Test: Convert back to PassiveDM.
2059+ vbucket->setState (vbucket_state_replica);
2060+ {
2061+ auto & pdm = VBucketTestIntrospector::public_getPassiveDM (*vbucket);
2062+ EXPECT_EQ (2 , pdm.getNumTracked ());
2063+ EXPECT_EQ (0 , pdm.getHighPreparedSeqno ());
2064+ EXPECT_EQ (2 , pdm.getHighCompletedSeqno ());
2065+
2066+ // Test(2): Check that notification of local persistence will remove
2067+ // the completed items and advance HPS.
2068+
2069+ // @todo MB-35366: This notifySnapshotEndReceived *shouldn't* be
2070+ // necessary; calling notifyPersistenceToDurabilityMonitor (via
2071+ // simulateLocalAck) should be sufficient to prepare.
2072+ pdm.notifySnapshotEndReceived (4 );
2073+ simulateLocalAck (4 );
2074+
2075+ EXPECT_EQ (0 , pdm.getNumTracked ());
2076+ EXPECT_EQ (2 , pdm.getHighPreparedSeqno ());
2077+ EXPECT_EQ (2 , pdm.getHighCompletedSeqno ());
2078+ }
2079+ }
2080+
18992081// Test that a double set_vb_state with identical state & topology is handled
19002082// correctly.
19012083// MB-35189: ns_server can send such set_vb_state messages, and in the
0 commit comments