1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Linq ;
3
4
using System . Net ;
4
5
using System . Threading ;
5
6
using System . Threading . Tasks ;
@@ -144,7 +145,7 @@ internal long ValidateSubscriptions()
144
145
private sealed class Subscription
145
146
{
146
147
private Action < RedisChannel , RedisValue > handler ;
147
- private ServerEndPoint owner ;
148
+ private List < ServerEndPoint > owners = new List < ServerEndPoint > ( ) ;
148
149
149
150
public Subscription ( Action < RedisChannel , RedisValue > value ) => handler = value ;
150
151
@@ -170,33 +171,80 @@ public bool Remove(Action<RedisChannel, RedisValue> value)
170
171
}
171
172
172
173
public Task SubscribeToServer ( ConnectionMultiplexer multiplexer , RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
174
+ {
175
+ // subscribe to all masters in cluster for keyspace/keyevent notifications
176
+ if ( channel . IsKeyspaceChannel ) {
177
+ return SubscribeToMasters ( multiplexer , channel , flags , asyncState , internalCall ) ;
178
+ }
179
+ return SubscribeToSingleServer ( multiplexer , channel , flags , asyncState , internalCall ) ;
180
+ }
181
+
182
+ private Task SubscribeToSingleServer ( ConnectionMultiplexer multiplexer , RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
173
183
{
174
184
var cmd = channel . IsPatternBased ? RedisCommand . PSUBSCRIBE : RedisCommand . SUBSCRIBE ;
175
185
var selected = multiplexer . SelectServer ( - 1 , cmd , flags , default ( RedisKey ) ) ;
176
186
177
- if ( selected == null || Interlocked . CompareExchange ( ref owner , selected , null ) != null ) return null ;
187
+ lock ( owners )
188
+ {
189
+ if ( selected == null || owners . Contains ( selected ) ) return null ;
190
+ owners . Add ( selected ) ;
191
+ }
178
192
179
193
var msg = Message . Create ( - 1 , flags , cmd , channel ) ;
180
-
181
194
return selected . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ;
182
195
}
183
196
197
+ private Task SubscribeToMasters ( ConnectionMultiplexer multiplexer , RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
198
+ {
199
+ List < Task > subscribeTasks = new List < Task > ( ) ;
200
+ var cmd = channel . IsPatternBased ? RedisCommand . PSUBSCRIBE : RedisCommand . SUBSCRIBE ;
201
+ var masters = multiplexer . GetServerSnapshot ( ) . Where ( s => ! s . IsSlave && s . EndPoint . Equals ( s . ClusterConfiguration . Origin ) ) ;
202
+
203
+ lock ( owners )
204
+ {
205
+ foreach ( var master in masters )
206
+ {
207
+ if ( owners . Contains ( master ) ) continue ;
208
+ owners . Add ( master ) ;
209
+ var msg = Message . Create ( - 1 , flags , cmd , channel ) ;
210
+ if ( internalCall ) msg . FlagsRaw = msg . FlagsRaw | ( CommandFlags ) 128 ;
211
+ subscribeTasks . Add ( master . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ) ;
212
+ }
213
+ }
214
+
215
+ return Task . WhenAll ( subscribeTasks ) ;
216
+ }
217
+
184
218
public Task UnsubscribeFromServer ( RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
185
219
{
186
- var oldOwner = Interlocked . Exchange ( ref owner , null ) ;
187
- if ( oldOwner == null ) return null ;
220
+ if ( owners . Count == 0 ) return null ;
188
221
222
+ List < Task > queuedTasks = new List < Task > ( ) ;
189
223
var cmd = channel . IsPatternBased ? RedisCommand . PUNSUBSCRIBE : RedisCommand . UNSUBSCRIBE ;
190
224
var msg = Message . Create ( - 1 , flags , cmd , channel ) ;
191
225
if ( internalCall ) msg . SetInternalCall ( ) ;
192
- return oldOwner . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ;
226
+ foreach ( var owner in owners )
227
+ queuedTasks . Add ( owner . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ) ;
228
+ owners . Clear ( ) ;
229
+ return Task . WhenAll ( queuedTasks . ToArray ( ) ) ;
193
230
}
194
231
195
- internal ServerEndPoint GetOwner ( ) => Interlocked . CompareExchange ( ref owner , null , null ) ;
232
+ internal ServerEndPoint GetOwner ( )
233
+ {
234
+ var owner = owners ? [ 0 ] ; // we subscribe to arbitrary server, so why not return one
235
+ return Interlocked . CompareExchange ( ref owner , null , null ) ;
236
+ }
196
237
197
238
internal void Resubscribe ( RedisChannel channel , ServerEndPoint server )
198
239
{
199
- if ( server != null && Interlocked . CompareExchange ( ref owner , server , server ) == server )
240
+ bool hasOwner ;
241
+
242
+ lock ( owners )
243
+ {
244
+ hasOwner = owners . Contains ( server ) ;
245
+ }
246
+
247
+ if ( server != null && hasOwner )
200
248
{
201
249
var cmd = channel . IsPatternBased ? RedisCommand . PSUBSCRIBE : RedisCommand . SUBSCRIBE ;
202
250
var msg = Message . Create ( - 1 , CommandFlags . FireAndForget , cmd , channel ) ;
@@ -208,16 +256,15 @@ internal void Resubscribe(RedisChannel channel, ServerEndPoint server)
208
256
internal bool Validate ( ConnectionMultiplexer multiplexer , RedisChannel channel )
209
257
{
210
258
bool changed = false ;
211
- var oldOwner = Interlocked . CompareExchange ( ref owner , null , null ) ;
212
- if ( oldOwner != null && ! oldOwner . IsSelectable ( RedisCommand . PSUBSCRIBE ) )
259
+ if ( owners . Count != 0 && ! owners . All ( o => o . IsSelectable ( RedisCommand . PSUBSCRIBE ) ) )
213
260
{
214
261
if ( UnsubscribeFromServer ( channel , CommandFlags . FireAndForget , null , true ) != null )
215
262
{
216
263
changed = true ;
217
264
}
218
- oldOwner = null ;
265
+ owners . Clear ( ) ;
219
266
}
220
- if ( oldOwner == null && SubscribeToServer ( multiplexer , channel , CommandFlags . FireAndForget , null , true ) != null )
267
+ if ( owners . Count == 0 && SubscribeToServer ( multiplexer , channel , CommandFlags . FireAndForget , null , true ) != null )
221
268
{
222
269
changed = true ;
223
270
}
0 commit comments