@@ -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:
@@ -432,42 +433,43 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
432
433
callbacks = ac -> sub .channels ;
433
434
434
435
/* Locate the right callback */
435
- assert (reply -> element [1 ]-> type == REDIS_REPLY_STRING );
436
- sname = sdsnewlen (reply -> element [1 ]-> str ,reply -> element [1 ]-> len );
437
- if (sname == NULL )
438
- goto oom ;
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 ;
439
439
440
- de = dictFind (callbacks ,sname );
441
- if (de != NULL ) {
442
- cb = dictGetEntryVal (de );
443
-
444
- /* If this is an subscribe reply decrease pending counter. */
445
- if (strcasecmp (stype + pvariant ,"subscribe" ) == 0 ) {
446
- cb -> pending_subs -= 1 ;
440
+ if ((de = dictFind (callbacks ,sname )) != NULL ) {
441
+ cb = dictGetEntryVal (de );
442
+ memcpy (dstcb ,cb ,sizeof (* dstcb ));
447
443
}
444
+ }
448
445
449
- memcpy (dstcb ,cb ,sizeof (* dstcb ));
450
-
451
- /* If this is an unsubscribe message, remove it. */
452
- if (strcasecmp (stype + pvariant ,"unsubscribe" ) == 0 ) {
453
- if (cb -> pending_subs == 0 )
454
- dictDelete (callbacks ,sname );
455
-
456
- /* If this was the last unsubscribe message, revert to
457
- * non-subscribe mode. */
458
- assert (reply -> element [2 ]-> type == REDIS_REPLY_INTEGER );
459
-
460
- /* Unset subscribed flag only when no pipelined pending subscribe. */
461
- if (reply -> element [2 ]-> integer == 0
462
- && dictSize (ac -> sub .channels ) == 0
463
- && dictSize (ac -> sub .patterns ) == 0 ) {
464
- c -> flags &= ~REDIS_SUBSCRIBED ;
465
-
466
- /* Move ongoing regular command callbacks. */
467
- redisCallback cb ;
468
- while (__redisShiftCallback (& ac -> sub .replies ,& cb ) == REDIS_OK ) {
469
- __redisPushCallback (& ac -> replies ,& cb );
470
- }
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 );
471
473
}
472
474
}
473
475
}
@@ -540,7 +542,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
540
542
541
543
/* Even if the context is subscribed, pending regular
542
544
* callbacks will get a reply before pub/sub messages arrive. */
543
- redisCallback cb = {NULL , NULL , 0 , NULL };
545
+ redisCallback cb = {NULL , NULL , 0 , 0 , NULL };
544
546
if (__redisShiftCallback (& ac -> replies ,& cb ) != REDIS_OK ) {
545
547
/*
546
548
* A spontaneous reply in a not-subscribed context can be the error
@@ -757,6 +759,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
757
759
redisContext * c = & (ac -> c );
758
760
redisCallback cb ;
759
761
struct dict * cbdict ;
762
+ dictIterator it ;
760
763
dictEntry * de ;
761
764
redisCallback * existcb ;
762
765
int pvariant , hasnext ;
@@ -773,6 +776,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
773
776
cb .fn = fn ;
774
777
cb .privdata = privdata ;
775
778
cb .pending_subs = 1 ;
779
+ cb .unsubscribe_sent = 0 ;
776
780
777
781
/* Find out which command will be appended. */
778
782
p = nextArgument (cmd ,& cstr ,& clen );
@@ -812,6 +816,51 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
812
816
* subscribed to one or more channels or patterns. */
813
817
if (!(c -> flags & REDIS_SUBSCRIBED )) return REDIS_ERR ;
814
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
+
815
864
/* (P)UNSUBSCRIBE does not have its own response: every channel or
816
865
* pattern that is unsubscribed will receive a message. This means we
817
866
* should not append a callback function for this command. */
0 commit comments