@@ -148,6 +148,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
148
148
ac -> sub .replies .tail = NULL ;
149
149
ac -> sub .channels = channels ;
150
150
ac -> sub .patterns = patterns ;
151
+ ac -> sub .pending_unsubs = 0 ;
151
152
152
153
return ac ;
153
154
oom :
@@ -411,11 +412,11 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
411
412
static int __redisGetSubscribeCallback (redisAsyncContext * ac , redisReply * reply , redisCallback * dstcb ) {
412
413
redisContext * c = & (ac -> c );
413
414
dict * callbacks ;
414
- redisCallback * cb ;
415
+ redisCallback * cb = NULL ;
415
416
dictEntry * de ;
416
417
int pvariant ;
417
418
char * stype ;
418
- sds sname ;
419
+ sds sname = NULL ;
419
420
420
421
/* Match reply with the expected format of a pushed message.
421
422
* The type and number of elements (3 to 4) are specified at:
@@ -431,45 +432,44 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
431
432
else
432
433
callbacks = ac -> sub .channels ;
433
434
434
- /* Ignore replies without a channel/pattern string */
435
- if (reply -> element [1 ]-> type != REDIS_REPLY_STRING ) return REDIS_OK ;
436
-
437
435
/* Locate the right callback */
438
- sname = sdsnewlen (reply -> element [1 ]-> str ,reply -> element [1 ]-> len );
439
- if (sname == NULL )
440
- goto oom ;
441
-
442
- de = dictFind (callbacks ,sname );
443
- if (de != NULL ) {
444
- cb = dictGetEntryVal (de );
436
+ if (reply -> element [1 ]-> type == REDIS_REPLY_STRING ) {
437
+ sname = sdsnewlen (reply -> element [1 ]-> str ,reply -> element [1 ]-> len );
438
+ if (sname == NULL ) goto oom ;
445
439
446
- /* If this is an subscribe reply decrease pending counter. */
447
- if ( strcasecmp ( stype + pvariant , "subscribe" ) == 0 ) {
448
- cb -> pending_subs -= 1 ;
440
+ if (( de = dictFind ( callbacks , sname )) != NULL ) {
441
+ cb = dictGetEntryVal ( de );
442
+ memcpy ( dstcb , cb , sizeof ( * dstcb )) ;
449
443
}
444
+ }
450
445
451
- memcpy (dstcb ,cb ,sizeof (* dstcb ));
452
-
453
- /* If this is an unsubscribe message, remove it. */
454
- if (strcasecmp (stype + pvariant ,"unsubscribe" ) == 0 ) {
455
- if (cb -> pending_subs == 0 )
456
- dictDelete (callbacks ,sname );
457
-
458
- /* If this was the last unsubscribe message, revert to
459
- * non-subscribe mode. */
460
- assert (reply -> element [2 ]-> type == REDIS_REPLY_INTEGER );
461
-
462
- /* Unset subscribed flag only when no pipelined pending subscribe. */
463
- if (reply -> element [2 ]-> integer == 0
464
- && dictSize (ac -> sub .channels ) == 0
465
- && dictSize (ac -> sub .patterns ) == 0 ) {
466
- c -> flags &= ~REDIS_SUBSCRIBED ;
467
-
468
- /* Move ongoing regular command callbacks. */
469
- redisCallback cb ;
470
- while (__redisShiftCallback (& ac -> sub .replies ,& cb ) == REDIS_OK ) {
471
- __redisPushCallback (& ac -> replies ,& cb );
472
- }
446
+ /* If this is an subscribe reply decrease pending counter. */
447
+ if (strcasecmp (stype + pvariant ,"subscribe" ) == 0 ) {
448
+ assert (cb != NULL );
449
+ cb -> pending_subs -= 1 ;
450
+
451
+ } else if (strcasecmp (stype + pvariant ,"unsubscribe" ) == 0 ) {
452
+ if (cb == NULL )
453
+ ac -> sub .pending_unsubs -= 1 ;
454
+ else if (cb -> pending_subs == 0 )
455
+ dictDelete (callbacks ,sname );
456
+
457
+ /* If this was the last unsubscribe message, revert to
458
+ * non-subscribe mode. */
459
+ assert (reply -> element [2 ]-> type == REDIS_REPLY_INTEGER );
460
+
461
+ /* Unset subscribed flag only when no pipelined pending subscribe
462
+ * or pending unsubscribe replies. */
463
+ if (reply -> element [2 ]-> integer == 0
464
+ && dictSize (ac -> sub .channels ) == 0
465
+ && dictSize (ac -> sub .patterns ) == 0
466
+ && ac -> sub .pending_unsubs == 0 ) {
467
+ c -> flags &= ~REDIS_SUBSCRIBED ;
468
+
469
+ /* Move ongoing regular command callbacks. */
470
+ redisCallback cb ;
471
+ while (__redisShiftCallback (& ac -> sub .replies ,& cb ) == REDIS_OK ) {
472
+ __redisPushCallback (& ac -> replies ,& cb );
473
473
}
474
474
}
475
475
}
@@ -542,7 +542,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
542
542
543
543
/* Even if the context is subscribed, pending regular
544
544
* callbacks will get a reply before pub/sub messages arrive. */
545
- redisCallback cb = {NULL , NULL , 0 , NULL };
545
+ redisCallback cb = {NULL , NULL , 0 , 0 , NULL };
546
546
if (__redisShiftCallback (& ac -> replies ,& cb ) != REDIS_OK ) {
547
547
/*
548
548
* A spontaneous reply in a not-subscribed context can be the error
@@ -759,6 +759,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
759
759
redisContext * c = & (ac -> c );
760
760
redisCallback cb ;
761
761
struct dict * cbdict ;
762
+ dictIterator it ;
762
763
dictEntry * de ;
763
764
redisCallback * existcb ;
764
765
int pvariant , hasnext ;
@@ -775,6 +776,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
775
776
cb .fn = fn ;
776
777
cb .privdata = privdata ;
777
778
cb .pending_subs = 1 ;
779
+ cb .unsubscribe_sent = 0 ;
778
780
779
781
/* Find out which command will be appended. */
780
782
p = nextArgument (cmd ,& cstr ,& clen );
@@ -814,6 +816,51 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
814
816
* subscribed to one or more channels or patterns. */
815
817
if (!(c -> flags & REDIS_SUBSCRIBED )) return REDIS_ERR ;
816
818
819
+ if (pvariant )
820
+ cbdict = ac -> sub .patterns ;
821
+ else
822
+ cbdict = ac -> sub .channels ;
823
+
824
+ if (hasnext ) {
825
+ /* Send an unsubscribe with specific channels/patterns.
826
+ * Bookkeeping the number of expected replies */
827
+ while ((p = nextArgument (p ,& astr ,& alen )) != NULL ) {
828
+ sname = sdsnewlen (astr ,alen );
829
+ if (sname == NULL )
830
+ goto oom ;
831
+
832
+ de = dictFind (cbdict ,sname );
833
+ if (de != NULL ) {
834
+ existcb = dictGetEntryVal (de );
835
+ if (existcb -> unsubscribe_sent == 0 )
836
+ existcb -> unsubscribe_sent = 1 ;
837
+ else
838
+ /* Already sent, reply to be ignored */
839
+ ac -> sub .pending_unsubs += 1 ;
840
+ } else {
841
+ /* Not subscribed to, reply to be ignored */
842
+ ac -> sub .pending_unsubs += 1 ;
843
+ }
844
+ sdsfree (sname );
845
+ }
846
+ } else {
847
+ /* Send an unsubscribe without specific channels/patterns.
848
+ * Bookkeeping the number of expected replies */
849
+ int no_subs = 1 ;
850
+ dictInitIterator (& it ,cbdict );
851
+ while ((de = dictNext (& it )) != NULL ) {
852
+ existcb = dictGetEntryVal (de );
853
+ if (existcb -> unsubscribe_sent == 0 ) {
854
+ existcb -> unsubscribe_sent = 1 ;
855
+ no_subs = 0 ;
856
+ }
857
+ }
858
+ /* Unsubscribing to all channels/patterns, where none is
859
+ * subscribed to, results in a single reply to be ignored. */
860
+ if (no_subs == 1 )
861
+ ac -> sub .pending_unsubs += 1 ;
862
+ }
863
+
817
864
/* (P)UNSUBSCRIBE does not have its own response: every channel or
818
865
* pattern that is unsubscribed will receive a message. This means we
819
866
* should not append a callback function for this command. */
0 commit comments