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,81 @@ 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
-
194
+ if ( internalCall ) msg . SetInternalCall ( ) ;
181
195
return selected . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ;
182
196
}
183
197
198
+ private Task SubscribeToMasters ( ConnectionMultiplexer multiplexer , RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
199
+ {
200
+ List < Task > subscribeTasks = new List < Task > ( ) ;
201
+ var cmd = channel . IsPatternBased ? RedisCommand . PSUBSCRIBE : RedisCommand . SUBSCRIBE ;
202
+ var masters = multiplexer . GetServerSnapshot ( ) . Where ( s => ! s . IsSlave && s . EndPoint . Equals ( s . ClusterConfiguration . Origin ) ) ;
203
+
204
+ lock ( owners )
205
+ {
206
+ foreach ( var master in masters )
207
+ {
208
+ if ( owners . Contains ( master ) ) continue ;
209
+ owners . Add ( master ) ;
210
+ var msg = Message . Create ( - 1 , flags , cmd , channel ) ;
211
+ if ( internalCall ) msg . SetInternalCall ( ) ;
212
+ subscribeTasks . Add ( master . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ) ;
213
+ }
214
+ }
215
+
216
+ return Task . WhenAll ( subscribeTasks ) ;
217
+ }
218
+
184
219
public Task UnsubscribeFromServer ( RedisChannel channel , CommandFlags flags , object asyncState , bool internalCall )
185
220
{
186
- var oldOwner = Interlocked . Exchange ( ref owner , null ) ;
187
- if ( oldOwner == null ) return null ;
221
+ if ( owners . Count == 0 ) return null ;
188
222
223
+ List < Task > queuedTasks = new List < Task > ( ) ;
189
224
var cmd = channel . IsPatternBased ? RedisCommand . PUNSUBSCRIBE : RedisCommand . UNSUBSCRIBE ;
190
225
var msg = Message . Create ( - 1 , flags , cmd , channel ) ;
191
226
if ( internalCall ) msg . SetInternalCall ( ) ;
192
- return oldOwner . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ;
227
+ foreach ( var owner in owners )
228
+ queuedTasks . Add ( owner . QueueDirectAsync ( msg , ResultProcessor . TrackSubscriptions , asyncState ) ) ;
229
+ owners . Clear ( ) ;
230
+ return Task . WhenAll ( queuedTasks . ToArray ( ) ) ;
193
231
}
194
232
195
- internal ServerEndPoint GetOwner ( ) => Interlocked . CompareExchange ( ref owner , null , null ) ;
233
+ internal ServerEndPoint GetOwner ( )
234
+ {
235
+ var owner = owners ? [ 0 ] ; // we subscribe to arbitrary server, so why not return one
236
+ return Interlocked . CompareExchange ( ref owner , null , null ) ;
237
+ }
196
238
197
239
internal void Resubscribe ( RedisChannel channel , ServerEndPoint server )
198
240
{
199
- if ( server != null && Interlocked . CompareExchange ( ref owner , server , server ) == server )
241
+ bool hasOwner ;
242
+
243
+ lock ( owners )
244
+ {
245
+ hasOwner = owners . Contains ( server ) ;
246
+ }
247
+
248
+ if ( server != null && hasOwner )
200
249
{
201
250
var cmd = channel . IsPatternBased ? RedisCommand . PSUBSCRIBE : RedisCommand . SUBSCRIBE ;
202
251
var msg = Message . Create ( - 1 , CommandFlags . FireAndForget , cmd , channel ) ;
@@ -208,16 +257,15 @@ internal void Resubscribe(RedisChannel channel, ServerEndPoint server)
208
257
internal bool Validate ( ConnectionMultiplexer multiplexer , RedisChannel channel )
209
258
{
210
259
bool changed = false ;
211
- var oldOwner = Interlocked . CompareExchange ( ref owner , null , null ) ;
212
- if ( oldOwner != null && ! oldOwner . IsSelectable ( RedisCommand . PSUBSCRIBE ) )
260
+ if ( owners . Count != 0 && ! owners . All ( o => o . IsSelectable ( RedisCommand . PSUBSCRIBE ) ) )
213
261
{
214
262
if ( UnsubscribeFromServer ( channel , CommandFlags . FireAndForget , null , true ) != null )
215
263
{
216
264
changed = true ;
217
265
}
218
- oldOwner = null ;
266
+ owners . Clear ( ) ;
219
267
}
220
- if ( oldOwner == null && SubscribeToServer ( multiplexer , channel , CommandFlags . FireAndForget , null , true ) != null )
268
+ if ( owners . Count == 0 && SubscribeToServer ( multiplexer , channel , CommandFlags . FireAndForget , null , true ) != null )
221
269
{
222
270
changed = true ;
223
271
}
0 commit comments