feat: add FileStore tombstone, TTL & consumer state persistence (Task 2)
Port Go filestore tombstone/deletion tests, consumer state encode/decode, consumer file store persistence, and message TTL enforcement. Adds ConsumerStateCodec and ConsumerFileStore implementations. 17 new tests ported from filestore_test.go.
This commit is contained in:
@@ -26,6 +26,9 @@ public sealed class MsgBlock : IDisposable
|
||||
private readonly SafeFileHandle _handle;
|
||||
private readonly Dictionary<ulong, (long Offset, int Length)> _index = new();
|
||||
private readonly HashSet<ulong> _deleted = new();
|
||||
// Go: SkipMsg writes tombstone records with empty subject — tracked separately so
|
||||
// recovery can distinguish intentional sequence gaps from soft-deleted messages.
|
||||
private readonly HashSet<ulong> _skipSequences = new();
|
||||
private readonly long _maxBytes;
|
||||
private readonly ReaderWriterLockSlim _lock = new();
|
||||
private long _writeOffset; // Tracks the append position independently of FileStream.Position
|
||||
@@ -402,6 +405,7 @@ public sealed class MsgBlock : IDisposable
|
||||
|
||||
_index[sequence] = (offset, encoded.Length);
|
||||
_deleted.Add(sequence);
|
||||
_skipSequences.Add(sequence); // Track skip sequences separately for recovery
|
||||
// Note: intentionally NOT added to _cache since it is deleted.
|
||||
|
||||
if (_totalWritten == 0)
|
||||
@@ -447,6 +451,22 @@ public sealed class MsgBlock : IDisposable
|
||||
finally { _lock.ExitReadLock(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum skip-sequence written into this block (0 if none).
|
||||
/// Skip sequences are intentional tombstones from SkipMsg/SkipMsgs —
|
||||
/// they bump _last without storing a live message, so recovery must account
|
||||
/// for them when computing the high-water mark.
|
||||
/// </summary>
|
||||
public ulong MaxSkipSequence
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try { return _skipSequences.Count > 0 ? _skipSequences.Max() : 0UL; }
|
||||
finally { _lock.ExitReadLock(); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the set of soft-deleted sequence numbers for read-only inspection.
|
||||
/// Reference: golang/nats-server/server/filestore.go — dmap access for state queries.
|
||||
@@ -582,7 +602,12 @@ public sealed class MsgBlock : IDisposable
|
||||
_index[record.Sequence] = (offset, recordLength);
|
||||
|
||||
if (record.Deleted)
|
||||
{
|
||||
_deleted.Add(record.Sequence);
|
||||
// Empty subject = skip/tombstone record (from SkipMsg/SkipMsgs).
|
||||
if (string.IsNullOrEmpty(record.Subject))
|
||||
_skipSequences.Add(record.Sequence);
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
_firstSequence = record.Sequence;
|
||||
|
||||
Reference in New Issue
Block a user