Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,28 @@ static RaftCluster CreateCluster(int port, bool coldStart, IPersistentState wal
}
}

[Theory]
[InlineData(2L, HeartbeatResult.ReplicatedWithLeaderTerm)]
[InlineData(3L, HeartbeatResult.Replicated)]
public Task EmptyAppendEntriesAfterEntry(long senderTerm, HeartbeatResult expected)
{
return EmptyAppendEntriesAfterEntryCore(CreateCluster, senderTerm, expected);

static RaftCluster CreateCluster(int port, bool coldStart, IPersistentState wal)
{
var config = new RaftCluster.TcpConfiguration(new IPEndPoint(IPAddress.Loopback, port))
{
ColdStart = coldStart,
ConfigurationStorage = null,
};

return new(config)
{
AuditTrail = wal,
};
}
}

[Fact]
public Task ClusterRecovery()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,23 @@ private protected async Task ClusterRecoveryCore(Func<int, bool, IPersistentStat
await host3.StopAsync();
}

private protected async Task EmptyAppendEntriesAfterEntryCore(Func<int, bool, IPersistentState, RaftCluster> clusterFactory, long senderTerm, HeartbeatResult expected)
{
// the follower's last entry is of term 2
await using var wal = CreateWal();
Equal(1L, await wal.AppendAsync(new BufferedEntry(2L, false, RandomBytes(16)), TestToken));
await using var follower = clusterFactory(3398, false, wal);
await follower.StartAsync(TestToken);

// a heartbeat with no entries after it: the follower's log holds an entry of the sender's term only if that one is
var result = await follower.As<ILocalMember>().AppendEntriesAsync(Random.Shared.Next<ClusterMemberId>(), senderTerm,
new LogEntryProducer<BufferedEntry>(), prevLogIndex: 1L, prevLogTerm: 2L, commitIndex: 1L, stateVersion: 0, TestToken);
Equal(expected, result.Value.Result);
Equal(1L, result.Value.LastIndex);

await follower.StopAsync(TestToken);
}

private protected async Task LeadershipCore(Func<int, bool, IPersistentState, RaftCluster> clusterFactory)
{
// first node - cold start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,10 @@ protected async ValueTask<Result<ReplicationStatus>> AppendEntriesAsync<TEntry>(
{
Value = new()
{
Result = IsReplicatedWithExpectedTerm(entries) ? HeartbeatResult.ReplicatedWithLeaderTerm : HeartbeatResult.Replicated,
// an empty request whose preceding entry is of the sender's term proves the log holds the leader's entries of its term too
Result = IsReplicatedWithExpectedTerm(entries) || (!notEmpty && prevLogTerm == senderTerm)
? HeartbeatResult.ReplicatedWithLeaderTerm
: HeartbeatResult.Replicated,
LastIndex = lastIndex,
}
};
Expand Down
Loading