fix(cluster): gossiped-role predicates compare case-insensitively — a miscased peer no longer un-elects the Primary

This commit is contained in:
Joseph Doherty
2026-08-20 10:04:16 -04:00
parent bcec88ef70
commit e46c1db5e5
12 changed files with 370 additions and 11 deletions
@@ -23,8 +23,13 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable
private readonly string? _clusterRole;
private readonly string? _clusterId;
private readonly object _lock = new();
private readonly Dictionary<string, Member?> _roleLeaders = new(StringComparer.Ordinal);
private readonly Dictionary<string, HashSet<Member>> _membersByRole = new(StringComparer.Ordinal);
// R3-04 (01/C-9, census row 17): OrdinalIgnoreCase — Akka compares gossiped role names ordinally,
// so a PEER running an older build can still gossip a miscased plain role during a rolling
// upgrade. Keying these by the exact-cased role string would leave MembersWithRole("driver") and
// RoleLeader("driver") blind to a peer that gossips "Driver", the same class of gap #532 closed
// for _localRoles above.
private readonly Dictionary<string, Member?> _roleLeaders = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, HashSet<Member>> _membersByRole = new(StringComparer.OrdinalIgnoreCase);
private IActorRef? _subscriber;
/// <summary>Initializes a new instance of the ClusterRoleInfo class.</summary>
@@ -153,4 +153,15 @@ public static class RoleParser
return roles;
}
/// <summary>True when a GOSSIPED role list carries <paramref name="role"/>, compared
/// case-insensitively. Akka compares role names ordinally and a PEER may be running an older
/// build that gossips a verbatim 'Driver' (archreview 01/C-9), so every predicate over
/// <c>Member.Roles</c> goes through here rather than <c>List.Contains</c>.</summary>
/// <param name="gossipedRoles">The gossiped role list (e.g. <c>Member.Roles</c>).</param>
/// <param name="role">The role to test for.</param>
/// <returns>True when <paramref name="role"/> is present, case-insensitively.</returns>
public static bool GossipedRolesHave(IEnumerable<string>? gossipedRoles, string role) =>
gossipedRoles is not null
&& gossipedRoles.Any(r => string.Equals(r, role, StringComparison.OrdinalIgnoreCase));
}