11using  System ; 
22using  System . Collections . Generic ; 
3+ using  System . Linq ; 
34using  System . Net ; 
45using  System . Threading ; 
56using  System . Threading . Tasks ; 
@@ -144,7 +145,7 @@ internal long ValidateSubscriptions()
144145        private  sealed  class  Subscription 
145146        { 
146147            private  Action < RedisChannel ,  RedisValue >  handler ; 
147-             private  ServerEndPoint   owner ; 
148+             private  List < ServerEndPoint >   owners   =   new   List < ServerEndPoint > ( ) ; 
148149
149150            public  Subscription ( Action < RedisChannel ,  RedisValue >  value )  =>  handler  =  value ; 
150151
@@ -170,33 +171,80 @@ public bool Remove(Action<RedisChannel, RedisValue> value)
170171            } 
171172
172173            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 ) 
173183            { 
174184                var  cmd  =  channel . IsPatternBased  ?  RedisCommand . PSUBSCRIBE  :  RedisCommand . SUBSCRIBE ; 
175185                var  selected  =  multiplexer . SelectServer ( - 1 ,  cmd ,  flags ,  default ( RedisKey ) ) ; 
176186
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+                 } 
178192
179193                var  msg  =  Message . Create ( - 1 ,  flags ,  cmd ,  channel ) ; 
180- 
181194                return  selected . QueueDirectAsync ( msg ,  ResultProcessor . TrackSubscriptions ,  asyncState ) ; 
182195            } 
183196
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+ 
184218            public  Task  UnsubscribeFromServer ( RedisChannel  channel ,  CommandFlags  flags ,  object  asyncState ,  bool  internalCall ) 
185219            { 
186-                 var  oldOwner  =  Interlocked . Exchange ( ref  owner ,  null ) ; 
187-                 if  ( oldOwner  ==  null )  return  null ; 
220+                 if  ( owners . Count  ==  0 )  return  null ; 
188221
222+                 List < Task >  queuedTasks  =  new  List < Task > ( ) ; 
189223                var  cmd  =  channel . IsPatternBased  ?  RedisCommand . PUNSUBSCRIBE  :  RedisCommand . UNSUBSCRIBE ; 
190224                var  msg  =  Message . Create ( - 1 ,  flags ,  cmd ,  channel ) ; 
191225                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 ( ) ) ; 
193230            } 
194231
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+             } 
196237
197238            internal  void  Resubscribe ( RedisChannel  channel ,  ServerEndPoint  server ) 
198239            { 
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 ) 
200248                { 
201249                    var  cmd  =  channel . IsPatternBased  ?  RedisCommand . PSUBSCRIBE  :  RedisCommand . SUBSCRIBE ; 
202250                    var  msg  =  Message . Create ( - 1 ,  CommandFlags . FireAndForget ,  cmd ,  channel ) ; 
@@ -208,16 +256,15 @@ internal void Resubscribe(RedisChannel channel, ServerEndPoint server)
208256            internal  bool  Validate ( ConnectionMultiplexer  multiplexer ,  RedisChannel  channel ) 
209257            { 
210258                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 ) ) ) 
213260                { 
214261                    if  ( UnsubscribeFromServer ( channel ,  CommandFlags . FireAndForget ,  null ,  true )  !=  null ) 
215262                    { 
216263                        changed  =  true ; 
217264                    } 
218-                     oldOwner   =   null ; 
265+                     owners . Clear ( ) ; 
219266                } 
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 ) 
221268                { 
222269                    changed  =  true ; 
223270                } 
0 commit comments