Skip to content

Commit fbc84e7

Browse files
committed
Fixed #276
1 parent f647275 commit fbc84e7

File tree

9 files changed

+55
-27
lines changed

9 files changed

+55
-27
lines changed

src/cluster/DotNext.AspNetCore.Cluster/Net/Cluster/Consensus/Raft/Http/IRaftHttpCluster.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,27 @@ public interface IRaftHttpCluster : IRaftCluster, IMessageBus, IStandbyModeSuppo
1111
/// <summary>
1212
/// Announces a new member in the cluster.
1313
/// </summary>
14-
/// <param name="address">The addres of the cluster member.</param>
14+
/// <param name="address">The address of the cluster member.</param>
1515
/// <param name="token">The token that can be used to cancel the operation.</param>
1616
/// <returns>
1717
/// <see langword="true"/> if the node has been added to the cluster successfully;
1818
/// <see langword="false"/> if the node rejects the replication or the address of the node cannot be committed.
1919
/// </returns>
20+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
2021
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
2122
Task<bool> AddMemberAsync(Uri address, CancellationToken token = default);
2223

2324
/// <summary>
2425
/// Removes the member from the cluster.
2526
/// </summary>
26-
/// <param name="address">The addres of the cluster member.</param>
27+
/// <param name="address">The address of the cluster member.</param>
2728
/// <param name="token">The token that can be used to cancel the operation.</param>
2829
/// <returns>
2930
/// <see langword="true"/> if the node has been removed from the cluster successfully;
3031
/// <see langword="false"/> if the node rejects the replication or the address of the node cannot be committed.
3132
/// </returns>
33+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
34+
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
3235
Task<bool> RemoveMemberAsync(Uri address, CancellationToken token = default);
3336

3437
/// <summary>

