@@ -12,20 +12,30 @@ internal sealed partial class MultiGroupSubscriber
1212 public void Subscribe (
1313 RedisChannel channel ,
1414 Action < RedisChannel , RedisValue > handler ,
15- CommandFlags flags = CommandFlags . None ) => parent . SubscribeToAll ( channel , handler , flags , asyncState ) ;
15+ CommandFlags flags = CommandFlags . None ) => parent . SubscribeToAll ( new ( channel , handler , flags , asyncState ) ) ;
1616
1717 public Task SubscribeAsync (
1818 RedisChannel channel ,
1919 Action < RedisChannel , RedisValue > handler ,
20- CommandFlags flags = CommandFlags . None ) => parent . SubscribeToAllAsync ( channel , handler , flags , asyncState ) ;
20+ CommandFlags flags = CommandFlags . None ) => parent . SubscribeToAllAsync ( new ( channel , handler , flags , asyncState ) ) ;
2121
2222 public ChannelMessageQueue Subscribe (
2323 RedisChannel channel ,
24- CommandFlags flags = CommandFlags . None ) => throw new NotImplementedException ( "Soon" ) ;
24+ CommandFlags flags = CommandFlags . None )
25+ {
26+ var tuple = new MultiGroupMultiplexer . HandlerTuple ( channel , flags , asyncState ) ;
27+ parent . SubscribeToAll ( tuple ) ;
28+ return tuple . Queue ! ;
29+ }
2530
26- public Task < ChannelMessageQueue > SubscribeAsync (
31+ public async Task < ChannelMessageQueue > SubscribeAsync (
2732 RedisChannel channel ,
28- CommandFlags flags = CommandFlags . None ) => throw new NotImplementedException ( "Soon" ) ;
33+ CommandFlags flags = CommandFlags . None )
34+ {
35+ var tuple = new MultiGroupMultiplexer . HandlerTuple ( channel , flags , asyncState ) ;
36+ await parent . SubscribeToAllAsync ( tuple ) ;
37+ return tuple . Queue ! ;
38+ }
2939
3040 // to do this we'd need to track the *filtered* subscriber per node per subscriber per channel
3141 public void Unsubscribe (
@@ -57,37 +67,115 @@ private static Action<RedisChannel, RedisValue> FilteredHandler(MultiGroupMultip
5767 } ;
5868 }
5969
60- private readonly struct HandlerTuple ( RedisChannel channel , Action < RedisChannel , RedisValue > handler , CommandFlags flags , object ? asyncState )
70+ private static void ForwardFilteredMessages (
71+ MultiGroupMultiplexer parent ,
72+ ConnectionGroupMember active ,
73+ ChannelMessageQueue writeTo ,
74+ ChannelMessageQueue readFrom )
75+ {
76+ // Create an async worker; note we can't just use a handler-style callback, because the
77+ // key point of ChannelMessageQueue is to preserve order, and our callback implementation explicitly
78+ // does not guarantee anything about order.
79+ _ = Task . Run ( ( ) => ForwardFilteredMessagesAsync ( parent , active , writeTo , readFrom ) ) ;
80+ static async Task ForwardFilteredMessagesAsync (
81+ MultiGroupMultiplexer parent ,
82+ ConnectionGroupMember active ,
83+ ChannelMessageQueue writeTo ,
84+ ChannelMessageQueue readFrom )
85+ {
86+ try
87+ {
88+ while ( await readFrom . WaitToReadAsync ( ) )
89+ {
90+ while ( readFrom . TryRead ( out var message ) )
91+ {
92+ if ( ReferenceEquals ( parent . _active , active . Multiplexer ) )
93+ {
94+ // Because of the switchover being imperfect, we can't guarantee exactly one writer, so
95+ // we need to be synchronized; in reality, it will *almost never* be contended, so
96+ // this isn't a bottleneck - so we will pay the price of a lock here, and keep the
97+ // queue in single-writer mode.
98+ writeTo . SynchronizedWrite ( message . Channel , message . Message ) ;
99+ }
100+ }
101+ }
102+ }
103+ catch ( Exception ex )
104+ {
105+ parent . OnInternalError ( ex ) ;
106+ }
107+ }
108+ }
109+
110+ internal readonly struct HandlerTuple
61111 {
62- public readonly RedisChannel Channel = channel ;
63- public readonly Action < RedisChannel , RedisValue > Handler = handler ;
64- public readonly CommandFlags Flags = flags ;
65- public readonly object ? AsyncState = asyncState ;
112+ public readonly RedisChannel Channel ;
113+ public Action < RedisChannel , RedisValue > ? Handler => _handlerOrQueue as Action < RedisChannel , RedisValue > ;
114+ public ChannelMessageQueue ? Queue => _handlerOrQueue as ChannelMessageQueue ;
115+ public readonly CommandFlags Flags ;
116+ public readonly object ? AsyncState ;
117+
118+ private readonly object _handlerOrQueue ;
119+
120+ public HandlerTuple ( RedisChannel channel , Action < RedisChannel , RedisValue > handler , CommandFlags flags , object ? asyncState )
121+ {
122+ Channel = channel ;
123+ _handlerOrQueue = handler ;
124+ Flags = flags ;
125+ AsyncState = asyncState ;
126+ }
127+
128+ public HandlerTuple ( RedisChannel channel , CommandFlags flags , object ? asyncState )
129+ {
130+ Channel = channel ;
131+ // note: multi-writer because we can't rule out race conditions at switchover
132+ _handlerOrQueue = new ChannelMessageQueue ( channel , null ) ;
133+ Flags = flags ;
134+ AsyncState = asyncState ;
135+ }
66136 }
67137
68138 private List < HandlerTuple > _handlers = new ( ) ;
69139
70- public void SubscribeToAll ( RedisChannel channel , Action < RedisChannel , RedisValue > handler , CommandFlags flags , object ? asyncState )
140+ internal void SubscribeToAll ( HandlerTuple tuple )
71141 {
72142 lock ( _handlers )
73143 {
74- _handlers . Add ( new HandlerTuple ( channel , handler , flags , asyncState ) ) ;
144+ _handlers . Add ( tuple ) ;
75145 }
76146 foreach ( var member in _members )
77147 {
78- member . Multiplexer . GetSubscriber ( asyncState ) . Subscribe ( channel , FilteredHandler ( this , member , handler ) , flags ) ;
148+ var sub = member . Multiplexer . GetSubscriber ( tuple . AsyncState ) ;
149+ if ( tuple . Handler is not null )
150+ {
151+ sub . Subscribe ( tuple . Channel , FilteredHandler ( this , member , tuple . Handler ) , tuple . Flags ) ;
152+ }
153+ else if ( tuple . Queue is not null )
154+ {
155+ var from = sub . Subscribe ( tuple . Channel , tuple . Flags ) ;
156+ ForwardFilteredMessages ( this , member , tuple . Queue , from ) ;
157+ }
79158 }
80159 }
81160
82- public async Task SubscribeToAllAsync ( RedisChannel channel , Action < RedisChannel , RedisValue > handler , CommandFlags flags , object ? asyncState )
161+ internal async Task SubscribeToAllAsync ( HandlerTuple tuple )
83162 {
84163 lock ( _handlers )
85164 {
86- _handlers . Add ( new HandlerTuple ( channel , handler , flags , asyncState ) ) ;
165+ _handlers . Add ( tuple ) ;
87166 }
88167 foreach ( var member in _members )
89168 {
90- await member . Multiplexer . GetSubscriber ( asyncState ) . SubscribeAsync ( channel , FilteredHandler ( this , member , handler ) , flags ) ;
169+ var sub = member . Multiplexer . GetSubscriber ( tuple . AsyncState ) ;
170+ if ( tuple . Handler is not null )
171+ {
172+ await sub . SubscribeAsync ( tuple . Channel , FilteredHandler ( this , member , tuple . Handler ) , tuple . Flags ) ;
173+ }
174+ else if ( tuple . Queue is not null )
175+ {
176+ var from = await sub . SubscribeAsync ( tuple . Channel , tuple . Flags ) ;
177+ ForwardFilteredMessages ( this , member , tuple . Queue , from ) ;
178+ }
91179 }
92180 }
93181
@@ -110,16 +198,24 @@ private async Task AddPubSubHandlersAsync(ConnectionGroupMember member)
110198 // when adding a connection to an established group, add any missing pub/sub handlers
111199 var lease = LeaseHandlers ( out var count ) ;
112200 object ? asyncState = null ;
113- ISubscriber ? subscriber = null ; // try to reuse when possible
201+ ISubscriber ? sub = null ; // try to reuse when possible
114202 for ( int i = 0 ; i < count ; i ++ )
115203 {
116204 var tuple = lease [ i ] ;
117- if ( subscriber is null || tuple . AsyncState != asyncState )
205+ if ( sub is null || tuple . AsyncState != asyncState )
118206 {
119207 asyncState = tuple . AsyncState ;
120- subscriber = member . Multiplexer . GetSubscriber ( asyncState ) ;
208+ sub = member . Multiplexer . GetSubscriber ( asyncState ) ;
209+ }
210+ if ( tuple . Handler is not null )
211+ {
212+ await sub . SubscribeAsync ( tuple . Channel , FilteredHandler ( this , member , tuple . Handler ) , tuple . Flags ) ;
213+ }
214+ else if ( tuple . Queue is not null )
215+ {
216+ var from = await sub . SubscribeAsync ( tuple . Channel , tuple . Flags ) ;
217+ ForwardFilteredMessages ( this , member , tuple . Queue , from ) ;
121218 }
122- await subscriber . SubscribeAsync ( tuple . Channel , FilteredHandler ( this , member , tuple . Handler ) , tuple . Flags ) ;
123219 }
124220 ArrayPool < HandlerTuple > . Shared . Return ( lease ) ;
125221 }
0 commit comments