Skip to content

Let event subscribers attribute group-forwarded events to their member - #3258

Draft
mgravell wants to merge 1 commit into
mainfrom
marc/group-member-sender
Draft

mgravell wants to merge 1 commit into
mainfrom
marc/group-member-sender

Conversation

@mgravell

@mgravell mgravell commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Proposed resolution for #3249: let a subscriber to an event forwarded by a connection group say which member the event came from, without making ConnectionGroupMember.Multiplexer public.

Why not make Multiplexer public

The group owns each member's connection. It attaches the connection when the member connects, and clears it when the member is removed. Making Multiplexer public would turn an identity comparison into a contract on a full ConnectionMultiplexer, one that callers could dispose, close, or send commands to (bypassing group routing), or keep after the group has moved on. It also throws once the member is detached.

What this adds

  • ConnectionGroupMember.IsSenderOf(object? sender): whether sender is this member, or the connection the member currently holds. It is false for the group itself, for null, and for a member that has been removed.
  • ConnectionGroupExtensions.FindMember(this IConnectionGroup group, object? sender): the member an event came from, or null. It lives in StackExchange.Redis.Availability, alongside IConnectionGroup, so no new usings are needed.

Both are additive. IConnectionGroup is a public interface, so it is left untouched: adding members to it would break implementers, and default interface methods are not available on every target. ConnectionGroupMember is sealed.

This applies to every event the group forwards from its members, not only ServerMaintenanceEvent: ConnectionFailed, ConnectionRestored, ErrorMessage, HashSlotMoved, ConfigurationChanged, etc., all arrive with the member's own connection as sender.

Usage

The case from the issue:

group.ServerMaintenanceEvent += (sender, e) =>
{
    if (group.ActiveMember?.IsSenderOf(sender) != true) return; // not about the member currently in use
    // ...
};

Or, to attribute rather than filter:

group.ConnectionFailed += (sender, e) =>
{
    var member = group.FindMember(sender);
    logger.LogWarning("{Member}: {FailureType}", member?.Name ?? "(unknown)", e.FailureType);
};

The active member can change at any time, including just after such a check; that is inherent, and documented.

A note on MOVING

Server-native maintenance notifications (including MOVING) are currently disabled inside a multi-group connection; see Which deployments send these. That has been the case since #3191, which shipped in 3.3.0. So the specific scenario in the issue should not arise today. The docs say that restriction is expected to be lifted, though, and Azure's pub/sub maintenance events, which are not subject to it, already arrive through ServerMaintenanceEvent inside a group. Either way, how the library reacts to a notification remains an internal detail; this only lets callers know that it happened, and where.

Changes

  • IsSenderOf on ConnectionGroupMember, and a new ConnectionGroupExtensions with FindMember.
  • The group's internal GetMember now uses IsSenderOf, so there is one definition. It also no longer goes through the Multiplexer getter, which throws when nothing is attached.
  • GroupMemberSenderUnitTests uses two in-process servers and the real forwarding path. It covers:
    • events raised on each member's connection are attributed to that member;
    • ActiveMember?.IsSenderOf picks out exactly one member;
    • null, unrelated objects and the group itself are never claimed;
    • a removed member no longer claims its old connection.
  • docs/Failover.md has a new section, "Which member raised an event".

The full repo builds clean with /p:CI=true; unit and group tests pass on net10.0, and the new tests pass on net8.0.

Checklist

  • I fully and freely contribute this code in accordance with the project license (and am legally able to do so)
  • I take responsibility for this contribution's quality and correctness, including any portions produced with AI assistance (see CONTRIBUTING.md).

Adds ConnectionGroupMember.IsSenderOf(object?) and ConnectionGroupExtensions.FindMember, so that a
handler for an event forwarded by a connection group (ServerMaintenanceEvent, ConnectionFailed, ...)
can say which member it came from, without exposing the member's connection.

See #3249.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant