xref: /redis-3.2.3/src/pubsub.c (revision 79d2f70f)
1 /*
2  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *   * Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *   * Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *   * Neither the name of Redis nor the names of its contributors may be used
14  *     to endorse or promote products derived from this software without
15  *     specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "redis.h"
31 
32 /*-----------------------------------------------------------------------------
33  * Pubsub low level API
34  *----------------------------------------------------------------------------*/
35 
36 void freePubsubPattern(void *p) {
37     pubsubPattern *pat = p;
38 
39     decrRefCount(pat->pattern);
40     zfree(pat);
41 }
42 
43 int listMatchPubsubPattern(void *a, void *b) {
44     pubsubPattern *pa = a, *pb = b;
45 
46     return (pa->client == pb->client) &&
47            (equalStringObjects(pa->pattern,pb->pattern));
48 }
49 
50 /* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
51  * 0 if the client was already subscribed to that channel. */
52 int pubsubSubscribeChannel(redisClient *c, robj *channel) {
53     dictEntry *de;
54     list *clients = NULL;
55     int retval = 0;
56 
57     /* Add the channel to the client -> channels hash table */
58     if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
59         retval = 1;
60         incrRefCount(channel);
61         /* Add the client to the channel -> list of clients hash table */
62         de = dictFind(server.pubsub_channels,channel);
63         if (de == NULL) {
64             clients = listCreate();
65             dictAdd(server.pubsub_channels,channel,clients);
66             incrRefCount(channel);
67         } else {
68             clients = dictGetVal(de);
69         }
70         listAddNodeTail(clients,c);
71     }
72     /* Notify the client */
73     addReply(c,shared.mbulkhdr[3]);
74     addReply(c,shared.subscribebulk);
75     addReplyBulk(c,channel);
76     addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
77     return retval;
78 }
79 
80 /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
81  * 0 if the client was not subscribed to the specified channel. */
82 int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
83     dictEntry *de;
84     list *clients;
85     listNode *ln;
86     int retval = 0;
87 
88     /* Remove the channel from the client -> channels hash table */
89     incrRefCount(channel); /* channel may be just a pointer to the same object
90                             we have in the hash tables. Protect it... */
91     if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
92         retval = 1;
93         /* Remove the client from the channel -> clients list hash table */
94         de = dictFind(server.pubsub_channels,channel);
95         redisAssertWithInfo(c,NULL,de != NULL);
96         clients = dictGetVal(de);
97         ln = listSearchKey(clients,c);
98         redisAssertWithInfo(c,NULL,ln != NULL);
99         listDelNode(clients,ln);
100         if (listLength(clients) == 0) {
101             /* Free the list and associated hash entry at all if this was
102              * the latest client, so that it will be possible to abuse
103              * Redis PUBSUB creating millions of channels. */
104             dictDelete(server.pubsub_channels,channel);
105         }
106     }
107     /* Notify the client */
108     if (notify) {
109         addReply(c,shared.mbulkhdr[3]);
110         addReply(c,shared.unsubscribebulk);
111         addReplyBulk(c,channel);
112         addReplyLongLong(c,dictSize(c->pubsub_channels)+
113                        listLength(c->pubsub_patterns));
114 
115     }
116     decrRefCount(channel); /* it is finally safe to release it */
117     return retval;
118 }
119 
120 /* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
121 int pubsubSubscribePattern(redisClient *c, robj *pattern) {
122     int retval = 0;
123 
124     if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
125         retval = 1;
126         pubsubPattern *pat;
127         listAddNodeTail(c->pubsub_patterns,pattern);
128         incrRefCount(pattern);
129         pat = zmalloc(sizeof(*pat));
130         pat->pattern = getDecodedObject(pattern);
131         pat->client = c;
132         listAddNodeTail(server.pubsub_patterns,pat);
133     }
134     /* Notify the client */
135     addReply(c,shared.mbulkhdr[3]);
136     addReply(c,shared.psubscribebulk);
137     addReplyBulk(c,pattern);
138     addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
139     return retval;
140 }
141 
142 /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
143  * 0 if the client was not subscribed to the specified channel. */
144 int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
145     listNode *ln;
146     pubsubPattern pat;
147     int retval = 0;
148 
149     incrRefCount(pattern); /* Protect the object. May be the same we remove */
150     if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
151         retval = 1;
152         listDelNode(c->pubsub_patterns,ln);
153         pat.client = c;
154         pat.pattern = pattern;
155         ln = listSearchKey(server.pubsub_patterns,&pat);
156         listDelNode(server.pubsub_patterns,ln);
157     }
158     /* Notify the client */
159     if (notify) {
160         addReply(c,shared.mbulkhdr[3]);
161         addReply(c,shared.punsubscribebulk);
162         addReplyBulk(c,pattern);
163         addReplyLongLong(c,dictSize(c->pubsub_channels)+
164                        listLength(c->pubsub_patterns));
165     }
166     decrRefCount(pattern);
167     return retval;
168 }
169 
170 /* Unsubscribe from all the channels. Return the number of channels the
171  * client was subscribed from. */
172 int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
173     dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
174     dictEntry *de;
175     int count = 0;
176 
177     while((de = dictNext(di)) != NULL) {
178         robj *channel = dictGetKey(de);
179 
180         count += pubsubUnsubscribeChannel(c,channel,notify);
181     }
182     /* We were subscribed to nothing? Still reply to the client. */
183     if (notify && count == 0) {
184         addReply(c,shared.mbulkhdr[3]);
185         addReply(c,shared.unsubscribebulk);
186         addReply(c,shared.nullbulk);
187         addReplyLongLong(c,dictSize(c->pubsub_channels)+
188                        listLength(c->pubsub_patterns));
189     }
190     dictReleaseIterator(di);
191     return count;
192 }
193 
194 /* Unsubscribe from all the patterns. Return the number of patterns the
195  * client was subscribed from. */
196 int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) {
197     listNode *ln;
198     listIter li;
199     int count = 0;
200 
201     listRewind(c->pubsub_patterns,&li);
202     while ((ln = listNext(&li)) != NULL) {
203         robj *pattern = ln->value;
204 
205         count += pubsubUnsubscribePattern(c,pattern,notify);
206     }
207     if (notify && count == 0) {
208         /* We were subscribed to nothing? Still reply to the client. */
209         addReply(c,shared.mbulkhdr[3]);
210         addReply(c,shared.punsubscribebulk);
211         addReply(c,shared.nullbulk);
212         addReplyLongLong(c,dictSize(c->pubsub_channels)+
213                        listLength(c->pubsub_patterns));
214     }
215     return count;
216 }
217 
218 /* Publish a message */
219 int pubsubPublishMessage(robj *channel, robj *message) {
220     int receivers = 0;
221     dictEntry *de;
222     listNode *ln;
223     listIter li;
224 
225     /* Send to clients listening for that channel */
226     de = dictFind(server.pubsub_channels,channel);
227     if (de) {
228         list *list = dictGetVal(de);
229         listNode *ln;
230         listIter li;
231 
232         listRewind(list,&li);
233         while ((ln = listNext(&li)) != NULL) {
234             redisClient *c = ln->value;
235 
236             addReply(c,shared.mbulkhdr[3]);
237             addReply(c,shared.messagebulk);
238             addReplyBulk(c,channel);
239             addReplyBulk(c,message);
240             receivers++;
241         }
242     }
243     /* Send to clients listening to matching channels */
244     if (listLength(server.pubsub_patterns)) {
245         listRewind(server.pubsub_patterns,&li);
246         channel = getDecodedObject(channel);
247         while ((ln = listNext(&li)) != NULL) {
248             pubsubPattern *pat = ln->value;
249 
250             if (stringmatchlen((char*)pat->pattern->ptr,
251                                 sdslen(pat->pattern->ptr),
252                                 (char*)channel->ptr,
253                                 sdslen(channel->ptr),0)) {
254                 addReply(pat->client,shared.mbulkhdr[4]);
255                 addReply(pat->client,shared.pmessagebulk);
256                 addReplyBulk(pat->client,pat->pattern);
257                 addReplyBulk(pat->client,channel);
258                 addReplyBulk(pat->client,message);
259                 receivers++;
260             }
261         }
262         decrRefCount(channel);
263     }
264     return receivers;
265 }
266 
267 /*-----------------------------------------------------------------------------
268  * Pubsub commands implementation
269  *----------------------------------------------------------------------------*/
270 
271 void subscribeCommand(redisClient *c) {
272     int j;
273 
274     for (j = 1; j < c->argc; j++)
275         pubsubSubscribeChannel(c,c->argv[j]);
276 }
277 
278 void unsubscribeCommand(redisClient *c) {
279     if (c->argc == 1) {
280         pubsubUnsubscribeAllChannels(c,1);
281     } else {
282         int j;
283 
284         for (j = 1; j < c->argc; j++)
285             pubsubUnsubscribeChannel(c,c->argv[j],1);
286     }
287 }
288 
289 void psubscribeCommand(redisClient *c) {
290     int j;
291 
292     for (j = 1; j < c->argc; j++)
293         pubsubSubscribePattern(c,c->argv[j]);
294 }
295 
296 void punsubscribeCommand(redisClient *c) {
297     if (c->argc == 1) {
298         pubsubUnsubscribeAllPatterns(c,1);
299     } else {
300         int j;
301 
302         for (j = 1; j < c->argc; j++)
303             pubsubUnsubscribePattern(c,c->argv[j],1);
304     }
305 }
306 
307 void publishCommand(redisClient *c) {
308     int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
309     if (server.cluster_enabled)
310         clusterPropagatePublish(c->argv[1],c->argv[2]);
311     else
312         forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
313     addReplyLongLong(c,receivers);
314 }
315 
316 /* PUBSUB command for Pub/Sub introspection. */
317 void pubsubCommand(redisClient *c) {
318     if (!strcasecmp(c->argv[1]->ptr,"channels") &&
319         (c->argc == 2 || c->argc ==3))
320     {
321         /* PUBSUB CHANNELS [<pattern>] */
322         sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr;
323         dictIterator *di = dictGetIterator(server.pubsub_channels);
324         dictEntry *de;
325         long mblen = 0;
326         void *replylen;
327 
328         replylen = addDeferredMultiBulkLength(c);
329         while((de = dictNext(di)) != NULL) {
330             robj *cobj = dictGetKey(de);
331             sds channel = cobj->ptr;
332 
333             if (!pat || stringmatchlen(pat, sdslen(pat),
334                                        channel, sdslen(channel),0))
335             {
336                 addReplyBulk(c,cobj);
337                 mblen++;
338             }
339         }
340         dictReleaseIterator(di);
341         setDeferredMultiBulkLength(c,replylen,mblen);
342     } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
343         /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
344         int j;
345 
346         addReplyMultiBulkLen(c,(c->argc-2)*2);
347         for (j = 2; j < c->argc; j++) {
348             list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
349 
350             addReplyBulk(c,c->argv[j]);
351             addReplyBulkLongLong(c,l ? listLength(l) : 0);
352         }
353     } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
354         /* PUBSUB NUMPAT */
355         addReplyLongLong(c,listLength(server.pubsub_patterns));
356     } else {
357         addReplyErrorFormat(c,
358             "Unknown PUBSUB subcommand or wrong number of arguments for '%s'",
359             (char*)c->argv[1]->ptr);
360     }
361 }
362