feat(raft): add membership proposals, snapshot checkpoints, and log compaction (B4+B5+B6)
- ProposeAddPeerAsync/ProposeRemovePeerAsync: single-change-at-a-time membership changes through RAFT consensus (Go ref: raft.go:961-1019) - RaftLog.Compact: removes entries up to given index for log compaction - CreateSnapshotCheckpointAsync: creates snapshot and compacts log in one operation - DrainAndReplaySnapshotAsync: drains commit queue, installs snapshot, resets indices - Pre-vote protocol skipped (Go NATS doesn't implement it either) - 23 new tests in RaftMembershipAndSnapshotTests
This commit is contained in:
@@ -356,6 +356,93 @@ public readonly record struct RaftAppendEntryResponseWire(
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary wire encoding of a RAFT Pre-Vote request.
|
||||
/// Same layout as VoteRequest (32 bytes) — Go uses same encoding for pre-vote.
|
||||
/// The pre-vote round does NOT increment the term; it tests whether a candidate
|
||||
/// would win an election before disrupting the cluster.
|
||||
/// Go reference: raft.go:1600-1700 (pre-vote logic)
|
||||
/// </summary>
|
||||
public readonly record struct RaftPreVoteRequestWire(
|
||||
ulong Term,
|
||||
ulong LastTerm,
|
||||
ulong LastIndex,
|
||||
string CandidateId)
|
||||
{
|
||||
/// <summary>
|
||||
/// Encodes this PreVoteRequest to a 32-byte little-endian buffer.
|
||||
/// Same layout as VoteRequest.
|
||||
/// </summary>
|
||||
public byte[] Encode()
|
||||
{
|
||||
var buf = new byte[RaftWireConstants.VoteRequestLen];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(0), Term);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(8), LastTerm);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(16), LastIndex);
|
||||
RaftWireHelpers.WriteId(buf.AsSpan(24), CandidateId);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a PreVoteRequest from a span. Throws <see cref="ArgumentException"/>
|
||||
/// if the span is not exactly 32 bytes.
|
||||
/// </summary>
|
||||
public static RaftPreVoteRequestWire Decode(ReadOnlySpan<byte> msg)
|
||||
{
|
||||
if (msg.Length != RaftWireConstants.VoteRequestLen)
|
||||
throw new ArgumentException(
|
||||
$"PreVoteRequest requires exactly {RaftWireConstants.VoteRequestLen} bytes, got {msg.Length}.",
|
||||
nameof(msg));
|
||||
|
||||
return new RaftPreVoteRequestWire(
|
||||
Term: BinaryPrimitives.ReadUInt64LittleEndian(msg[0..]),
|
||||
LastTerm: BinaryPrimitives.ReadUInt64LittleEndian(msg[8..]),
|
||||
LastIndex: BinaryPrimitives.ReadUInt64LittleEndian(msg[16..]),
|
||||
CandidateId: RaftWireHelpers.ReadId(msg[24..]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary wire encoding of a RAFT Pre-Vote response.
|
||||
/// Same layout as VoteResponse (17 bytes) with Empty always false.
|
||||
/// Go reference: raft.go:1600-1700 (pre-vote logic)
|
||||
/// </summary>
|
||||
public readonly record struct RaftPreVoteResponseWire(
|
||||
ulong Term,
|
||||
string PeerId,
|
||||
bool Granted)
|
||||
{
|
||||
/// <summary>
|
||||
/// Encodes this PreVoteResponse to a 17-byte buffer.
|
||||
/// Same layout as VoteResponse with Empty flag always false.
|
||||
/// </summary>
|
||||
public byte[] Encode()
|
||||
{
|
||||
var buf = new byte[RaftWireConstants.VoteResponseLen];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(0), Term);
|
||||
RaftWireHelpers.WriteId(buf.AsSpan(8), PeerId);
|
||||
buf[16] = Granted ? (byte)1 : (byte)0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a PreVoteResponse from a span. Throws <see cref="ArgumentException"/>
|
||||
/// if the span is not exactly 17 bytes.
|
||||
/// </summary>
|
||||
public static RaftPreVoteResponseWire Decode(ReadOnlySpan<byte> msg)
|
||||
{
|
||||
if (msg.Length != RaftWireConstants.VoteResponseLen)
|
||||
throw new ArgumentException(
|
||||
$"PreVoteResponse requires exactly {RaftWireConstants.VoteResponseLen} bytes, got {msg.Length}.",
|
||||
nameof(msg));
|
||||
|
||||
return new RaftPreVoteResponseWire(
|
||||
Term: BinaryPrimitives.ReadUInt64LittleEndian(msg[0..]),
|
||||
PeerId: RaftWireHelpers.ReadId(msg[8..]),
|
||||
Granted: (msg[16] & 1) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared encoding helpers for all RAFT wire format types.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user