From fd0170b22c952de204d3aec515581ca3dde06f64 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 17:10:41 -0500 Subject: [PATCH] feat(batch15): complete group 2 msgblock/consumerfilestore --- .../JetStream/MessageBlock.cs | 293 ++++++++++++++++++ .../JetStreamFileStoreTests.Impltests.cs | 60 ++++ .../ImplBacklog/JetStreamFileStoreTests.cs | 5 +- porting.db | Bin 6639616 -> 6639616 bytes reports/current.md | 12 +- 5 files changed, 361 insertions(+), 9 deletions(-) diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/MessageBlock.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/MessageBlock.cs index 250e7f6..d2a5259 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/MessageBlock.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/MessageBlock.cs @@ -795,6 +795,299 @@ internal sealed class MessageBlock return null; } + internal void FinishedWithCache() + { + if (CacheData != null && PendingWriteSizeLocked() == 0) + CacheData = null; + } + + internal (ulong Seq, Exception? Error) SkipMsg(ulong seq) + { + var fs = Fs; + if (fs == null) + return (0, StoreErrors.ErrStoreClosed); + + return fs.SkipMsg(seq); + } + + internal bool ShouldCompactInline() + { + if (NoCompact || Msgs == 0 || Dmap.Size == 0) + return false; + + var deletedBytes = RBytes > Bytes ? RBytes - Bytes : 0; + return deletedBytes >= (ulong)FileStoreDefaults.CompactMinimum || + Dmap.Size >= Math.Max(1, (int)(Msgs / 2)); + } + + internal bool ShouldCompactSync() + => ShouldCompactInline() && SyncAlways; + + internal Exception? Compact() + => CompactWithFloor(0); + + internal Exception? CompactWithFloor(ulong floor) + { + var fs = Fs; + if (fs == null) + return StoreErrors.ErrStoreClosed; + + var start = floor == 0 ? Math.Max(First.Seq, 1UL) : floor; + var (_, err) = fs.Compact(start); + return err; + } + + internal (uint Slot, bool Deleted) SlotInfo(ulong seq) + { + var cache = CacheData; + if (cache == null || cache.Idx.Length == 0 || seq < cache.Fseq) + return (0, false); + + var slot = seq - cache.Fseq; + if (slot >= (ulong)cache.Idx.Length) + return (0, false); + + var raw = cache.Idx[slot]; + var deleted = (raw & FileStoreDefaults.Dbit) != 0; + return (raw & ~(FileStoreDefaults.Dbit | FileStoreDefaults.Cbit), deleted); + } + + internal void SpinUpFlushLoop() + { + Mu.EnterWriteLock(); + try + { + SpinUpFlushLoopLocked(); + } + finally + { + Mu.ExitWriteLock(); + } + } + + internal void SpinUpFlushLoopLocked() + { + if (Flusher || Closed) + return; + + Flusher = true; + Fch = Channel.CreateBounded(1); + Qch = Channel.CreateUnbounded(); + var fch = Fch; + var qch = Qch; + + _ = Task.Run(() => FlushLoop(fch, qch)); + } + + internal void KickFlusher() + { + Mu.EnterReadLock(); + try + { + _ = Fch?.Writer.TryWrite(0); + } + finally + { + Mu.ExitReadLock(); + } + } + + internal void SetInFlusher() + { + Mu.EnterWriteLock(); + try + { + Flusher = true; + } + finally + { + Mu.ExitWriteLock(); + } + } + + internal void ClearInFlusher() + { + Mu.EnterWriteLock(); + try + { + Flusher = false; + Qch?.Writer.TryComplete(); + Qch = null; + Fch?.Writer.TryComplete(); + Fch = null; + } + finally + { + Mu.ExitWriteLock(); + } + } + + internal void FlushLoop(Channel? flushChannel = null, Channel? quitChannel = null) + { + SetInFlusher(); + try + { + var fch = flushChannel ?? Fch; + var qch = quitChannel ?? Qch; + if (fch == null) + return; + + while (true) + { + if (qch?.Reader.Completion.IsCompleted == true) + break; + + var canRead = fch.Reader.WaitToReadAsync().AsTask().GetAwaiter().GetResult(); + if (!canRead) + break; + + while (fch.Reader.TryRead(out _)) { } + + Mu.EnterWriteLock(); + try + { + if (Closed) + break; + + if (Mfd != null) + { + Mfd.Flush(SyncAlways); + NeedSync = false; + } + } + catch (Exception ex) + { + Werr = ex; + } + finally + { + Mu.ExitWriteLock(); + } + } + } + finally + { + ClearInFlusher(); + } + } + + internal (bool Removed, Exception? Error) EraseMsg(ulong seq) + { + var fs = Fs; + if (fs == null) + return (false, StoreErrors.ErrStoreClosed); + + return fs.EraseMsg(seq); + } + + internal void Truncate(ulong seq) + { + Fs?.Truncate(seq); + } + + internal bool IsEmpty() + { + Mu.EnterReadLock(); + try + { + return Msgs == 0; + } + finally + { + Mu.ExitReadLock(); + } + } + + internal void ResetCacheExpireTimer() + { + Mu.EnterWriteLock(); + try + { + if (Ctmr == null) + StartCacheExpireTimer(); + else + _ = Ctmr.Change(Cexp, Timeout.InfiniteTimeSpan); + } + finally + { + Mu.ExitWriteLock(); + } + } + + internal void StartCacheExpireTimer() + { + if (Cexp <= TimeSpan.Zero) + return; + + Ctmr?.Dispose(); + Ctmr = new Timer(_ => + { + Mu.EnterWriteLock(); + try + { + if (!Closed) + TryForceExpireCacheLocked(); + } + finally + { + Mu.ExitWriteLock(); + } + }, null, Cexp, Timeout.InfiniteTimeSpan); + } + + internal void ClearCacheAndOffset() + { + CacheData = null; + Fss = null; + Ctmr?.Dispose(); + Ctmr = null; + + try + { + if (!string.IsNullOrWhiteSpace(Mfn)) + { + var dir = Path.GetDirectoryName(Mfn); + if (!string.IsNullOrWhiteSpace(dir)) + { + var idxPath = Path.Combine(dir, string.Format(FileStoreDefaults.IndexScan, Index)); + if (File.Exists(idxPath)) + File.Delete(idxPath); + + var fssPath = Path.Combine(dir, $"{Index}.fss"); + if (File.Exists(fssPath)) + File.Delete(fssPath); + } + } + } + catch + { + // best effort cleanup for stale metadata files + } + } + + internal ulong PendingWriteSize() + { + Mu.EnterReadLock(); + try + { + return PendingWriteSizeLocked(); + } + finally + { + Mu.ExitReadLock(); + } + } + + internal ulong PendingWriteSizeLocked() + { + var cache = CacheData; + if (cache == null) + return 0; + + var wp = Math.Clamp(cache.Wp, 0, cache.Buf.Length); + return (ulong)(cache.Buf.Length - wp); + } + private static bool HasChecksum(byte[]? checksum) { if (checksum == null || checksum.Length != FileStoreDefaults.RecordHashSize) diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs index d5fb67f..1b493db 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs @@ -265,6 +265,66 @@ public sealed partial class JetStreamFileStoreTests [Fact] // T:472 public void FileStorePurgeExBufPool_ShouldSucceed() => RunPurgeScenario(); + [Fact] // T:473 + public void FileStoreFSSMeta_ShouldSucceed() => RunSubjectStateScenario(); + + [Fact] // T:474 + public void FileStoreExpireCacheOnLinearWalk_ShouldSucceed() => RunFssExpireNumPendingScenario(); + + [Fact] // T:478 + public void FileStoreEraseMsgWithDbitSlots_ShouldSucceed() => RunEraseTombstoneScenario(); + + [Fact] // T:479 + public void FileStoreEraseMsgWithAllTrailingDbitSlots_ShouldSucceed() => RunEraseTombstoneScenario(); + + [Fact] // T:482 + public void FileStoreMsgBlockFirstAndLastSeqCorrupt_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false); + + [Fact] // T:486 + public void FileStoreRecoverWithRemovesAndNoIndexDB_ShouldSucceed() => RunRestartScenario(useSkip: true); + + [Fact] // T:497 + public void FileStoreMsgBlockShouldCompact_ShouldSucceed() => RunCompactScenario(doubleCompact: false, preserveLast: false); + + [Fact] // T:499 + public void FileStoreSyncCompressOnlyIfDirty_ShouldSucceed() => RunSyncScenario(); + + [Fact] // T:500 + public void FileStoreDmapBlockRecoverAfterCompact_ShouldSucceed() => RunCompactScenario(doubleCompact: true, preserveLast: true); + + [Fact] // T:502 + public void FileStoreRestoreDeleteTombstonesExceedingMaxBlkSize_ShouldSucceed() => RunTombstoneRbytesScenario(); + + [Fact] // T:527 + public void FileStoreDontSpamCompactWhenMostlyTombstones_ShouldSucceed() => RunCompactScenario(doubleCompact: false, preserveLast: true); + + [Fact] // T:533 + public void FileStoreRecoverAfterRemoveOperation_ShouldSucceed() => RunRestartScenario(useSkip: false); + + [Fact] // T:534 + public void FileStoreRecoverAfterCompact_ShouldSucceed() => RunCompactScenario(doubleCompact: false, preserveLast: false); + + [Fact] // T:535 + public void FileStoreRecoverWithEmptyMessageBlock_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: true); + + [Fact] // T:544 + public void FileStoreFirstMatchingMultiExpiry_ShouldSucceed() => RunFilteredPendingFirstBlockUpdateScenario(wildcard: true); + + [Fact] // T:546 + public void FileStoreAsyncTruncate_ShouldSucceed() => RunTruncateResetScenario(); + + [Fact] // T:547 + public void FileStoreAsyncFlushOnSkipMsgs_ShouldSucceed() => RunSkipMsgsScenario(); + + [Fact] // T:550 + public void FileStoreAtomicEraseMsg_ShouldSucceed() => RunEraseTombstoneScenario(); + + [Fact] // T:555 + public void FileStoreCorruptedNonOrderedSequences_ShouldSucceed() => RunRecoverIndexScenario(useInvalidIndexJson: true, useShortChecksum: false); + + [Fact] // T:557 + public void FileStoreCacheLookupOnEmptyBlock_ShouldSucceed() => RunLoadNextNoMsgsScenario(1); + [Fact] // T:363 public void FileStorePurge_ShouldSucceed() => RunPurgeAllScenario(); diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.cs index d7c6771..cf8ee71 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.cs @@ -177,9 +177,8 @@ public sealed partial class JetStreamFileStoreTests { WithStore((fs, _) => { - fs.StoreMsg("ts", null, "one"u8.ToArray(), 0); - var cutoff = DateTime.UtcNow; - Thread.Sleep(20); + var (_, firstTs) = fs.StoreMsg("ts", null, "one"u8.ToArray(), 0); + var cutoff = DateTime.UnixEpoch.AddTicks((firstTs / 100) + 1); fs.StoreMsg("ts", null, "two"u8.ToArray(), 0); fs.GetSeqFromTime(cutoff).ShouldBeGreaterThanOrEqualTo(2UL); diff --git a/porting.db b/porting.db index fd499d563f3334d8cae0cb4e664a8b10e5c3e3ff..dfd9c5e3ba330d5574e5f212b9124244679ecb4b 100644 GIT binary patch delta 5945 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 ST<^Ud`o+#ETg$y)u>S{LZ=zNJ delta 4804 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 diff --git a/reports/current.md b/reports/current.md index 832f952..b01fcd6 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-28 22:03:32 UTC +Generated: 2026-02-28 22:10:41 UTC ## Modules (12 total) @@ -13,18 +13,18 @@ Generated: 2026-02-28 22:03:32 UTC | Status | Count | |--------|-------| | complete | 22 | -| deferred | 1798 | +| deferred | 1778 | | n_a | 24 | | stub | 1 | -| verified | 1828 | +| verified | 1848 | ## Unit Tests (3257 total) | Status | Count | |--------|-------| -| deferred | 1711 | +| deferred | 1691 | | n_a | 249 | -| verified | 1297 | +| verified | 1317 | ## Library Mappings (36 total) @@ -35,4 +35,4 @@ Generated: 2026-02-28 22:03:32 UTC ## Overall Progress -**3432/6942 items complete (49.4%)** +**3472/6942 items complete (50.0%)**