feat(storage): port FileStore Go tests and add sync methods (Go parity)
Add 67 Go-parity tests from filestore_test.go covering: - SkipMsg/SkipMsgs sequence reservation - RemoveMsg/EraseMsg soft-delete - LoadMsg/LoadLastMsg/LoadNextMsg message retrieval - AllLastSeqs/MultiLastSeqs per-subject last sequences - SubjectForSeq reverse lookup - NumPending with filters and last-per-subject mode - Recovery watermark preservation after purge - FastState NumDeleted/LastTime correctness - PurgeEx with empty subject + keep parameter - Compact _first watermark tracking - Multi-block operations and state verification Implements missing IStreamStore sync methods on FileStore: RemoveMsg, EraseMsg, SkipMsg, SkipMsgs, LoadMsg, LoadLastMsg, LoadNextMsg, AllLastSeqs, MultiLastSeqs, SubjectForSeq, NumPending. Adds MsgBlock.WriteSkip() for tombstone sequence reservation. Adds IDisposable to FileStore for synchronous test disposal.
This commit is contained in:
@@ -367,6 +367,56 @@ public sealed class MsgBlock : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a skip record for the given sequence number — reserves the sequence
|
||||
/// without storing actual message data. The record is written with the Deleted
|
||||
/// flag set so recovery skips it when rebuilding the in-memory message cache.
|
||||
/// This mirrors Go's SkipMsg tombstone behaviour.
|
||||
/// Reference: golang/nats-server/server/filestore.go — SkipMsg.
|
||||
/// </summary>
|
||||
public void WriteSkip(ulong sequence)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_writeOffset >= _maxBytes)
|
||||
throw new InvalidOperationException("Block is sealed; cannot write skip record.");
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
var record = new MessageRecord
|
||||
{
|
||||
Sequence = sequence,
|
||||
Subject = string.Empty,
|
||||
Headers = ReadOnlyMemory<byte>.Empty,
|
||||
Payload = ReadOnlyMemory<byte>.Empty,
|
||||
Timestamp = now,
|
||||
Deleted = true, // skip = deleted from the start
|
||||
};
|
||||
|
||||
var encoded = MessageRecord.Encode(record);
|
||||
var offset = _writeOffset;
|
||||
|
||||
RandomAccess.Write(_handle, encoded, offset);
|
||||
_writeOffset = offset + encoded.Length;
|
||||
|
||||
_index[sequence] = (offset, encoded.Length);
|
||||
_deleted.Add(sequence);
|
||||
// Note: intentionally NOT added to _cache since it is deleted.
|
||||
|
||||
if (_totalWritten == 0)
|
||||
_firstSequence = sequence;
|
||||
|
||||
_lastSequence = Math.Max(_lastSequence, sequence);
|
||||
_nextSequence = Math.Max(_nextSequence, sequence + 1);
|
||||
_totalWritten++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the write cache, releasing memory. After this call, all reads will
|
||||
/// go to disk. Called when the block is sealed (no longer the active block)
|
||||
|
||||
Reference in New Issue
Block a user