3636#include < bmqp_blobpoolutil.h>
3737#include < bmqp_crc32c.h>
3838#include < bmqp_ctrlmsg_messages.h>
39+ #include < bmqp_protocol.h>
40+ #include < bmqp_storageeventbuilder.h>
3941#include < bmqt_messageguid.h>
42+ #include < bmqt_resultcode.h>
4043#include < bmqt_uri.h>
4144
4245#include < bmqu_memoutstream.h>
@@ -160,6 +163,67 @@ void recoveredQueuesCb(
160163 static_cast <void >(queueKeyInfoMap);
161164}
162165
166+ // / Build a storage event carrying a single regular SyncPt journal record with
167+ // / the specified `leaseId`, `seqNum`, `journalOffsetWords`, `dataOffsetDwords`
168+ // / and `qlistOffsetWords`, and apply it to the specified `fs` as a
169+ // / partition-sync event received from `source`. Use the specified
170+ // / `blobSpPool`, `bufferFactory`, `partitionId` and `allocator`.
171+ void applyReplicatedSyncPoint (mqbs::FileStore* fs,
172+ bmqp::BlobPoolUtil::BlobSpPool* blobSpPool,
173+ bdlbb::BlobBufferFactory* bufferFactory,
174+ mqbnet::ClusterNode* source,
175+ int partitionId,
176+ unsigned int leaseId,
177+ bsls::Types::Uint64 seqNum,
178+ unsigned int journalOffsetWords,
179+ unsigned int dataOffsetDwords,
180+ unsigned int qlistOffsetWords,
181+ bslma::Allocator* allocator)
182+ {
183+ // Lay out a regular SyncPt journal record in a fresh blob buffer. The
184+ // RecordHeader carries the PSN that 'processStorageEvent' validates.
185+ bdlbb::BlobBuffer recBuf;
186+ bufferFactory->allocate (&recBuf);
187+ bsl::memset (recBuf.data (),
188+ 0 ,
189+ mqbs::FileStoreProtocol::k_JOURNAL_RECORD_SIZE);
190+
191+ mqbs::JournalOpRecord* rec = new (recBuf.data ())
192+ mqbs::JournalOpRecord (mqbs::JournalOpType::e_SYNCPOINT,
193+ mqbs::SyncPointType::e_REGULAR,
194+ seqNum,
195+ k_NODE_ID,
196+ leaseId,
197+ dataOffsetDwords,
198+ qlistOffsetWords,
199+ mqbs::RecordHeader::k_MAGIC);
200+ rec->header ()
201+ .setPrimaryLeaseId (leaseId)
202+ .setSequenceNumber (seqNum)
203+ .setTimestamp (
204+ bdlt::EpochUtil::convertToTimeT64 (bdlt::CurrentTime::utc ()));
205+
206+ bdlbb::BlobBuffer journalRecBuf (
207+ recBuf.buffer (),
208+ mqbs::FileStoreProtocol::k_JOURNAL_RECORD_SIZE);
209+
210+ bmqp::StorageEventBuilder builder (mqbs::FileStoreProtocol::k_VERSION,
211+ bmqp::EventType::e_PARTITION_SYNC,
212+ blobSpPool,
213+ allocator);
214+ bmqt::EventBuilderResult::Enum brc = builder.packMessage (
215+ bmqp::StorageMessageType::e_JOURNAL_OP,
216+ static_cast <unsigned int >(partitionId),
217+ 0 , // flags
218+ journalOffsetWords,
219+ journalRecBuf);
220+ BMQTST_ASSERT_EQ (brc, bmqt::EventBuilderResult::e_SUCCESS);
221+
222+ fs->processStorageEvent (builder.blob (),
223+ true , // isPartitionSyncEvent
224+ source);
225+ }
226+
163227// CLASSES
164228// ============
165229// class Tester
@@ -910,7 +974,8 @@ static void test1_breathingTest()
910974 // TBD: verify
911975 }
912976
913- fs.close ();
977+ rc = fs.close ();
978+ BMQTST_ASSERT_EQ (0 , rc);
914979
915980 BMQTST_ASSERT_EQ (false , fs.isOpen ());
916981
@@ -1009,7 +1074,8 @@ static void test2_printTest()
10091074 stream << fsIt;
10101075 BMQTST_ASSERT_EQ (stream.str (), " INVALID" );
10111076
1012- fs.close ();
1077+ const int rc = fs.close ();
1078+ BMQTST_ASSERT_EQ (0 , rc);
10131079}
10141080
10151081static void test3_partitionFullAlarm ()
@@ -1153,7 +1219,8 @@ static void test3_partitionFullAlarm()
11531219 poster.postMessage () == mqbi::StorageResult::e_SUCCESS);
11541220
11551221 fs.unregisterStorage (storage_sp.get ());
1156- fs.close ();
1222+ rc = fs.close ();
1223+ BMQTST_ASSERT_EQ (0 , rc);
11571224}
11581225
11591226static void test4_recoverMessagesAcrossLeaseIds ()
@@ -1271,7 +1338,99 @@ static void test4_recoverMessagesAcrossLeaseIds()
12711338 // All records from both leaseIds should have been recovered.
12721339 BMQTST_ASSERT_EQ (fs.numRecords (), numRecords);
12731340
1274- fs.close ();
1341+ rc = fs.close ();
1342+ BMQTST_ASSERT_EQ (0 , rc);
1343+ }
1344+
1345+ static void test5_writeHeadFollowsAppliedLease ()
1346+ // ------------------------------------------------------------------------
1347+ // WRITE HEAD FOLLOWS APPLIED LEASE ID
1348+ //
1349+ // Concerns:
1350+ // When a replica applies a record whose primary leaseId is higher than its
1351+ // current write head via a partition-sync event, the write head must advance
1352+ // to that leaseId (together with the sequence number).
1353+ //
1354+ // Testing:
1355+ // processStorageEvent
1356+ // ------------------------------------------------------------------------
1357+ {
1358+ bmqtst::TestHelperUtil::ignoreCheckDefAlloc () = true ;
1359+
1360+ Tester tester (" ./test-cluster123-5" );
1361+ mqbs::FileStore& fs = tester.fileStore ();
1362+
1363+ int rc = fs.open (0 );
1364+ BMQTST_ASSERT_EQ (0 , rc);
1365+
1366+ bdlbb::PooledBlobBufferFactory bufferFactory (
1367+ 1024 ,
1368+ bmqtst::TestHelperUtil::allocator ());
1369+ bmqp::BlobPoolUtil::BlobSpPoolSp blobSpPool =
1370+ bmqp::BlobPoolUtil::createBlobPool (
1371+ &bufferFactory,
1372+ bmqtst::TestHelperUtil::allocator ());
1373+
1374+ const int k_PARTITION_ID = 0 ;
1375+ const unsigned int dataOffsetDwords = k_SIZEOF_HEADERS_DATA_FILE /
1376+ bmqp::Protocol::k_DWORD_SIZE;
1377+ const unsigned int qlistOffsetWords = k_SIZEOF_HEADERS_QLIST_FILE /
1378+ bmqp::Protocol::k_WORD_SIZE;
1379+
1380+ unsigned int journalOffsetWords = k_SIZEOF_HEADERS_JOURNAL_FILE /
1381+ bmqp::Protocol::k_WORD_SIZE;
1382+ const unsigned int k_RECORD_WORDS =
1383+ mqbs::FileStoreProtocol::k_JOURNAL_RECORD_SIZE /
1384+ bmqp::Protocol::k_WORD_SIZE;
1385+
1386+ fs.setActivePrimary (tester.node (), 1 );
1387+ // A sync point is issued -> [1, 1]
1388+ journalOffsetWords += k_RECORD_WORDS;
1389+ BMQTST_ASSERT_EQ (1U , fs.writeHeadLeaseId ());
1390+ BMQTST_ASSERT_EQ (1ULL , fs.writeHeadSeqNum ());
1391+
1392+ applyReplicatedSyncPoint (&fs,
1393+ blobSpPool.get (),
1394+ &bufferFactory,
1395+ tester.node (),
1396+ k_PARTITION_ID,
1397+ 1 , // leaseId
1398+ 2 , // seqNum
1399+ journalOffsetWords,
1400+ dataOffsetDwords,
1401+ qlistOffsetWords,
1402+ bmqtst::TestHelperUtil::allocator ());
1403+ journalOffsetWords += k_RECORD_WORDS;
1404+ BMQTST_ASSERT_EQ (1U , fs.writeHeadLeaseId ());
1405+ BMQTST_ASSERT_EQ (2ULL , fs.writeHeadSeqNum ());
1406+
1407+ for (bsls::Types::Uint64 seqNum = 1 ; seqNum <= 4 ; ++seqNum) {
1408+ applyReplicatedSyncPoint (&fs,
1409+ blobSpPool.get (),
1410+ &bufferFactory,
1411+ tester.node (),
1412+ k_PARTITION_ID,
1413+ 2 , // leaseId
1414+ seqNum,
1415+ journalOffsetWords,
1416+ dataOffsetDwords,
1417+ qlistOffsetWords,
1418+ bmqtst::TestHelperUtil::allocator ());
1419+ journalOffsetWords += k_RECORD_WORDS;
1420+ BMQTST_ASSERT_EQ (2U , fs.writeHeadLeaseId ());
1421+ BMQTST_ASSERT_EQ (seqNum, fs.writeHeadSeqNum ());
1422+ }
1423+
1424+ // The delayed 'setActivePrimary' for leaseId 2 now arrives. Verify it
1425+ // does not reset the sequence number of leaseId 2 back to zero.
1426+ fs.setActivePrimary (tester.node (), 2 );
1427+ // A sync point is issued -> [2, 5]
1428+ BMQTST_ASSERT_EQ (2U , fs.writeHeadLeaseId ());
1429+ BMQTST_ASSERT_GE (5ULL , fs.writeHeadSeqNum ());
1430+ BMQTST_ASSERT_EQ (tester.node (), fs.primaryNode ());
1431+
1432+ rc = fs.close ();
1433+ BMQTST_ASSERT_EQ (0 , rc);
12751434}
12761435
12771436} // close unnamed namespace
@@ -1288,6 +1447,7 @@ int main(int argc, char* argv[])
12881447
12891448 switch (_testCase) {
12901449 case 0 :
1450+ case 5 : test5_writeHeadFollowsAppliedLease (); break ;
12911451 case 4 : test4_recoverMessagesAcrossLeaseIds (); break ;
12921452 case 3 : test3_partitionFullAlarm (); break ;
12931453 case 2 : test2_printTest (); break ;
0 commit comments