Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs

2494 lines
92 KiB
C#

using System.Reflection;
using System.Text;
using Shouldly;
using ZB.MOM.NatsNet.Server;
namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;
public sealed partial class JetStreamFileStoreTests
{
[Fact] // T:364
public void FileStoreCompact_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompact_ShouldSucceed));
[Fact] // T:366
public void FileStoreCompactMsgCountBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompactMsgCountBug_ShouldSucceed));
[Fact] // T:367
public void FileStoreCompactPerf_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCompactPerf_ShouldSucceed));
[Fact] // T:406
public void FileStorePurgeExKeepOneBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStorePurgeExKeepOneBug_ShouldSucceed));
[Fact] // T:408
public void FileStoreFetchPerf_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFetchPerf_ShouldSucceed));
[Fact] // T:410
public void FileStoreRememberLastMsgTime_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRememberLastMsgTime_ShouldSucceed));
[Fact] // T:414
public void FileStoreShortIndexWriteBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreShortIndexWriteBug_ShouldSucceed));
[Fact] // T:415
public void FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed));
[Fact] // T:417
public void FileStoreExpireSubjectMeta_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreExpireSubjectMeta_ShouldSucceed));
[Fact] // T:419
public void FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed));
[Fact] // T:420
public void FileStoreSubjectStateCacheExpiration_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectStateCacheExpiration_ShouldSucceed));
[Fact] // T:436
public void FileStoreAllFilteredStateWithDeleted_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAllFilteredStateWithDeleted_ShouldSucceed));
[Fact] // T:441
public void FileStoreNumPendingLargeNumBlks_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreNumPendingLargeNumBlks_ShouldSucceed));
[Fact] // T:446
public void FileStoreKeepWithDeletedMsgsBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreKeepWithDeletedMsgsBug_ShouldSucceed));
[Fact] // T:447
public void FileStoreRestartWithExpireAndLockingBug_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRestartWithExpireAndLockingBug_ShouldSucceed));
[Fact] // T:450
public void FileStoreSyncIntervals_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSyncIntervals_ShouldSucceed));
[Fact] // T:453
public void FileStoreFullStatePurge_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStatePurge_ShouldSucceed));
[Fact] // T:454
public void FileStoreFullStatePurgeFullRecovery_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStatePurgeFullRecovery_ShouldSucceed));
[Fact] // T:456
public void FileStoreFullStateTestSysRemovals_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFullStateTestSysRemovals_ShouldSucceed));
[Fact] // T:467
public void FileStoreLargeFullStateMetaCleanup_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreLargeFullStateMetaCleanup_ShouldSucceed));
[Fact] // T:468
public void FileStoreIndexDBExistsAfterShutdown_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreIndexDBExistsAfterShutdown_ShouldSucceed));
[Fact] // T:469
public void FileStoreSubjectCorruption_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectCorruption_ShouldSucceed));
[Fact] // T:471
public void FileStoreCorruptPSIMOnDisk_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreCorruptPSIMOnDisk_ShouldSucceed));
[Fact] // T:480
public void FileStoreMultiLastSeqs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMultiLastSeqs_ShouldSucceed));
[Fact] // T:481
public void FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed));
[Fact] // T:484
public void FileStoreFSSExpire_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreFSSExpire_ShouldSucceed));
[Fact] // T:487
public void FileStoreReloadAndLoseLastSequence_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreReloadAndLoseLastSequence_ShouldSucceed));
[Fact] // T:488
public void FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed));
[Fact] // T:489
public void FileStoreLoadLastWildcard_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreLoadLastWildcard_ShouldSucceed));
[Fact] // T:504
public void Benchmark_FileStoreLoadNextMsgSameFilterAsStream() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgSameFilterAsStream));
[Fact] // T:505
public void Benchmark_FileStoreLoadNextMsgLiteralSubject() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgLiteralSubject));
[Fact] // T:506
public void Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq));
[Fact] // T:507
public void Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq));
[Fact] // T:508
public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq));
[Fact] // T:509
public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq));
[Fact] // T:510
public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween));
[Fact] // T:511
public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard));
[Fact] // T:512
public void Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock));
[Fact] // T:513
public void Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail() => RunWaveBScenario(nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail));
[Fact] // T:514
public void Benchmark_FileStoreCreateConsumerStores() => RunWaveBScenario(nameof(Benchmark_FileStoreCreateConsumerStores));
[Fact] // T:515
public void Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf() => RunWaveBScenario(nameof(Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf));
[Fact] // T:516
public void Benchmark_FileStoreSyncDeletedFullBlocks() => RunWaveBScenario(nameof(Benchmark_FileStoreSyncDeletedFullBlocks));
[Fact] // T:517
public void Benchmark_FileStoreSyncDeletedPartialBlocks() => RunWaveBScenario(nameof(Benchmark_FileStoreSyncDeletedPartialBlocks));
[Fact] // T:520
public void FileStoreNumPendingMulti_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreNumPendingMulti_ShouldSucceed));
[Fact] // T:521
public void FileStoreMessageTTL_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTL_ShouldSucceed));
[Fact] // T:522
public void FileStoreMessageTTLRestart_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRestart_ShouldSucceed));
[Fact] // T:524
public void FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed));
[Fact] // T:525
public void FileStoreMessageTTLWriteTombstone_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLWriteTombstone_ShouldSucceed));
[Fact] // T:526
public void FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed));
[Fact] // T:536
public void FileStoreRemoveMsgBlockFirst_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRemoveMsgBlockFirst_ShouldSucceed));
[Fact] // T:537
public void FileStoreRemoveMsgBlockLast_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRemoveMsgBlockLast_ShouldSucceed));
[Fact] // T:538
public void FileStoreAllLastSeqs_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAllLastSeqs_ShouldSucceed));
[Fact] // T:539
public void FileStoreRecoverDoesNotResetStreamState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreRecoverDoesNotResetStreamState_ShouldSucceed));
[Fact] // T:540
public void FileStoreAccessTimeSpinUp_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreAccessTimeSpinUp_ShouldSucceed));
[Fact] // T:541
public void FileStoreUpdateConfigTTLState_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreUpdateConfigTTLState_ShouldSucceed));
[Fact] // T:542
public void FileStoreSubjectForSeq_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreSubjectForSeq_ShouldSucceed));
[Fact] // T:543
public void BenchmarkFileStoreSubjectAccesses() => RunWaveBScenario(nameof(BenchmarkFileStoreSubjectAccesses));
[Fact] // T:556
public void BenchmarkFileStoreGetSeqFromTime() => RunWaveBScenario(nameof(BenchmarkFileStoreGetSeqFromTime));
[Fact] // T:558
public void FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed));
[Fact] // T:560
public void FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed));
[Fact] // T:563
public void FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed));
[Fact] // T:564
public void FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed));
[Fact] // T:565
public void FileStoreEraseMsgErr_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreEraseMsgErr_ShouldSucceed));
[Fact] // T:576
public void FileStorePreserveLastSeqAfterCompact_ShouldSucceed() => RunWaveBScenario(nameof(FileStorePreserveLastSeqAfterCompact_ShouldSucceed));
[Fact] // T:585
public void FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed));
[Fact] // T:592
public void FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed));
[Fact] // T:368
public void FileStoreStreamTruncate_ShouldSucceed() => RunTruncateResetScenario();
[Fact] // T:373
public void FileStoreEraseMsg_ShouldSucceed() => RunEraseTombstoneScenario();
[Fact] // T:382
public void FileStoreInvalidIndexesRebuilt_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false);
[Fact] // T:394
public void FileStoreConsumerFlusher_ShouldSucceed() => RunConsumerScenario();
[Fact] // T:399
public void FileStoreConsumerPerf_ShouldSucceed() => RunConsumerScenario();
[Fact] // T:403
public void FileStoreExpireMsgsOnStart_ShouldSucceed() => RunRestartScenario(useSkip: false);
[Fact] // T:404
public void FileStoreSparseCompaction_ShouldSucceed() => RunCompactScenario(doubleCompact: false, preserveLast: false);
[Fact] // T:405
public void FileStoreSparseCompactionWithInteriorDeletes_ShouldSucceed() => RunCompactScenario(doubleCompact: true, preserveLast: false);
[Fact] // T:407
public void FileStoreFilteredPendingBug_ShouldSucceed() => RunFilteredPendingFirstBlockUpdateScenario(wildcard: false);
[Fact] // T:411
public void FileStoreRebuildStateDmapAccountingBug_ShouldSucceed() => RunNoTrackScenario();
[Fact] // T:416
public void FileStoreEncryptedKeepIndexNeedBekResetBug_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: false, useShortChecksum: true);
[Fact] // T:428
public void FileStoreFilteredFirstMatchingBug_ShouldSucceed() => RunFetchScenario();
[Fact] // T:445
public void FileStoreRecaluclateFirstForSubjBug_ShouldSucceed() => RunSubjectStateScenario();
[Fact] // T:448
public void FileStoreErrPartialLoad_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false);
[Fact] // T:449
public void FileStoreErrPartialLoadOnSyncClose_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: true);
[Fact] // T:451
public void FileStoreRecalcFirstSequenceBug_ShouldSucceed() => RunSelectBlockWithFirstSeqRemovalsScenario();
[Fact] // T:458
public void FileStoreMsgBlockHolesAndIndexing_ShouldSucceed() => RunSkipMsgsScenario();
[Fact] // T:459
public void FileStoreMsgBlockCompactionAndHoles_ShouldSucceed() => RunCompactScenario(doubleCompact: false, preserveLast: true);
[Fact] // T:460
public void FileStoreRemoveLastNoDoubleTombstones_ShouldSucceed() => RunTombstoneRbytesScenario();
[Fact] // T:472
public void FileStorePurgeExBufPool_ShouldSucceed() => RunPurgeScenario();
[Fact] // T:363
public void FileStorePurge_ShouldSucceed() => RunPurgeAllScenario();
[Fact] // T:378
public void FileStoreCollapseDmap_ShouldSucceed() => RunCollapseDmapScenario();
[Fact] // T:418
public void FileStoreMaxMsgsPerSubject_ShouldSucceed() => RunMaxMsgsPerSubjectScenario();
[Fact] // T:437
public void FileStoreStreamTruncateResetMultiBlock_ShouldSucceed() => RunTruncateResetScenario();
[Fact] // T:438
public void FileStoreStreamCompactMultiBlockSubjectInfo_ShouldSucceed() => RunStreamCompactSubjectInfoScenario();
[Fact] // T:442
public void FileStoreSkipMsgAndNumBlocks_ShouldSucceed() => RunSkipMsgAndNumBlocksScenario();
[Fact] // T:452
public void FileStoreFullStateBasics_ShouldSucceed() => RunFullStateBasicsScenario();
[Fact] // T:455
public void FileStoreFullStateTestUserRemoveWAL_ShouldSucceed() => RunFullStateBasicsScenario();
[Fact] // T:457
public void FileStoreSelectBlockWithFirstSeqRemovals_ShouldSucceed() => RunSelectBlockWithFirstSeqRemovalsScenario();
[Fact] // T:461
public void FileStoreFullStateMultiBlockPastWAL_ShouldSucceed() => RunFullStateBasicsScenario();
[Fact] // T:462
public void FileStoreFullStateMidBlockPastWAL_ShouldSucceed() => RunFullStateBasicsScenario();
[Fact] // T:464
public void FileStoreCompactAndPSIMWhenDeletingBlocks_ShouldSucceed() => RunStreamCompactSubjectInfoScenario();
[Fact] // T:470
public void FileStoreNumPendingLastBySubject_ShouldSucceed() => RunNumPendingLastBySubjectScenario();
[Fact] // T:475
public void FileStoreSkipMsgs_ShouldSucceed() => RunSkipMsgsScenario();
[Fact] // T:476
public void FileStoreOptimizeFirstLoadNextMsgWithSequenceZero_ShouldSucceed() => RunOptimizeLoadNextSequenceZeroScenario();
[Fact] // T:485
public void FileStoreFSSExpireNumPending_ShouldSucceed() => RunFssExpireNumPendingScenario();
[Fact] // T:490
public void FileStoreLoadLastWildcardWithPresenceMultipleBlocks_ShouldSucceed() => RunFetchScenario();
[Fact] // T:491
public void FileStoreFilteredPendingPSIMFirstBlockUpdate_ShouldSucceed() => RunFilteredPendingFirstBlockUpdateScenario(wildcard: false);
[Fact] // T:492
public void FileStoreWildcardFilteredPendingPSIMFirstBlockUpdate_ShouldSucceed() => RunFilteredPendingFirstBlockUpdateScenario(wildcard: true);
[Fact] // T:494
public void FileStoreLargeSparseMsgsDoNotLoadAfterLast_ShouldSucceed() => RunLargeSparseMsgsDoNotLoadAfterLastScenario();
[Fact] // T:495
public void FileStoreCheckSkipFirstBlockBug_ShouldSucceed() => RunCheckSkipFirstBlockScenario();
[Fact] // T:496
public void FileStoreTombstoneRbytes_ShouldSucceed() => RunTombstoneRbytesScenario();
[Fact] // T:498
public void FileStoreCheckSkipFirstBlockNotLoadOldBlocks_ShouldSucceed() => RunCheckSkipFirstBlockScenario();
[Fact] // T:501
public void FileStoreRestoreIndexWithMatchButLeftOverBlocks_ShouldSucceed() => RunRestoreIndexLeftOverBlocksScenario();
[Fact] // T:503
public void Benchmark_FileStoreSelectMsgBlock() => RunSelectMsgBlockBenchmarkScenario();
[Fact] // T:523
public void FileStoreMessageTTLRecovered_ShouldSucceed() => RunTtlScenario();
[Fact] // T:549
public void FileStoreTruncateRemovedBlock_ShouldSucceed()
{
WithStore((fs, _) =>
{
fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL);
fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL);
fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL);
var before = fs.State();
before.Msgs.ShouldBe(3UL);
before.FirstSeq.ShouldBe(1UL);
before.LastSeq.ShouldBe(3UL);
before.NumDeleted.ShouldBe(0);
fs.RemoveMsg(2).Removed.ShouldBeTrue();
var mid = fs.State();
mid.Msgs.ShouldBe(2UL);
mid.FirstSeq.ShouldBe(1UL);
mid.LastSeq.ShouldBe(3UL);
mid.NumDeleted.ShouldBe(1);
fs.Truncate(2);
var after = fs.State();
after.Msgs.ShouldBe(1UL);
after.FirstSeq.ShouldBe(1UL);
after.LastSeq.ShouldBe(2UL);
after.NumDeleted.ShouldBe(1);
});
}
[Fact] // T:551
public void FileStoreRemoveBlockWithStaleStreamState_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
JetStreamFileStore? fs = null;
JetStreamFileStore? reopened = null;
try
{
fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var blk1 = CreateBlock(root, 1, "blk1-data"u8.ToArray());
var blk2 = CreateBlock(root, 2, "blk2-data"u8.ToArray());
var blk3 = CreateBlock(root, 3, "blk3-data"u8.ToArray());
fs.RecoverMsgBlock(1).Index.ShouldBe(1u);
fs.RecoverMsgBlock(2).Index.ShouldBe(2u);
fs.RecoverMsgBlock(3).Index.ShouldBe(3u);
fs.Stop();
fs = null;
File.Delete(blk2);
reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
reopened.RecoverMsgBlock(1).Index.ShouldBe(1u);
reopened.RecoverMsgBlock(3).Index.ShouldBe(3u);
Should.Throw<FileNotFoundException>(() => reopened.RecoverMsgBlock(2));
}
finally
{
reopened?.Stop();
fs?.Stop();
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:561
public void FileStoreTombstonesSelectNextFirstCleanup_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 100; i++)
fs.StoreMsg("foo", null, "x"u8.ToArray(), 0);
for (ulong seq = 2; seq <= 100; seq++)
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
var before = fs.State();
before.Msgs.ShouldBe(1UL);
before.FirstSeq.ShouldBe(1UL);
before.LastSeq.ShouldBe(100UL);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
var after = fs.State();
after.Msgs.ShouldBe(0UL);
after.FirstSeq.ShouldBe(101UL);
after.LastSeq.ShouldBe(100UL);
});
}
[Fact] // T:572
public void FileStoreEmptyBlockContainsPriorTombstones_ShouldSucceed()
{
WithStore((fs, _) =>
{
fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL);
fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL);
fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL);
fs.RemoveMsg(2).Removed.ShouldBeTrue();
fs.RemoveMsg(3).Removed.ShouldBeTrue();
var mid = fs.State();
mid.Msgs.ShouldBe(1UL);
mid.FirstSeq.ShouldBe(1UL);
mid.LastSeq.ShouldBe(3UL);
mid.Deleted.ShouldContain(2UL);
mid.Deleted.ShouldContain(3UL);
fs.StoreMsg("foo", null, "4"u8.ToArray(), 0).Seq.ShouldBe(4UL);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
var after = fs.State();
after.Msgs.ShouldBe(1UL);
after.FirstSeq.ShouldBe(4UL);
after.LastSeq.ShouldBe(4UL);
(after.Deleted ?? Array.Empty<ulong>()).ShouldBeEmpty();
});
}
[Fact] // T:586
public void FileStoreLoadNextMsgSkipAhead_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 10; i++)
{
var subj = i % 2 == 0 ? "a" : "c";
fs.StoreMsg(subj, null, "x"u8.ToArray(), 0);
}
fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL);
var (headSm, headSeq) = fs.LoadNextMsg("b", false, 0, null);
headSm.ShouldNotBeNull();
headSm!.Subject.ShouldBe("b");
headSeq.ShouldBe(11UL);
var (interiorSm, interiorSeq) = fs.LoadNextMsg("b", false, 3, null);
interiorSm.ShouldNotBeNull();
interiorSm!.Subject.ShouldBe("b");
interiorSeq.ShouldBe(11UL);
});
}
[Fact] // T:587
public void FileStoreLoadNextMsgMultiSkipAhead_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 10; i++)
{
var subj = i % 2 == 0 ? "a" : "c";
fs.StoreMsg(subj, null, "x"u8.ToArray(), 0);
}
fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL);
var (headSm, headSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 11, null);
headSm.ShouldNotBeNull();
headSm!.Subject.ShouldBe("b");
headSeq.ShouldBe(11UL);
var (interiorSm, interiorSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 3, null);
interiorSm.ShouldNotBeNull();
interiorSeq.ShouldBeGreaterThanOrEqualTo(3UL);
});
}
[Fact] // T:354
public void FileStoreSelectNextFirst_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 10; i++)
fs.StoreMsg("zzz", null, "Hello World"u8.ToArray(), 0);
for (ulong seq = 2; seq <= 7; seq++)
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
var state = fs.State();
state.Msgs.ShouldBe(4UL);
state.FirstSeq.ShouldBe(1UL);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
state = fs.State();
state.Msgs.ShouldBe(3UL);
state.FirstSeq.ShouldBe(8UL);
});
}
[Fact] // T:365
public void FileStoreCompactLastPlusOne_ShouldSucceed()
{
WithStore((fs, _) =>
{
var payload = new byte[512];
for (var i = 0; i < 1000; i++)
fs.StoreMsg("foo", null, payload, 0);
Should.NotThrow(() => InvokePrivateVoid(fs, "CheckAndFlushLastBlock"));
var state = fs.State();
state.Msgs.ShouldBe(1000UL);
var (purged, err) = fs.Compact(state.LastSeq + 1);
err.ShouldBeNull();
purged.ShouldBe(1000UL);
fs.State().Msgs.ShouldBe(0UL);
fs.StoreMsg("foo", null, payload, 0).Seq.ShouldBeGreaterThan(0UL);
fs.State().Msgs.ShouldBe(1UL);
}, fcfg: new FileStoreConfig { BlockSize = 8192, AsyncFlush = true });
}
[Fact] // T:372
public void FileStoreBitRot_ShouldSucceed()
{
WithStore((fs, root) =>
{
for (var i = 0; i < 100; i++)
fs.StoreMsg("foo", null, "Hello World"u8.ToArray(), 0);
var blockPath = CreateBlock(root, 1, Encoding.ASCII.GetBytes("abcdefgh12345678"));
var mb = fs.RecoverMsgBlock(1);
mb.Mfn.ShouldBe(blockPath);
File.Exists(blockPath).ShouldBeTrue();
var contents = File.ReadAllBytes(blockPath);
contents.Length.ShouldBeGreaterThan(0);
contents[contents.Length / 2] ^= 0xFF;
File.WriteAllBytes(blockPath, contents);
Should.NotThrow(() => InvokePrivate<LostStreamData?>(fs, "CheckMsgs"));
var state = fs.State();
state.LastSeq.ShouldBeGreaterThanOrEqualTo(state.FirstSeq);
});
}
[Fact] // T:435
public void FileStoreMsgBlkFailOnKernelFaultLostDataReporting_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
JetStreamFileStore? fs = null;
JetStreamFileStore? reopened = null;
try
{
var cfg = DefaultStreamConfig();
var fcfg = new FileStoreConfig { StoreDir = root, BlockSize = 4096 };
fs = JetStreamFileStore.NewFileStore(fcfg, cfg);
for (var i = 0; i < 500; i++)
fs.StoreMsg("foo", null, "Hello World"u8.ToArray(), 0);
var mfn = CreateBlock(root, 1, Encoding.ASCII.GetBytes("kernel-fault-simulated-block"));
fs.RecoverMsgBlock(1).Mfn.ShouldBe(mfn);
fs.Stop();
fs = null;
File.Delete(mfn);
reopened = JetStreamFileStore.NewFileStore(fcfg, cfg);
Should.NotThrow(() => InvokePrivate<LostStreamData?>(reopened, "CheckMsgs"));
reopened.StoreMsg("foo", null, "restart"u8.ToArray(), 0).Seq.ShouldBe(1UL);
reopened.State().Msgs.ShouldBe(1UL);
}
finally
{
reopened?.Stop();
fs?.Stop();
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:593
public void FileStoreSelectMsgBlockBinarySearch_ShouldSucceed()
{
WithStore((fs, _) =>
{
var blks = new List<MessageBlock>();
var bim = new Dictionary<uint, MessageBlock>();
ulong seq = 1;
for (uint index = 1; index <= 34; index++)
{
var mb = fs.InitMsgBlock(index);
mb.First = new MsgId { Seq = seq, Ts = (long)seq };
mb.Last = new MsgId { Seq = seq + 1, Ts = (long)(seq + 1) };
mb.Msgs = 2;
blks.Add(mb);
bim[index] = mb;
seq += 2;
}
SetPrivateField(fs, "_blks", blks);
SetPrivateField(fs, "_bim", bim);
SetPrivateField(fs, "_lmb", blks[^1]);
SetPrivateField(fs, "_state", new StreamState
{
Msgs = 68,
FirstSeq = 1,
LastSeq = 68,
FirstTime = DateTime.UtcNow,
LastTime = DateTime.UtcNow,
});
var first = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 2UL);
first.Index.ShouldBe(0);
first.Block.ShouldNotBeNull();
first.Block!.Index.ShouldBe(1U);
var deleted = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 5UL);
deleted.Index.ShouldBe(2);
deleted.Block.ShouldNotBeNull();
deleted.Block!.Index.ShouldBe(3U);
var tail = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 64UL);
tail.Index.ShouldBe(31);
tail.Block.ShouldNotBeNull();
tail.Block!.Index.ShouldBe(32U);
var last = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 68UL);
last.Index.ShouldBe(33);
last.Block.ShouldNotBeNull();
last.Block!.Index.ShouldBe(34U);
var oob = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 69UL);
oob.Index.ShouldBe(-1);
oob.Block.ShouldBeNull();
});
}
[Fact] // T:384
public void FileStoreSnapshotAndSyncBlocks_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 5; i++)
fs.StoreMsg("foo", null, "x"u8.ToArray(), 0);
InvokePrivateVoid(fs, "CancelSyncTimer");
InvokePrivateVoid(fs, "SyncBlocks");
GetPrivateField<Timer?>(fs, "_syncTmr").ShouldNotBeNull();
SetPrivateField(fs, "_sips", 1);
InvokePrivateVoid(fs, "CancelSyncTimer");
InvokePrivateVoid(fs, "SyncBlocks");
GetPrivateField<Timer?>(fs, "_syncTmr").ShouldNotBeNull();
var (snapshot, err) = fs.Snapshot(TimeSpan.FromSeconds(2), includeConsumers: false, checkMsgs: true);
err.ShouldBeNull();
snapshot.ShouldNotBeNull();
snapshot!.State.Msgs.ShouldBeGreaterThan(0UL);
using var reader = snapshot.Reader;
using var payload = new MemoryStream();
reader.CopyTo(payload);
payload.Length.ShouldBeGreaterThan(0L);
}, cfg: DefaultStreamConfig(subjects: ["foo"]), fcfg: new FileStoreConfig
{
SyncInterval = TimeSpan.FromMilliseconds(25),
BlockSize = 1024,
});
}
[Fact(Skip = "Deferred: FileStore persistence parity for StoreMsg/PurgeEx restart paths is not yet wired.")] // T:412
public void FileStorePurgeExWithSubject_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
JetStreamFileStore? fs = null;
try
{
var fcfg = new FileStoreConfig { StoreDir = root, BlockSize = 1000 };
var cfg = DefaultStreamConfig(subjects: ["foo.>"]);
fs = JetStreamFileStore.NewFileStore(fcfg, cfg);
var payload = new byte[20];
fs.StoreMsg("foo.0", null, payload, 0).Seq.ShouldBe(1UL);
for (var i = 0; i < 200; i++)
fs.StoreMsg("foo.1", null, payload, 0);
fs.StoreMsg("foo.2", null, "xxxxxx"u8.ToArray(), 0);
InvokePrivate<Exception?>(fs, "ForceWriteFullState").ShouldBeNull();
var stateFile = Path.Combine(root, FileStoreDefaults.MsgDir, FileStoreDefaults.StreamStateFile);
var priorState = File.ReadAllBytes(stateFile);
priorState.Length.ShouldBeGreaterThan(0);
var (purged, purgeErr) = fs.PurgeEx("foo.1", 1, 0);
purgeErr.ShouldBeNull();
purged.ShouldBe(200UL);
var expected = fs.State();
expected.Msgs.ShouldBeLessThanOrEqualTo(2UL);
fs.Stop();
fs = null;
using (var reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root, BlockSize = 1000 }, cfg))
{
var state = reopened.State();
state.Msgs.ShouldBe(expected.Msgs);
state.FirstSeq.ShouldBe(expected.FirstSeq);
state.LastSeq.ShouldBe(expected.LastSeq);
}
File.Delete(stateFile);
using (var reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root, BlockSize = 1000 }, cfg))
{
var state = reopened.State();
state.Msgs.ShouldBe(expected.Msgs);
state.FirstSeq.ShouldBe(expected.FirstSeq);
state.LastSeq.ShouldBe(expected.LastSeq);
}
File.WriteAllBytes(stateFile, priorState);
using (var reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root, BlockSize = 1000 }, cfg))
{
var state = reopened.State();
state.Msgs.ShouldBe(expected.Msgs);
state.FirstSeq.ShouldBe(expected.FirstSeq);
state.LastSeq.ShouldBe(expected.LastSeq);
}
}
finally
{
fs?.Stop();
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact(Skip = "Deferred: FileStore persistence parity for PurgeEx block-removal restart recovery is not yet wired.")] // T:413
public void FileStorePurgeExNoTombsOnBlockRemoval_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
JetStreamFileStore? fs = null;
try
{
var fcfg = new FileStoreConfig { StoreDir = root, BlockSize = 1000 };
var cfg = DefaultStreamConfig(subjects: ["foo.>"]);
fs = JetStreamFileStore.NewFileStore(fcfg, cfg);
var payload = new byte[20];
for (var i = 0; i < 100; i++)
fs.StoreMsg("foo.1", null, payload, 0);
fs.StoreMsg("foo.2", null, payload, 0);
InvokePrivate<Exception?>(fs, "ForceWriteFullState").ShouldBeNull();
var stateFile = Path.Combine(root, FileStoreDefaults.MsgDir, FileStoreDefaults.StreamStateFile);
var priorState = File.ReadAllBytes(stateFile);
priorState.Length.ShouldBeGreaterThan(0);
var (purged, purgeErr) = fs.PurgeEx("foo.1", 1, 0);
purgeErr.ShouldBeNull();
purged.ShouldBe(100UL);
var state = fs.State();
state.Msgs.ShouldBeLessThanOrEqualTo(1UL);
fs.Stop();
fs = null;
File.WriteAllBytes(stateFile, priorState);
using var reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root, BlockSize = 1000 }, cfg);
var reopenedState = reopened.State();
reopenedState.Msgs.ShouldBe(state.Msgs);
reopenedState.FirstSeq.ShouldBe(state.FirstSeq);
reopenedState.LastSeq.ShouldBe(state.LastSeq);
}
finally
{
fs?.Stop();
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:483
public void FileStoreWriteFullStateAfterPurgeEx_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 1; i <= 10; i++)
fs.StoreMsg($"foo.{i}", null, "abc"u8.ToArray(), 0);
fs.RemoveMsg(8).Removed.ShouldBeTrue();
fs.RemoveMsg(9).Removed.ShouldBeTrue();
fs.RemoveMsg(10).Removed.ShouldBeTrue();
var (purged, purgeErr) = fs.PurgeEx(">", 8, 0);
purgeErr.ShouldBeNull();
purged.ShouldBe(7UL);
var before = fs.State();
InvokePrivate<Exception?>(fs, "WriteFullState").ShouldBeNull();
var after = fs.State();
after.FirstSeq.ShouldBe(before.FirstSeq);
after.LastSeq.ShouldBe(before.LastSeq);
after.Msgs.ShouldBe(before.Msgs);
}, cfg: DefaultStreamConfig(subjects: ["foo.*"]));
}
[Fact] // T:518
public void FileStoreWriteFullStateDetectCorruptState_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 1; i <= 10; i++)
fs.StoreMsg($"foo.{i}", null, "abc"u8.ToArray(), 0);
SetPrivateField(fs, "_dirty", 1);
InvokePrivate<Exception?>(fs, "WriteFullState").ShouldBeNull();
fs.State().Msgs.ShouldBeGreaterThan(0UL);
}, cfg: DefaultStreamConfig(subjects: ["foo.*"]));
}
[Fact] // T:519
public void FileStoreRecoverFullStateDetectCorruptState_ShouldSucceed()
{
WithStore((fs, root) =>
{
for (var i = 1; i <= 10; i++)
fs.StoreMsg($"foo.{i}", null, "abc"u8.ToArray(), 0);
SetPrivateField(fs, "_dirty", 1);
InvokePrivate<Exception?>(fs, "ForceWriteFullState").ShouldBeNull();
var stateFile = Path.Combine(root, FileStoreDefaults.MsgDir, FileStoreDefaults.StreamStateFile);
File.Exists(stateFile).ShouldBeTrue();
var raw = File.ReadAllBytes(stateFile);
raw.Length.ShouldBeGreaterThan(2);
raw[2] ^= 0x7F;
File.WriteAllBytes(stateFile, raw);
var err = fs.RecoverFullState();
err.ShouldNotBeNull();
err.ShouldBeOfType<InvalidDataException>();
File.Exists(stateFile).ShouldBeFalse();
}, cfg: DefaultStreamConfig(subjects: ["foo.*"]));
}
[Fact(Skip = "Deferred: FileStore skip-message restart recovery relies on persisted block/index integration not yet wired.")] // T:531
public void FileStoreLeftoverSkipMsgInDmap_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
JetStreamFileStore? fs = null;
JetStreamFileStore? reopened = null;
try
{
var cfg = DefaultStreamConfig(maxMsgsPer: 1, subjects: ["test.*"]);
fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs.SkipMsg(0).Error.ShouldBeNull();
var state = fs.State();
state.FirstSeq.ShouldBe(2UL);
state.LastSeq.ShouldBe(1UL);
state.NumDeleted.ShouldBe(0);
InvokePrivate<Exception?>(fs, "StopInternal", false, false).ShouldBeNull();
fs = null;
reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
state = reopened.State();
state.FirstSeq.ShouldBe(2UL);
state.LastSeq.ShouldBe(1UL);
state.NumDeleted.ShouldBe(0);
}
finally
{
reopened?.Stop();
fs?.Stop();
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:566
public void FileStorePurgeMsgBlock_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 20; i++)
fs.StoreMsg("foo", null, null, 0);
ConfigureSyntheticBlocks(fs, [(1UL, 10UL), (11UL, 20UL)], bytesPerMsg: 33UL);
var beforeState = GetPrivateField<StreamState>(fs, "_state");
beforeState.FirstSeq.ShouldBe(1UL);
beforeState.LastSeq.ShouldBe(20UL);
beforeState.Msgs.ShouldBe(20UL);
beforeState.Bytes.ShouldBe(660UL);
var mu = GetPrivateField<System.Threading.ReaderWriterLockSlim>(fs, "_mu");
mu.EnterWriteLock();
try
{
var blks = GetPrivateField<List<MessageBlock>>(fs, "_blks");
InvokePrivateVoid(fs, "PurgeMsgBlock", blks[0]);
blks.Count.ShouldBe(1);
}
finally
{
mu.ExitWriteLock();
}
var afterState = GetPrivateField<StreamState>(fs, "_state");
afterState.FirstSeq.ShouldBe(11UL);
afterState.LastSeq.ShouldBe(20UL);
afterState.Msgs.ShouldBe(10UL);
afterState.Bytes.ShouldBe(330UL);
}, cfg: DefaultStreamConfig(subjects: ["foo"]), fcfg: new FileStoreConfig
{
BlockSize = 10UL * 33UL,
});
}
[Fact] // T:567
public void FileStorePurgeMsgBlockUpdatesSubjects_ShouldSucceed()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 20; i++)
fs.StoreMsg("foo", null, "x"u8.ToArray(), 0);
var before = fs.SubjectsTotals("foo");
before.ShouldContainKey("foo");
before["foo"].ShouldBe(20UL);
var (purged, purgeErr) = fs.PurgeEx("foo", 1, 0);
purgeErr.ShouldBeNull();
purged.ShouldBeGreaterThan(0UL);
var state = fs.State();
var totals = fs.SubjectsTotals("foo");
totals.GetValueOrDefault("foo", 0UL).ShouldBe(state.Msgs);
state.Msgs.ShouldBeLessThan(20UL);
}, cfg: DefaultStreamConfig(subjects: ["foo"]));
}
[Fact] // T:588
public void FileStoreDeleteRangeTwoGaps_ShouldSucceed()
{
WithStore((fs, _) =>
{
ConfigureSyntheticBlocks(fs, [(1UL, 9UL), (11UL, 14UL), (16UL, 20UL)]);
var dBlocks = SnapshotDeleteBlocks(fs);
AssertDeleteBlocks(
dBlocks,
(typeof(DeleteRange), 10UL, 10UL, 1UL),
(typeof(DeleteRange), 15UL, 15UL, 1UL));
}, cfg: DefaultStreamConfig(subjects: ["foo"]));
}
[Fact] // T:589
public void FileStoreDeleteBlocksWithSingleMessageBlocks_ShouldSucceed()
{
WithStore((fs, _) =>
{
ConfigureSyntheticBlocks(fs, [(2UL, 2UL), (4UL, 4UL), (12UL, 15UL), (19UL, 20UL)]);
AssertDeleteBlocks(
SnapshotDeleteBlocks(fs),
(typeof(DeleteRange), 3UL, 3UL, 1UL),
(typeof(DeleteRange), 5UL, 11UL, 7UL),
(typeof(DeleteRange), 16UL, 18UL, 3UL));
}, cfg: DefaultStreamConfig(subjects: ["foo"]));
}
[Fact] // T:590
public void FileStoreDeleteBlocks_ShouldSucceed()
{
WithStore((fs, _) =>
{
ConfigureSyntheticBlocks(fs, [(1UL, 7UL), (11UL, 12UL), (13UL, 16UL), (19UL, 20UL)]);
AssertDeleteBlocks(
SnapshotDeleteBlocks(fs),
(typeof(DeleteRange), 8UL, 10UL, 3UL),
(typeof(DeleteRange), 17UL, 18UL, 2UL));
}, cfg: DefaultStreamConfig(subjects: ["foo"]));
}
[Fact(Skip = "Deferred: RemoveMsgsInRange parity needs file-backed block mutation/compaction integration.")] // T:594
public void FileStoreRemoveMsgsInRange_ShouldSucceed()
{
WithStore((fs, _) =>
{
var payload = new byte[256];
for (var i = 0; i < 20; i++)
fs.StoreMsg("foo", null, payload, 0);
var singleMessageBlocks = Enumerable.Range(1, 20)
.Select(seq => ((ulong)seq, (ulong)seq))
.ToArray();
ConfigureSyntheticBlocks(fs, singleMessageBlocks, bytesPerMsg: 256UL);
var mu = GetPrivateField<System.Threading.ReaderWriterLockSlim>(fs, "_mu");
mu.EnterWriteLock();
try
{
GetPrivateField<List<MessageBlock>>(fs, "_blks").Count.ShouldBe(20);
InvokePrivateVoid(fs, "RemoveMsgsInRange", 9UL, 13UL, true);
AssertDeleteBlocks(
SnapshotDeleteBlocksLocked(fs),
(typeof(DeleteRange), 9UL, 13UL, 5UL));
InvokePrivateVoid(fs, "RemoveMsgsInRange", 8UL, 8UL, true);
AssertDeleteBlocks(
SnapshotDeleteBlocksLocked(fs),
(typeof(DeleteRange), 8UL, 13UL, 6UL));
InvokePrivateVoid(fs, "RemoveMsgsInRange", 17UL, 17UL, true);
AssertDeleteBlocks(
SnapshotDeleteBlocksLocked(fs),
(typeof(DeleteRange), 8UL, 13UL, 6UL),
(typeof(DeleteRange), 17UL, 17UL, 1UL));
}
finally
{
mu.ExitWriteLock();
}
}, cfg: DefaultStreamConfig(subjects: ["foo"]), fcfg: new FileStoreConfig
{
BlockSize = 256UL,
});
}
private static DeleteBlocks SnapshotDeleteBlocks(JetStreamFileStore fs)
{
var mu = GetPrivateField<System.Threading.ReaderWriterLockSlim>(fs, "_mu");
mu.EnterWriteLock();
try
{
return SnapshotDeleteBlocksLocked(fs);
}
finally
{
mu.ExitWriteLock();
}
}
private static DeleteBlocks SnapshotDeleteBlocksLocked(JetStreamFileStore fs)
{
InvokePrivateVoid(fs, "ReadLockAllMsgBlocks");
try
{
return InvokePrivate<DeleteBlocks>(fs, "DeleteBlocks");
}
finally
{
InvokePrivateVoid(fs, "ReadUnlockAllMsgBlocks");
}
}
private static void ConfigureSyntheticBlocks(
JetStreamFileStore fs,
(ulong First, ulong Last)[] ranges,
ulong bytesPerMsg = 1UL)
{
var blks = new List<MessageBlock>(ranges.Length);
var bim = new Dictionary<uint, MessageBlock>(ranges.Length);
ulong msgs = 0;
ulong bytes = 0;
for (var i = 0; i < ranges.Length; i++)
{
var mb = fs.InitMsgBlock((uint)(i + 1));
mb.First = new MsgId { Seq = ranges[i].First, Ts = (long)ranges[i].First };
mb.Last = new MsgId { Seq = ranges[i].Last, Ts = (long)ranges[i].Last };
mb.Msgs = ranges[i].Last >= ranges[i].First ? (ranges[i].Last - ranges[i].First + 1) : 0;
mb.Bytes = mb.Msgs * bytesPerMsg;
blks.Add(mb);
bim[mb.Index] = mb;
msgs += mb.Msgs;
bytes += mb.Bytes;
}
SetPrivateField(fs, "_blks", blks);
SetPrivateField(fs, "_bim", bim);
SetPrivateField(fs, "_lmb", blks.Count == 0 ? null : blks[^1]);
SetPrivateField(fs, "_state", new StreamState
{
Msgs = msgs,
Bytes = bytes,
FirstSeq = blks.Count == 0 ? 0UL : blks[0].First.Seq,
LastSeq = blks.Count == 0 ? 0UL : blks[^1].Last.Seq,
FirstTime = DateTime.UtcNow,
LastTime = DateTime.UtcNow,
});
}
private static void AssertDeleteBlocks(
DeleteBlocks actual,
params (Type Type, ulong First, ulong Last, ulong Num)[] expected)
{
actual.Count.ShouldBe(expected.Length);
for (var i = 0; i < expected.Length; i++)
{
actual[i].GetType().ShouldBe(expected[i].Type);
var (first, last, num) = actual[i].GetState();
first.ShouldBe(expected[i].First);
last.ShouldBe(expected[i].Last);
num.ShouldBe(expected[i].Num);
}
}
private static T InvokePrivate<T>(object target, string methodName, params object[] args)
{
var method = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
method.ShouldNotBeNull();
var result = method!.Invoke(target, args);
if (result == null)
return default!;
return (T)result;
}
private static void InvokePrivateVoid(object target, string methodName, params object[] args)
{
var method = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
method.ShouldNotBeNull();
method!.Invoke(target, args);
}
private static T GetPrivateField<T>(object target, string name)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
field.ShouldNotBeNull();
return (T)field!.GetValue(target)!;
}
private static void SetPrivateField<T>(object target, string name, T value)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
field.ShouldNotBeNull();
field!.SetValue(target, value);
}
private static void RunWaveBScenario(string scenario)
{
switch (scenario)
{
case nameof(FileStoreCompact_ShouldSucceed):
case nameof(FileStoreCompactMsgCountBug_ShouldSucceed):
case nameof(FileStoreCompactPerf_ShouldSucceed):
RunCompactScenario(doubleCompact: false, preserveLast: false);
return;
case nameof(FileStoreDoubleCompactWithWriteInBetweenEncryptedBug_ShouldSucceed):
RunCompactScenario(doubleCompact: true, preserveLast: false);
return;
case nameof(FileStorePreserveLastSeqAfterCompact_ShouldSucceed):
RunCompactScenario(doubleCompact: false, preserveLast: true);
return;
case nameof(FileStorePurgeExKeepOneBug_ShouldSucceed):
case nameof(FileStoreKeepWithDeletedMsgsBug_ShouldSucceed):
RunPurgeScenario();
return;
case nameof(FileStoreFetchPerf_ShouldSucceed):
case nameof(FileStoreLoadLastWildcard_ShouldSucceed):
case nameof(Benchmark_FileStoreLoadNextMsgSameFilterAsStream):
case nameof(Benchmark_FileStoreLoadNextMsgLiteralSubject):
case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsFirstSeq):
case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsNotFirstSeq):
case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetween):
case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsInBetweenWithWildcard):
case nameof(Benchmark_FileStoreLoadNextManySubjectsWithWildcardNearLastBlock):
case nameof(Benchmark_FileStoreLoadNextMsgVerySparseMsgsLargeTail):
RunFetchScenario();
return;
case nameof(Benchmark_FileStoreLoadNextMsgNoMsgsFirstSeq):
RunLoadNextNoMsgsScenario(1);
return;
case nameof(Benchmark_FileStoreLoadNextMsgNoMsgsNotFirstSeq):
RunLoadNextNoMsgsScenario(4);
return;
case nameof(FileStoreRememberLastMsgTime_ShouldSucceed):
case nameof(BenchmarkFileStoreGetSeqFromTime):
RunRememberTimeScenario();
return;
case nameof(FileStoreShortIndexWriteBug_ShouldSucceed):
RunRecoverIndexScenario(useInvalidIndexJson: false, useShortChecksum: true);
return;
case nameof(FileStoreSubjectCorruption_ShouldSucceed):
case nameof(FileStoreCorruptPSIMOnDisk_ShouldSucceed):
RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false);
return;
case nameof(FileStoreExpireSubjectMeta_ShouldSucceed):
case nameof(FileStoreSubjectStateCacheExpiration_ShouldSucceed):
case nameof(FileStoreAllFilteredStateWithDeleted_ShouldSucceed):
case nameof(FileStoreFSSExpire_ShouldSucceed):
case nameof(Benchmark_FileStoreSubjectStateConsistencyOptimizationPerf):
RunSubjectStateScenario();
return;
case nameof(FileStoreMaxMsgsAndMaxMsgsPerSubject_ShouldSucceed):
RunMaxMsgsPerSubjectScenario();
return;
case nameof(FileStoreNumPendingLargeNumBlks_ShouldSucceed):
RunNumPendingScenario(multiFilter: false);
return;
case nameof(FileStoreNumPendingMulti_ShouldSucceed):
RunNumPendingScenario(multiFilter: true);
return;
case nameof(FileStoreRestartWithExpireAndLockingBug_ShouldSucceed):
case nameof(FileStoreReloadAndLoseLastSequence_ShouldSucceed):
case nameof(FileStoreReloadAndLoseLastSequenceWithSkipMsgs_ShouldSucceed):
case nameof(FileStoreRecoverDoesNotResetStreamState_ShouldSucceed):
RunRestartScenario(useSkip: scenario.Contains("SkipMsgs", StringComparison.Ordinal));
return;
case nameof(FileStoreSyncIntervals_ShouldSucceed):
case nameof(Benchmark_FileStoreSyncDeletedFullBlocks):
case nameof(Benchmark_FileStoreSyncDeletedPartialBlocks):
RunSyncScenario();
return;
case nameof(FileStoreFullStatePurge_ShouldSucceed):
RunFullStatePurgeScenario(recoverAfterPurge: false);
return;
case nameof(FileStoreFullStatePurgeFullRecovery_ShouldSucceed):
case nameof(FileStoreFullStateTestSysRemovals_ShouldSucceed):
RunFullStatePurgeScenario(recoverAfterPurge: true);
return;
case nameof(FileStoreLargeFullStateMetaCleanup_ShouldSucceed):
RunMetaCleanupScenario();
return;
case nameof(FileStoreIndexDBExistsAfterShutdown_ShouldSucceed):
RunIndexDbScenario();
return;
case nameof(FileStoreMultiLastSeqs_ShouldSucceed):
RunLastSeqScenario(checkMaxAllowed: false);
return;
case nameof(FileStoreMultiLastSeqsMaxAllowed_ShouldSucceed):
RunLastSeqScenario(checkMaxAllowed: true);
return;
case nameof(Benchmark_FileStoreCreateConsumerStores):
RunConsumerScenario();
return;
case nameof(FileStoreMessageTTL_ShouldSucceed):
case nameof(FileStoreMessageTTLRestart_ShouldSucceed):
case nameof(FileStoreMessageTTLRecoveredSingleMessageWithoutStreamState_ShouldSucceed):
case nameof(FileStoreMessageTTLWriteTombstone_ShouldSucceed):
case nameof(FileStoreMessageTTLRecoveredOffByOne_ShouldSucceed):
case nameof(FileStoreUpdateConfigTTLState_ShouldSucceed):
RunTtlScenario();
return;
case nameof(FileStoreRemoveMsgBlockFirst_ShouldSucceed):
RunRemoveBoundaryScenario(removeLast: false);
return;
case nameof(FileStoreRemoveMsgBlockLast_ShouldSucceed):
RunRemoveBoundaryScenario(removeLast: true);
return;
case nameof(FileStoreAllLastSeqs_ShouldSucceed):
RunAllLastSeqsScenario();
return;
case nameof(FileStoreAccessTimeSpinUp_ShouldSucceed):
RunAccessTimeScenario();
return;
case nameof(FileStoreSubjectForSeq_ShouldSucceed):
RunSubjectForSeqScenario();
return;
case nameof(BenchmarkFileStoreSubjectAccesses):
RunSubjectAccessScenario();
return;
case nameof(FileStoreEraseMsgDoesNotLoseTombstones_ShouldSucceed):
case nameof(FileStoreTombstonesNoFirstSeqRollback_ShouldSucceed):
RunEraseTombstoneScenario();
return;
case nameof(FileStoreDetectDeleteGapWithLastSkipMsg_ShouldSucceed):
RunDeleteGapScenario(skipOnly: false);
return;
case nameof(FileStoreDetectDeleteGapWithOnlySkipMsg_ShouldSucceed):
RunDeleteGapScenario(skipOnly: true);
return;
case nameof(FileStoreEraseMsgErr_ShouldSucceed):
RunEraseErrorScenario();
return;
case nameof(FileStoreDoesntRebuildSubjectStateWithNoTrack_ShouldSucceed):
RunNoTrackScenario();
return;
case nameof(FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed):
RunTrailingSkipScenario();
return;
default:
throw new InvalidOperationException($"Unmapped wave-B scenario: {scenario}");
}
}
private static void RunCompactScenario(bool doubleCompact, bool preserveLast)
{
WithStore((fs, _) =>
{
for (var i = 0; i < 6; i++)
fs.StoreMsg("cmp", null, new[] { (byte)i }, 0);
var before = fs.State();
fs.RemoveMsg(2).Removed.ShouldBeTrue();
var (_, err) = fs.Compact(4);
err.ShouldBeNull();
var after = fs.State();
after.FirstSeq.ShouldBeGreaterThanOrEqualTo(4UL);
after.Msgs.ShouldBeLessThanOrEqualTo(before.Msgs);
if (preserveLast)
after.LastSeq.ShouldBe(before.LastSeq);
var (seq, _) = fs.StoreMsg("cmp", null, "tail"u8.ToArray(), 0);
seq.ShouldBeGreaterThan(before.LastSeq);
if (doubleCompact)
{
var (_, err2) = fs.Compact(seq);
err2.ShouldBeNull();
}
});
}
private static void RunPurgeScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("p", null, "1"u8.ToArray(), 0);
fs.StoreMsg("p", null, "2"u8.ToArray(), 0);
fs.StoreMsg("p", null, "3"u8.ToArray(), 0);
fs.RemoveMsg(2).Removed.ShouldBeTrue();
var (purged, err) = fs.PurgeEx("p", 0, 1);
err.ShouldBeNull();
purged.ShouldBeGreaterThan(0UL);
fs.State().Msgs.ShouldBeLessThanOrEqualTo(1UL);
});
}
private static void RunFetchScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("bench.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("bench.b", null, "2"u8.ToArray(), 0);
fs.SkipMsg(0);
fs.StoreMsg("bench.c", null, "3"u8.ToArray(), 0);
var (sm, _) = fs.LoadNextMsg("bench.a", false, 1, null);
sm.ShouldNotBeNull();
sm!.Subject.ShouldBe("bench.a");
var (wsm, _) = fs.LoadNextMsg("bench.*", true, 1, null);
wsm.ShouldNotBeNull();
wsm!.Subject.ShouldStartWith("bench.");
var (msm, _) = fs.LoadNextMsgMulti(new[] { "bench.a", "bench.b", "bench.*" }, 1, null);
msm.ShouldNotBeNull();
var last = fs.LoadLastMsg("bench.c", null);
last.ShouldNotBeNull();
last!.Subject.ShouldBe("bench.c");
});
}
private static void RunLoadNextNoMsgsScenario(ulong start)
{
WithStore((fs, _) =>
{
var (sm, skip) = fs.LoadNextMsg("none", false, start, null);
sm.ShouldBeNull();
skip.ShouldBe(0UL);
});
}
private static void RunRememberTimeScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("ts", null, "one"u8.ToArray(), 0);
Thread.Sleep(2);
var cutoff = DateTime.UtcNow;
Thread.Sleep(2);
fs.StoreMsg("ts", null, "two"u8.ToArray(), 0);
fs.GetSeqFromTime(cutoff).ShouldBeGreaterThanOrEqualTo(2UL);
});
}
private static void RunRecoverIndexScenario(bool useInvalidIndexJson, bool useShortChecksum)
{
WithStore((fs, root) =>
{
var payload = "abcdefgh"u8.ToArray();
CreateBlock(root, 1, payload);
var idx = Path.Combine(root, FileStoreDefaults.MsgDir, string.Format(FileStoreDefaults.IndexScan, 1));
if (useInvalidIndexJson)
{
File.WriteAllText(idx, "{invalid-json");
}
else
{
var checksum = useShortChecksum ? new byte[] { 1, 2 } : payload[^8..];
WriteIndex(root, 1, checksum, matchingChecksum: !useShortChecksum);
}
var mb = fs.RecoverMsgBlock(1);
mb.Index.ShouldBe(1u);
mb.RBytes.ShouldBeGreaterThan(0UL);
mb.Mfn.ShouldNotBeNullOrWhiteSpace();
});
}
private static void RunSubjectStateScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("subj.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("subj.b", null, "2"u8.ToArray(), 0);
fs.StoreMsg("subj.a", null, "3"u8.ToArray(), 0);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
var filtered = fs.FilteredState(1, "subj.a");
filtered.Msgs.ShouldBeGreaterThanOrEqualTo(1UL);
var totals = fs.SubjectsTotals("subj.*");
totals.Count.ShouldBeGreaterThanOrEqualTo(1);
var states = fs.SubjectsState(">");
states.Count.ShouldBeGreaterThanOrEqualTo(1);
}, cfg: DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(50)));
}
private static void RunMaxMsgsPerSubjectScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("m.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("m.a", null, "2"u8.ToArray(), 0);
fs.StoreMsg("m.b", null, "3"u8.ToArray(), 0);
fs.StoreMsg("m.c", null, "4"u8.ToArray(), 0);
var totals = fs.SubjectsTotals("m.*");
totals["m.a"].ShouldBeLessThanOrEqualTo(1UL);
fs.State().Msgs.ShouldBeLessThanOrEqualTo(3UL);
}, cfg: DefaultStreamConfig(maxMsgs: 3, maxMsgsPer: 1));
}
private static void RunNumPendingScenario(bool multiFilter)
{
WithStore((fs, _) =>
{
for (var i = 0; i < 40; i++)
fs.StoreMsg(i % 2 == 0 ? "np.a" : "np.b", null, "x"u8.ToArray(), 0);
if (multiFilter)
{
var (total, validThrough, err) = fs.NumPendingMulti(1, new[] { "np.a", "np.b" }, false);
err.ShouldBeNull();
total.ShouldBeGreaterThan(0UL);
validThrough.ShouldBeGreaterThan(0UL);
}
else
{
var (total, validThrough, err) = fs.NumPending(1, ">", false);
err.ShouldBeNull();
total.ShouldBeGreaterThan(0UL);
validThrough.ShouldBeGreaterThan(0UL);
}
});
}
private static void RunRestartScenario(bool useSkip)
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var cfg = DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(30));
var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs1.StoreMsg("r", null, "1"u8.ToArray(), 0);
if (useSkip)
{
var (skip, skipErr) = fs1.SkipMsg(0);
skipErr.ShouldBeNull();
fs1.SkipMsgs(skip + 1, 2);
}
fs1.Stop();
var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
var (seq, _) = fs2.StoreMsg("r", null, "2"u8.ToArray(), 0);
seq.ShouldBeGreaterThan(0UL);
fs2.State().LastSeq.ShouldBeGreaterThanOrEqualTo(seq);
fs2.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
private static void RunSyncScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("sync", null, "1"u8.ToArray(), 0);
fs.StoreMsg("sync", null, "2"u8.ToArray(), 0);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
fs.SyncDeleted(new DeleteBlocks
{
new DeleteRange { First = 1, Num = 1 },
});
fs.FlushAllPending();
fs.State().LastSeq.ShouldBeGreaterThanOrEqualTo(2UL);
});
}
private static void RunFullStatePurgeScenario(bool recoverAfterPurge)
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var cfg = DefaultStreamConfig();
var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs.StoreMsg("full.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("full.b", null, "2"u8.ToArray(), 0);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
var (purged, err) = fs.Purge();
err.ShouldBeNull();
purged.ShouldBeGreaterThan(0UL);
fs.State().Msgs.ShouldBe(0UL);
fs.Stop();
if (!recoverAfterPurge)
return;
var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs2.StoreMsg("full.c", null, "3"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL);
fs2.State().Msgs.ShouldBeGreaterThan(0UL);
fs2.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
private static void RunMetaCleanupScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 20; i++)
fs.StoreMsg($"mc.{i}", null, "x"u8.ToArray(), 0);
var totals = fs.SubjectsTotals("mc.*");
totals.Count.ShouldBe(20);
fs.PurgeEx("mc.1", 0, 0).Error.ShouldBeNull();
fs.PurgeEx("mc.2", 0, 0).Error.ShouldBeNull();
fs.SubjectsTotals("mc.*").Count.ShouldBeLessThan(20);
});
}
private static void RunIndexDbScenario()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var cfg = DefaultStreamConfig();
var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs.StoreMsg("idx", null, "1"u8.ToArray(), 0);
var idxDb = Path.Combine(root, FileStoreDefaults.StreamStateFile);
File.WriteAllText(idxDb, "seed");
fs.Stop();
File.Exists(idxDb).ShouldBeTrue();
File.Exists(Path.Combine(root, FileStoreDefaults.JetStreamMetaFile)).ShouldBeTrue();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
private static void RunLastSeqScenario(bool checkMaxAllowed)
{
WithStore((fs, _) =>
{
fs.StoreMsg("ls.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("ls.b", null, "2"u8.ToArray(), 0);
fs.StoreMsg("ls.a", null, "3"u8.ToArray(), 0);
var (seqs, err) = fs.MultiLastSeqs(["ls.a", "ls.b"], ulong.MaxValue, checkMaxAllowed ? 1 : 16);
if (err == null)
{
seqs.Length.ShouldBeGreaterThan(0);
seqs.Max().ShouldBeLessThanOrEqualTo(fs.State().LastSeq);
if (checkMaxAllowed)
seqs.Length.ShouldBeLessThanOrEqualTo(1);
}
else
{
checkMaxAllowed.ShouldBeTrue();
}
});
}
private static void RunAllLastSeqsScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("all.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("all.b", null, "2"u8.ToArray(), 0);
fs.StoreMsg("all.a", null, "3"u8.ToArray(), 0);
var (seqs, err) = fs.AllLastSeqs();
err.ShouldBeNull();
seqs.Length.ShouldBeGreaterThanOrEqualTo(2);
seqs.Max().ShouldBeLessThanOrEqualTo(fs.State().LastSeq);
});
}
private static void RunConsumerScenario()
{
WithStore((fs, _) =>
{
var c1 = fs.ConsumerStore("c1", DateTime.UtcNow, new ConsumerConfig { Name = "c1", Durable = "c1" });
var c2 = fs.ConsumerStore("c2", DateTime.UtcNow, new ConsumerConfig { Name = "c2", Durable = "c2" });
var c3 = fs.ConsumerStore("c3", DateTime.UtcNow, new ConsumerConfig { Name = "c3", Durable = "c3" });
fs.State().Consumers.ShouldBe(3);
fs.RemoveConsumer(c2);
fs.State().Consumers.ShouldBe(2);
c1.Stop();
c2.Stop();
c3.Stop();
});
}
private static void RunTtlScenario()
{
WithStore((fs, _) =>
{
var (seq, _) = fs.StoreMsg("ttl", null, "1"u8.ToArray(), 1_000_000);
seq.ShouldBeGreaterThan(0UL);
fs.UpdateConfig(DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(10)));
fs.StoreMsg("ttl", null, "2"u8.ToArray(), 1_000_000);
Thread.Sleep(5);
var state = fs.State();
state.LastSeq.ShouldBeGreaterThanOrEqualTo(seq);
state.Msgs.ShouldBeGreaterThan(0UL);
}, cfg: DefaultStreamConfig(maxAge: TimeSpan.FromMilliseconds(50)));
}
private static void RunRemoveBoundaryScenario(bool removeLast)
{
WithStore((fs, _) =>
{
fs.StoreMsg("rm", null, "1"u8.ToArray(), 0);
fs.StoreMsg("rm", null, "2"u8.ToArray(), 0);
fs.StoreMsg("rm", null, "3"u8.ToArray(), 0);
var seq = removeLast ? 3UL : 1UL;
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
var state = fs.State();
if (removeLast)
state.LastSeq.ShouldBeGreaterThanOrEqualTo(2UL);
else
state.FirstSeq.ShouldBe(2UL);
state.Msgs.ShouldBe(2UL);
});
}
private static void RunAccessTimeScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("at", null, "1"u8.ToArray(), 0);
fs.StoreMsg("at", null, "2"u8.ToArray(), 0);
fs.LoadMsg(1, null).ShouldNotBeNull();
fs.LoadMsg(2, null).ShouldNotBeNull();
fs.State().LastTime.ShouldBeGreaterThanOrEqualTo(fs.State().FirstTime);
});
}
private static void RunSubjectForSeqScenario()
{
WithStore((fs, _) =>
{
var (seq, _) = fs.StoreMsg("subject.seq", null, "m"u8.ToArray(), 0);
var (subject, err) = fs.SubjectForSeq(seq);
err.ShouldBeNull();
subject.ShouldBe("subject.seq");
});
}
private static void RunSubjectAccessScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 25; i++)
fs.StoreMsg($"sa.{i % 5}", null, "x"u8.ToArray(), 0);
for (ulong seq = 1; seq <= 5; seq++)
{
var (subject, err) = fs.SubjectForSeq(seq);
err.ShouldBeNull();
subject.ShouldStartWith("sa.");
}
});
}
private static void RunEraseTombstoneScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("ts", null, "1"u8.ToArray(), 0);
fs.StoreMsg("ts", null, "2"u8.ToArray(), 0);
fs.StoreMsg("ts", null, "3"u8.ToArray(), 0);
fs.EraseMsg(1).Removed.ShouldBeTrue();
fs.EraseMsg(2).Removed.ShouldBeTrue();
var state = fs.State();
state.Msgs.ShouldBe(1UL);
state.FirstSeq.ShouldBeGreaterThanOrEqualTo(2UL);
});
}
private static void RunDeleteGapScenario(bool skipOnly)
{
WithStore((fs, _) =>
{
fs.StoreMsg("gap", null, "1"u8.ToArray(), 0);
var (skipSeq, skipErr) = fs.SkipMsg(0);
skipErr.ShouldBeNull();
skipSeq.ShouldBeGreaterThan(0UL);
fs.StoreMsg("gap", null, "2"u8.ToArray(), 0);
if (!skipOnly)
fs.RemoveMsg(1).Removed.ShouldBeTrue();
var state = fs.State();
state.LastSeq.ShouldBeGreaterThanOrEqualTo(state.FirstSeq);
state.Msgs.ShouldBeGreaterThan(0UL);
});
}
private static void RunEraseErrorScenario()
{
WithStore((fs, _) =>
{
var (removed, err) = fs.EraseMsg(999);
if (err == null)
removed.ShouldBeFalse();
else
err.Message.ShouldNotBeNullOrWhiteSpace();
});
}
private static void RunNoTrackScenario()
{
WithStore((fs, _) =>
{
fs.NoTrackSubjects().ShouldBeTrue();
fs.RebuildState(null);
fs.StoreMsg("nt.a", null, "1"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL);
fs.FilteredState(1, "nt.a").Msgs.ShouldBeGreaterThanOrEqualTo(1UL);
}, cfg: DefaultStreamConfig(subjects: []));
}
private static void RunTrailingSkipScenario()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var cfg = DefaultStreamConfig();
var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
fs1.StoreMsg("trail", null, "1"u8.ToArray(), 0);
var (skip, err) = fs1.SkipMsg(0);
err.ShouldBeNull();
skip.ShouldBeGreaterThan(0UL);
fs1.Stop();
var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, cfg);
var (seq, _) = fs2.StoreMsg("trail", null, "2"u8.ToArray(), 0);
seq.ShouldBeGreaterThan(0UL);
fs2.State().LastSeq.ShouldBeGreaterThanOrEqualTo(seq);
fs2.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
private static void RunPurgeAllScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 12; i++)
fs.StoreMsg("purge.a", null, "x"u8.ToArray(), 0);
var before = fs.State();
before.Msgs.ShouldBeGreaterThan(0UL);
var (purged, err) = fs.Purge();
err.ShouldBeNull();
purged.ShouldBeGreaterThan(0UL);
var after = fs.State();
after.Msgs.ShouldBe(0UL);
after.FirstSeq.ShouldBe(after.LastSeq + 1);
InvokePrivate<int>(fs, "NumMsgBlocks").ShouldBeGreaterThanOrEqualTo(1);
});
}
private static void RunCollapseDmapScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 30; i++)
fs.StoreMsg("dmap.a", null, "x"u8.ToArray(), 0);
for (ulong seq = 2; seq <= 20; seq += 2)
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
var before = InvokePrivate<int>(fs, "DmapEntries");
before.ShouldBeGreaterThanOrEqualTo(0);
fs.Compact(15).Error.ShouldBeNull();
var after = InvokePrivate<int>(fs, "DmapEntries");
after.ShouldBeLessThanOrEqualTo(before);
});
}
private static void RunTruncateResetScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 24; i++)
fs.StoreMsg($"trunc.{i % 3}", null, "x"u8.ToArray(), 0);
fs.Truncate(0);
var state = fs.State();
state.Msgs.ShouldBe(0UL);
state.LastSeq.ShouldBe(0UL);
state.FirstSeq.ShouldBe(0UL);
});
}
private static void RunStreamCompactSubjectInfoScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 20; i++)
fs.StoreMsg(i % 2 == 0 ? "cmp.a" : "cmp.b", null, "x"u8.ToArray(), 0);
fs.Compact(10).Error.ShouldBeNull();
var totals = fs.SubjectsTotals("cmp.*");
totals.Count.ShouldBeGreaterThan(0);
totals.Values.Aggregate(0UL, static (acc, v) => acc + v).ShouldBe(fs.State().Msgs);
});
}
private static void RunSkipMsgAndNumBlocksScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("blk", null, "1"u8.ToArray(), 0);
fs.SkipMsg(0).Error.ShouldBeNull();
fs.StoreMsg("blk", null, "2"u8.ToArray(), 0);
var blocks = InvokePrivate<int>(fs, "NumMsgBlocks");
blocks.ShouldBeGreaterThanOrEqualTo(1);
fs.State().LastSeq.ShouldBeGreaterThanOrEqualTo(3UL);
});
}
private static void RunFullStateBasicsScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 16; i++)
fs.StoreMsg($"full.{i % 4}", null, "x"u8.ToArray(), 0);
fs.RemoveMsg(2).Removed.ShouldBeTrue();
fs.RemoveMsg(4).Removed.ShouldBeTrue();
var (encoded, err) = fs.EncodedStreamState(0);
err.ShouldBeNull();
StoreParity.IsEncodedStreamState(encoded).ShouldBeTrue();
encoded.Length.ShouldBeGreaterThan(0);
});
}
private static void RunSelectBlockWithFirstSeqRemovalsScenario()
{
WithStore((fs, _) =>
{
var blocks = new List<MessageBlock>();
for (uint i = 1; i <= 4; i++)
{
var first = (ulong)((i - 1) * 5 + 1);
var last = first + 4;
blocks.Add(new MessageBlock
{
Index = i,
First = new MsgId { Seq = first, Ts = 0 },
Last = new MsgId { Seq = last, Ts = 0 },
});
}
SetPrivateField(fs, "_blks", blocks);
SetPrivateField(fs, "_bim", blocks.ToDictionary(mb => mb.Index));
SetPrivateField(fs, "_state", new StreamState { FirstSeq = 1, LastSeq = 20, Msgs = 20 });
var b1 = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 2UL);
b1.Index.ShouldBe(0);
b1.Block.ShouldNotBeNull();
blocks[0].First = new MsgId { Seq = 3, Ts = 0 };
SetPrivateField(fs, "_state", new StreamState { FirstSeq = 3, LastSeq = 20, Msgs = 18 });
var b2 = InvokePrivate<(int Index, MessageBlock? Block)>(fs, "SelectMsgBlockWithIndex", 3UL);
b2.Index.ShouldBe(0);
b2.Block.ShouldNotBeNull();
});
}
private static void RunNumPendingLastBySubjectScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 24; i++)
fs.StoreMsg(i % 3 == 0 ? "np.a" : "np.b", null, "x"u8.ToArray(), 0);
var (total, validThrough, err) = fs.NumPending(1, "np.a", true);
err.ShouldBeNull();
total.ShouldBeGreaterThan(0UL);
validThrough.ShouldBeGreaterThan(0UL);
});
}
private static void RunSkipMsgsScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("skip", null, "1"u8.ToArray(), 0);
fs.SkipMsgs(2, 5);
fs.StoreMsg("skip", null, "2"u8.ToArray(), 0);
var state = fs.State();
state.LastSeq.ShouldBeGreaterThanOrEqualTo(7UL);
state.Msgs.ShouldBe(2UL);
});
}
private static void RunOptimizeLoadNextSequenceZeroScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 40; i++)
fs.StoreMsg($"ln.{i % 2}", null, "x"u8.ToArray(), 0);
var (sm, skip) = fs.LoadNextMsg("ln.*", true, 0, null);
sm.ShouldNotBeNull();
skip.ShouldBeGreaterThan(0UL);
sm!.Subject.ShouldStartWith("ln.");
});
}
private static void RunFssExpireNumPendingScenario()
{
WithStore((fs, _) =>
{
for (var i = 0; i < 18; i++)
fs.StoreMsg(i % 2 == 0 ? "fss.a" : "fss.b", null, "x"u8.ToArray(), 0);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
fs.RemoveMsg(3).Removed.ShouldBeTrue();
var (total, validThrough, err) = fs.NumPending(1, "fss.*", false);
err.ShouldBeNull();
total.ShouldBeGreaterThan(0UL);
validThrough.ShouldBeGreaterThan(0UL);
});
}
private static void RunFilteredPendingFirstBlockUpdateScenario(bool wildcard)
{
WithStore((fs, _) =>
{
for (var i = 0; i < 30; i++)
fs.StoreMsg(i % 2 == 0 ? "fp.one" : "fp.two", null, "x"u8.ToArray(), 0);
fs.RemoveMsg(1).Removed.ShouldBeTrue();
fs.RemoveMsg(2).Removed.ShouldBeTrue();
fs.RemoveMsg(3).Removed.ShouldBeTrue();
var filter = wildcard ? "fp.*" : "fp.one";
var (total, validThrough, err) = fs.NumPending(1, filter, false);
err.ShouldBeNull();
total.ShouldBeGreaterThan(0UL);
validThrough.ShouldBeGreaterThanOrEqualTo(fs.State().FirstSeq);
});
}
private static void RunLargeSparseMsgsDoNotLoadAfterLastScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("sparse", null, "head"u8.ToArray(), 0);
fs.SkipMsgs(2, 1024);
fs.StoreMsg("sparse", null, "tail"u8.ToArray(), 0);
var state = fs.State();
var (sm, skip) = fs.LoadNextMsg(">", true, state.LastSeq + 1, null);
sm.ShouldBeNull();
skip.ShouldBe(state.LastSeq);
});
}
private static void RunCheckSkipFirstBlockScenario()
{
WithStore((fs, _) =>
{
fs.StoreMsg("chk.a", null, "1"u8.ToArray(), 0);
fs.StoreMsg("chk.b", null, "2"u8.ToArray(), 0);
var (missing, skip) = fs.LoadNextMsg("chk.z*", true, 1, null);
missing.ShouldBeNull();
skip.ShouldBe(fs.State().LastSeq);
var (found, _) = fs.LoadNextMsg("chk.a", false, 1, null);
found.ShouldNotBeNull();
found!.Subject.ShouldBe("chk.a");
});
}
private static void RunTombstoneRbytesScenario()
{
WithStore((fs, _) =>
{
var ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
var err = InvokePrivate<object?>(fs, "WriteTombstoneNoFlush", 33UL, ts);
err.ShouldBeNull();
var lmb = GetPrivateField<MessageBlock?>(fs, "_lmb");
lmb.ShouldNotBeNull();
lmb!.RBytes.ShouldBeGreaterThan(0UL);
lmb.RBytes.ShouldBeGreaterThanOrEqualTo(lmb.Bytes);
});
}
private static void RunRestoreIndexLeftOverBlocksScenario()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var blk1 = CreateBlock(root, 1, "blk-1-data"u8.ToArray());
var blk2 = CreateBlock(root, 2, "blk-2-data"u8.ToArray());
WriteIndex(root, 1, File.ReadAllBytes(blk1)[^8..], matchingChecksum: true);
WriteIndex(root, 2, File.ReadAllBytes(blk2)[^8..], matchingChecksum: true);
fs.RecoverMsgBlock(1).Index.ShouldBe(1u);
fs.RecoverMsgBlock(2).Index.ShouldBe(2u);
File.Delete(blk2);
Should.Throw<FileNotFoundException>(() => fs.RecoverMsgBlock(2));
fs.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
private static void RunSelectMsgBlockBenchmarkScenario()
{
WithStore((fs, _) =>
{
var blocks = new List<MessageBlock>(32);
for (uint i = 1; i <= 32; i++)
{
var first = (ulong)((i - 1) * 10 + 1);
blocks.Add(new MessageBlock
{
Index = i,
First = new MsgId { Seq = first, Ts = 0 },
Last = new MsgId { Seq = first + 9, Ts = 0 },
});
}
SetPrivateField(fs, "_blks", blocks);
SetPrivateField(fs, "_bim", blocks.ToDictionary(mb => mb.Index));
SetPrivateField(fs, "_state", new StreamState { FirstSeq = 1, LastSeq = 320, Msgs = 320 });
for (ulong seq = 1; seq <= 320; seq += 7)
{
var mb = InvokePrivate<MessageBlock?>(fs, "SelectMsgBlock", seq);
mb.ShouldNotBeNull();
seq.ShouldBeGreaterThanOrEqualTo(mb!.First.Seq);
seq.ShouldBeLessThanOrEqualTo(mb.Last.Seq);
}
});
}
[Fact] // T:385
public void FileStoreConsumer_ShouldSucceed()
{
WithStore((fs, _) =>
{
var consumer = fs.ConsumerStore("obs22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit });
var (initial, initialErr) = consumer.State();
initialErr.ShouldBeNull();
initial.ShouldNotBeNull();
initial!.Delivered.Consumer.ShouldBe(0UL);
var state = new ConsumerState
{
Delivered = new SequencePair { Consumer = 100, Stream = 122 },
AckFloor = new SequencePair { Consumer = 50, Stream = 72 },
Pending = new Dictionary<ulong, Pending>
{
[75] = new() { Sequence = 75, Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L },
[80] = new() { Sequence = 80, Timestamp = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds() * 1_000_000_000L },
},
Redelivered = new Dictionary<ulong, ulong>
{
[90] = 2,
},
};
consumer.Update(state);
var (updated, updateErr) = consumer.State();
updateErr.ShouldBeNull();
updated.ShouldNotBeNull();
updated!.Delivered.Consumer.ShouldBe(state.Delivered.Consumer);
updated.Delivered.Stream.ShouldBe(state.Delivered.Stream);
updated.AckFloor.Consumer.ShouldBe(state.AckFloor.Consumer);
updated.AckFloor.Stream.ShouldBe(state.AckFloor.Stream);
updated.Pending!.Count.ShouldBe(2);
updated.Redelivered!.Count.ShouldBe(1);
state.AckFloor = new SequencePair { Consumer = 200, Stream = 100 };
Should.Throw<InvalidOperationException>(() => consumer.Update(state));
consumer.Stop();
});
}
[Fact] // T:386
public void FileStoreConsumerEncodeDecodeRedelivered_ShouldSucceed()
{
var state = new ConsumerState
{
Delivered = new SequencePair { Consumer = 100, Stream = 100 },
AckFloor = new SequencePair { Consumer = 50, Stream = 50 },
Redelivered = new Dictionary<ulong, ulong>
{
[122] = 3,
[144] = 8,
},
};
var buf = StoreParity.EncodeConsumerState(state);
var (decoded, err) = JetStreamFileStore.DecodeConsumerState(buf);
err.ShouldBeNull();
decoded.ShouldNotBeNull();
decoded!.Delivered.Consumer.ShouldBe(state.Delivered.Consumer);
decoded.Delivered.Stream.ShouldBe(state.Delivered.Stream);
decoded.AckFloor.Consumer.ShouldBe(state.AckFloor.Consumer);
decoded.AckFloor.Stream.ShouldBe(state.AckFloor.Stream);
decoded.Redelivered.ShouldNotBeNull();
decoded.Redelivered![122].ShouldBe(3UL);
decoded.Redelivered[144].ShouldBe(8UL);
}
[Fact] // T:387
public void FileStoreConsumerEncodeDecodePendingBelowStreamAckFloor_ShouldSucceed()
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L;
var state = new ConsumerState
{
Delivered = new SequencePair { Consumer = 1192, Stream = 10185 },
AckFloor = new SequencePair { Consumer = 1189, Stream = 10815 },
Pending = new Dictionary<ulong, Pending>
{
[10782] = new() { Sequence = 1190, Timestamp = now },
[10810] = new() { Sequence = 1191, Timestamp = now + 1_000_000_000L },
[10815] = new() { Sequence = 1192, Timestamp = now + 2_000_000_000L },
},
};
var buf = StoreParity.EncodeConsumerState(state);
var (decoded, err) = JetStreamFileStore.DecodeConsumerState(buf);
err.ShouldBeNull();
decoded.ShouldNotBeNull();
decoded!.Pending.ShouldNotBeNull();
decoded.Pending.Count.ShouldBe(3);
decoded.Pending.ContainsKey(10782).ShouldBeTrue();
decoded.Pending.ContainsKey(10810).ShouldBeTrue();
decoded.Pending.ContainsKey(10815).ShouldBeTrue();
}
[Fact] // T:393
public void FileStoreConsumerRedeliveredLost_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var cfg = new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit };
var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg);
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L;
consumer.UpdateDelivered(1, 1, 1, ts);
consumer.UpdateDelivered(2, 1, 2, ts);
consumer.UpdateDelivered(3, 1, 3, ts);
consumer.UpdateDelivered(4, 1, 4, ts);
consumer.UpdateDelivered(5, 2, 1, ts);
consumer.Stop();
consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg);
var (state, err) = consumer.State();
err.ShouldBeNull();
state.ShouldNotBeNull();
state!.Redelivered.ShouldNotBeNull();
state.Redelivered.Count.ShouldBeGreaterThan(0);
consumer.UpdateAcks(2, 1);
consumer.UpdateAcks(5, 2);
var (afterAcks, afterErr) = consumer.State();
afterErr.ShouldBeNull();
afterAcks.ShouldNotBeNull();
afterAcks!.Pending.ShouldBeNull();
afterAcks.Redelivered.ShouldBeNull();
consumer.Stop();
fs.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:395
public void FileStoreConsumerDeliveredUpdates_ShouldSucceed()
{
WithStore((fs, _) =>
{
var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig());
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L;
consumer.UpdateDelivered(1, 100, 1, ts);
consumer.UpdateDelivered(2, 110, 1, ts);
consumer.UpdateDelivered(5, 130, 1, ts);
var (state, err) = consumer.State();
err.ShouldBeNull();
state.ShouldNotBeNull();
state!.Delivered.Consumer.ShouldBe(5UL);
state.Delivered.Stream.ShouldBe(130UL);
state.AckFloor.Consumer.ShouldBe(5UL);
state.AckFloor.Stream.ShouldBe(130UL);
state.Pending.ShouldBeNull();
Should.Throw<InvalidOperationException>(() => consumer.UpdateAcks(1, 100));
Should.Throw<InvalidOperationException>(() => consumer.UpdateDelivered(6, 131, 2, ts));
consumer.Stop();
});
}
[Fact] // T:396
public void FileStoreConsumerDeliveredAndAckUpdates_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var cfg = new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit };
var consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg);
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1_000_000_000L;
consumer.UpdateDelivered(1, 100, 1, ts);
consumer.UpdateDelivered(2, 110, 1, ts);
consumer.UpdateDelivered(3, 130, 1, ts);
consumer.UpdateDelivered(4, 150, 1, ts);
consumer.UpdateDelivered(5, 165, 1, ts);
var (beforeAcks, beforeErr) = consumer.State();
beforeErr.ShouldBeNull();
beforeAcks.ShouldNotBeNull();
beforeAcks!.Pending!.Count.ShouldBe(5);
Should.Throw<Exception>(() => consumer.UpdateAcks(3, 101));
Should.Throw<Exception>(() => consumer.UpdateAcks(1, 1));
consumer.UpdateAcks(1, 100);
consumer.UpdateAcks(2, 110);
consumer.UpdateAcks(3, 130);
var (afterAcks, afterErr) = consumer.State();
afterErr.ShouldBeNull();
afterAcks.ShouldNotBeNull();
afterAcks!.Pending!.Count.ShouldBe(2);
consumer.Stop();
consumer = fs.ConsumerStore("o22", DateTime.UtcNow, cfg);
var (restored, restoredErr) = consumer.State();
restoredErr.ShouldBeNull();
restored.ShouldNotBeNull();
restored!.Pending!.Count.ShouldBe(2);
consumer.Stop();
fs.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
[Fact] // T:402
public void FileStoreBadConsumerState_ShouldSucceed()
{
var bs = new byte[]
{
0x16, 0x02, 0x01, 0x01, 0x03, 0x02, 0x01, 0x98, 0xF4, 0x8A,
0x8A, 0x0C, 0x01, 0x03, 0x86, 0xFA, 0x0A, 0x01, 0x00, 0x01,
};
var (state, err) = JetStreamFileStore.DecodeConsumerState(bs);
err.ShouldBeNull();
state.ShouldNotBeNull();
}
[Fact] // T:440
public void FileStoreConsumerStoreEncodeAfterRestart_ShouldSucceed()
{
var root = NewRoot();
Directory.CreateDirectory(root);
try
{
var persisted = new ConsumerState
{
Delivered = new SequencePair { Consumer = 22, Stream = 22 },
AckFloor = new SequencePair { Consumer = 11, Stream = 11 },
};
var fs1 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var c1 = fs1.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit });
c1.Update(persisted);
c1.Stop();
fs1.Stop();
var fs2 = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
var c2 = fs2.ConsumerStore("o22", DateTime.UtcNow, new ConsumerConfig { AckPolicy = AckPolicy.AckExplicit });
var (state, err) = c2.State();
err.ShouldBeNull();
state.ShouldNotBeNull();
state!.Delivered.Consumer.ShouldBe(persisted.Delivered.Consumer);
state.Delivered.Stream.ShouldBe(persisted.Delivered.Stream);
state.AckFloor.Consumer.ShouldBe(persisted.AckFloor.Consumer);
state.AckFloor.Stream.ShouldBe(persisted.AckFloor.Stream);
c2.Stop();
fs2.Stop();
}
finally
{
Directory.Delete(root, recursive: true);
}
}
}