src/cluster/DotNext.AspNetCore.Cluster/Net/Cluster/Consensus/Raft/Http/RaftHttpCluster.Messaging.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async Task<TResponse> IOutputChannel.SendMessageAsync<TResponse>(IMessage messag
3030
var tokenSource = CombineTokens([LifecycleToken, token]);
3131
do
3232
{
33-
var leader = Leader ?? throw new InvalidOperationException(ExceptionMessages.LeaderIsUnavailable);
33+
var leader = Leader ?? throw new QuorumUnreachableException();
3434
try
3535
{
3636
return await (leader.IsRemote
@@ -69,7 +69,7 @@ async Task<TResponse> IOutputChannel.SendMessageAsync<TResponse>(IMessage messag
6969
using var tokenSource = CombineTokens([token, LifecycleToken]);
7070
do
7171
{
72-
var leader = Leader ?? throw new InvalidOperationException(ExceptionMessages.LeaderIsUnavailable);
72+
var leader = Leader ?? throw new QuorumUnreachableException();
7373
try
7474
{
7575
return await (leader.IsRemote
@@ -110,7 +110,7 @@ async Task IOutputChannel.SendSignalAsync(IMessage message, CancellationToken to
110110
using var tokenSource = CombineTokens([token, LifecycleToken]);
111111
do
112112
{
113-
var leader = Leader ?? throw new InvalidOperationException(ExceptionMessages.LeaderIsUnavailable);
113+
var leader = Leader ?? throw new QuorumUnreachableException();
114114
try
115115
{
116116
var response = leader.IsRemote

src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftCluster.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public interface IRaftCluster : IReplicationCluster<IRaftLogEntry>, IPeerMesh<IR
7171
/// <param name="token">The token that can be used to cancel the operation.</param>
7272
/// <returns>The task representing asynchronous result.</returns>
7373
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
74+
/// <exception cref="QuorumUnreachableException">The quorum is not visible to the local node.</exception>
7475
ValueTask ApplyReadBarrierAsync(CancellationToken token = default);
7576

7677
/// <summary>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace DotNext.Net.Cluster.Consensus.Raft;
2+
3+
/// <summary>
4+
/// Indicates that the operation requires a leader in the cluster.
5+
/// </summary>
6+
public abstract class LeaderOperationException : InvalidOperationException
7+
{
8+
private protected LeaderOperationException(string? message, Exception? innerException)
9+
: base(message, innerException)
10+
{
11+
}
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace DotNext.Net.Cluster.Consensus.Raft;
2+
3+
/// <summary>
4+
/// Indicates that the operation cannot be performed on a node which is not a leader.
5+
/// </summary>
6+
/// <param name="innerException">The exception that is the cause of the current exception.</param>
7+
public sealed class NotLeaderException(Exception? innerException = null) : LeaderOperationException(ExceptionMessages.LocalNodeNotLeader, innerException);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace DotNext.Net.Cluster.Consensus.Raft;
2+
3+
/// <summary>
4+
/// Indicates that the operation cannot be performed on a node is unable to recognize the leader in the cluster.
5+
/// </summary>
6+
/// <param name="innerException">The exception that is the cause of the current exception.</param>
7+
public sealed class QuorumUnreachableException(Exception? innerException = null) : LeaderOperationException(ExceptionMessages.LeaderIsUnavailable, innerException);

src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/RaftCluster.DefaultImpl.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66
using System.Threading.Channels;
7+
using Microsoft.Extensions.Logging;
78

89
namespace DotNext.Net.Cluster.Consensus.Raft;
910

1011
using Buffers;
1112
using IO;
1213
using IO.Log;
1314
using Membership;
14-
using Microsoft.Extensions.Logging;
1515
using TransportServices;
1616

1717
/// <summary>
@@ -188,12 +188,13 @@ private RaftClusterMember CreateMember(EndPoint address)
188188
/// <summary>
189189
/// Announces a new member in the cluster.
190190
/// </summary>
191-
/// <param name="address">The addres of the cluster member.</param>
191+
/// <param name="address">The address of the cluster member.</param>
192192
/// <param name="token">The token that can be used to cancel the operation.</param>
193193
/// <returns>
194194
/// <see langword="true"/> if the node has been added to the cluster successfully;
195195
/// <see langword="false"/> if the node rejects the replication or the address of the node cannot be committed.
196196
/// </returns>
197+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
197198
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
198199
public async Task<bool> AddMemberAsync(EndPoint address, CancellationToken token = default)
199200
{
@@ -212,6 +213,8 @@ public async Task<bool> AddMemberAsync(EndPoint address, CancellationToken token
212213
/// <see langword="true"/> if the node has been removed from the cluster successfully;
213214
/// <see langword="false"/> if the node rejects the replication or the address of the node cannot be committed.
214215
/// </returns>
216+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
217+
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
215218
public Task<bool> RemoveMemberAsync(EndPoint address, CancellationToken token = default)
216219
=> RemoveMemberAsync(ClusterMemberId.FromEndPoint(address), ConfigurationStorage, GetAddress, token);
217220

src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/RaftCluster.Membership.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ public async ValueTask<bool> AddMemberAsync(TMember member, CancellationToken to
246246
/// </returns>
247247
/// <exception cref="ArgumentOutOfRangeException"><paramref name="rounds"/> is less than or equal to zero.</exception>
248248
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
249-
/// <exception cref="InvalidOperationException">The current node is not a leader.</exception>
249+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
250250
/// <exception cref="ConcurrentMembershipModificationException">The method is called concurrently.</exception>
251251
protected async Task<bool> AddMemberAsync<TAddress>(TMember member, int rounds, IClusterConfigurationStorage<TAddress> configurationStorage, Func<TMember, TAddress> addressProvider, CancellationToken token = default)
252252
where TAddress : notnull
@@ -293,7 +293,7 @@ protected async Task<bool> AddMemberAsync<TAddress>(TMember member, int rounds,
293293
}
294294
catch (OperationCanceledException e) when (e.CausedBy(tokenSource, leaderState.Token))
295295
{
296-
throw new InvalidOperationException(ExceptionMessages.LocalNodeNotLeader, e);
296+
throw new NotLeaderException(e);
297297
}
298298
catch (OperationCanceledException e) when (e.CancellationToken == tokenSource.Token)
299299
{
@@ -327,7 +327,7 @@ private ValueTask<Result<bool>> CatchUpAsync(TMember member, long commitIndex, l
327327
/// <see langword="true"/> if the node has been removed from the cluster successfully;
328328
/// <see langword="false"/> if the node rejects the replication or the address of the node cannot be committed.
329329
/// </returns>
330-
/// <exception cref="InvalidOperationException">The current node is not a leader.</exception>
330+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
331331
/// <exception cref="OperationCanceledException">The operation has been canceled or the cluster elects a new leader.</exception>
332332
/// <exception cref="ConcurrentMembershipModificationException">The method is called concurrently.</exception>
333333
protected async Task<bool> RemoveMemberAsync<TAddress>(ClusterMemberId id, IClusterConfigurationStorage<TAddress> configurationStorage, Func<TMember, TAddress> addressProvider, CancellationToken token = default)
@@ -357,7 +357,7 @@ protected async Task<bool> RemoveMemberAsync<TAddress>(ClusterMemberId id, IClus
357357
}
358358
catch (OperationCanceledException e) when (e.CausedBy(tokenSource, leaderState.Token))
359359
{
360-
throw new InvalidOperationException(ExceptionMessages.LocalNodeNotLeader, e);
360+
throw new NotLeaderException(e);
361361
}
362362
catch (OperationCanceledException e) when (e.CancellationToken == tokenSource.Token)
363363
{

src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/RaftCluster.cs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace DotNext.Net.Cluster.Consensus.Raft;
1111
using Extensions;
1212
using IO.Log;
1313
using Membership;
14+
using Replication;
1415
using Threading;
1516
using Threading.Tasks;
16-
using IReplicationCluster = Replication.IReplicationCluster;
1717

1818
/// <summary>
1919
/// Represents transport-independent implementation of Raft protocol.
@@ -158,7 +158,7 @@ public bool TryGetLeaseToken(out CancellationToken token)
158158

159159
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
160160
private LeaderState<TMember> LeaderStateOrException
161-
=> state as LeaderState<TMember> ?? throw new InvalidOperationException(ExceptionMessages.LocalNodeNotLeader);
161+
=> state as LeaderState<TMember> ?? throw new NotLeaderException();
162162

163163
/// <summary>
164164
/// Associates audit trail with the current instance.
@@ -914,7 +914,7 @@ protected async ValueTask<bool> ResignAsync(CancellationToken token)
914914
{
915915
leaderState.ForceReplication();
916916
}
917-
catch (InvalidOperationException)
917+
catch (NotLeaderException)
918918
{
919919
// local node is not a leader
920920
goto exit;
@@ -932,12 +932,7 @@ protected async ValueTask<bool> ResignAsync(CancellationToken token)
932932
return new(result);
933933
}
934934

935-
/// <summary>
936-
/// Ensures linearizable read from underlying state machine.
937-
/// </summary>
938-
/// <param name="token">The token that can be used to cancel the operation.</param>
939-
/// <returns>The task representing asynchronous result.</returns>
940-
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
935+
/// <inheritdoc cref="IRaftCluster.ApplyReadBarrierAsync"/>
941936
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
942937
public async ValueTask ApplyReadBarrierAsync(CancellationToken token = default)
943938
{
@@ -949,7 +944,7 @@ public async ValueTask ApplyReadBarrierAsync(CancellationToken token = default)
949944
{
950945
await leaderState.ForceReplicationAsync(token).ConfigureAwait(false);
951946
}
952-
catch (InvalidOperationException)
947+
catch (NotLeaderException)
953948
{
954949
// local node is not a leader, retry
955950
continue;
@@ -964,7 +959,7 @@ public async ValueTask ApplyReadBarrierAsync(CancellationToken token = default)
964959
}
965960
else
966961
{
967-
throw new InvalidOperationException(ExceptionMessages.LeaderIsUnavailable);
962+
throw new QuorumUnreachableException();
968963
}
969964

970965
break;
@@ -1233,14 +1228,14 @@ async void IRaftStateMachine<TMember>.UnavailableMemberDetected(IRaftStateMachin
12331228
/// Forces replication.
12341229
/// </summary>
12351230
/// <remarks>
1236-
/// This methods waits for responses from all available cluster members, not from the majority of them.
1231+
/// This method waits for responses from all available cluster members, not from the majority of them.
12371232
/// </remarks>
12381233
/// <param name="token">The token that can be used to cancel waiting.</param>
12391234
/// <returns>The task representing asynchronous result.</returns>
1240-
/// <exception cref="InvalidOperationException">The local cluster member is not a leader.</exception>
1235+
/// <exception cref="NotLeaderException">The local cluster member is not a leader.</exception>
12411236
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
12421237
public ValueTask ForceReplicationAsync(CancellationToken token = default)
1243-
=> (state as LeaderState<TMember>)?.ForceReplicationAsync(token) ?? ValueTask.FromException(new InvalidOperationException(ExceptionMessages.LocalNodeNotLeader));
1238+
=> (state as LeaderState<TMember>)?.ForceReplicationAsync(token) ?? ValueTask.FromException(new NotLeaderException());
12441239

12451240
/// <summary>
12461241
/// Appends a new log entry and ensures that it is replicated and committed.
@@ -1250,7 +1245,7 @@ public ValueTask ForceReplicationAsync(CancellationToken token = default)
12501245
/// <param name="token">The token that can be used to cancel the operation.</param>
12511246
/// <returns><see langword="true"/> if the appended log entry has been committed by the majority of nodes; <see langword="false"/> if retry is required.</returns>
12521247
/// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
1253-
/// <exception cref="InvalidOperationException">The current node is not a leader.</exception>
1248+
/// <exception cref="NotLeaderException">The current node is not a leader.</exception>
12541249
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
12551250
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
12561251
public async ValueTask<bool> ReplicateAsync<TEntry>(TEntry entry, CancellationToken token)
@@ -1273,7 +1268,7 @@ public async ValueTask<bool> ReplicateAsync<TEntry>(TEntry entry, CancellationTo
12731268
}
12741269
catch (OperationCanceledException e) when (e.CausedBy(tokenSource, leaderState.Token))
12751270
{
1276-
throw new InvalidOperationException(ExceptionMessages.LocalNodeNotLeader, e);
1271+
throw new NotLeaderException(e);
12771272
}
12781273
catch (OperationCanceledException e) when (e.CancellationToken == tokenSource.Token)
12791274
{

0 commit comments

Comments
 (0)