redis-client 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile +1 -2
  4. data/Gemfile.lock +2 -3
  5. data/README.md +71 -8
  6. data/Rakefile +43 -23
  7. data/lib/redis_client/command_builder.rb +91 -0
  8. data/lib/redis_client/config.rb +19 -50
  9. data/lib/redis_client/connection_mixin.rb +40 -0
  10. data/lib/redis_client/decorator.rb +84 -0
  11. data/lib/redis_client/pooled.rb +38 -30
  12. data/lib/redis_client/ruby_connection/buffered_io.rb +153 -0
  13. data/lib/redis_client/{resp3.rb → ruby_connection/resp3.rb} +0 -26
  14. data/lib/redis_client/{connection.rb → ruby_connection.rb} +26 -31
  15. data/lib/redis_client/version.rb +1 -1
  16. data/lib/redis_client.rb +183 -36
  17. data/redis-client.gemspec +2 -4
  18. metadata +12 -59
  19. data/.rubocop.yml +0 -190
  20. data/ext/redis_client/hiredis/export.clang +0 -2
  21. data/ext/redis_client/hiredis/export.gcc +0 -7
  22. data/ext/redis_client/hiredis/extconf.rb +0 -61
  23. data/ext/redis_client/hiredis/hiredis_connection.c +0 -708
  24. data/ext/redis_client/hiredis/vendor/.gitignore +0 -9
  25. data/ext/redis_client/hiredis/vendor/.travis.yml +0 -131
  26. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +0 -364
  27. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +0 -165
  28. data/ext/redis_client/hiredis/vendor/COPYING +0 -29
  29. data/ext/redis_client/hiredis/vendor/Makefile +0 -308
  30. data/ext/redis_client/hiredis/vendor/README.md +0 -664
  31. data/ext/redis_client/hiredis/vendor/adapters/ae.h +0 -130
  32. data/ext/redis_client/hiredis/vendor/adapters/glib.h +0 -156
  33. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +0 -84
  34. data/ext/redis_client/hiredis/vendor/adapters/libev.h +0 -179
  35. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +0 -175
  36. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +0 -117
  37. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +0 -115
  38. data/ext/redis_client/hiredis/vendor/adapters/qt.h +0 -135
  39. data/ext/redis_client/hiredis/vendor/alloc.c +0 -86
  40. data/ext/redis_client/hiredis/vendor/alloc.h +0 -91
  41. data/ext/redis_client/hiredis/vendor/appveyor.yml +0 -24
  42. data/ext/redis_client/hiredis/vendor/async.c +0 -887
  43. data/ext/redis_client/hiredis/vendor/async.h +0 -147
  44. data/ext/redis_client/hiredis/vendor/async_private.h +0 -75
  45. data/ext/redis_client/hiredis/vendor/dict.c +0 -352
  46. data/ext/redis_client/hiredis/vendor/dict.h +0 -126
  47. data/ext/redis_client/hiredis/vendor/fmacros.h +0 -12
  48. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +0 -13
  49. data/ext/redis_client/hiredis/vendor/hiredis.c +0 -1174
  50. data/ext/redis_client/hiredis/vendor/hiredis.h +0 -336
  51. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +0 -12
  52. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +0 -13
  53. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +0 -157
  54. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +0 -12
  55. data/ext/redis_client/hiredis/vendor/net.c +0 -612
  56. data/ext/redis_client/hiredis/vendor/net.h +0 -56
  57. data/ext/redis_client/hiredis/vendor/read.c +0 -739
  58. data/ext/redis_client/hiredis/vendor/read.h +0 -129
  59. data/ext/redis_client/hiredis/vendor/sds.c +0 -1289
  60. data/ext/redis_client/hiredis/vendor/sds.h +0 -278
  61. data/ext/redis_client/hiredis/vendor/sdsalloc.h +0 -44
  62. data/ext/redis_client/hiredis/vendor/sockcompat.c +0 -248
  63. data/ext/redis_client/hiredis/vendor/sockcompat.h +0 -92
  64. data/ext/redis_client/hiredis/vendor/ssl.c +0 -544
  65. data/ext/redis_client/hiredis/vendor/test.c +0 -1401
  66. data/ext/redis_client/hiredis/vendor/test.sh +0 -78
  67. data/ext/redis_client/hiredis/vendor/win32.h +0 -56
  68. data/lib/redis_client/buffered_io.rb +0 -151
  69. data/lib/redis_client/hiredis_connection.rb +0 -80
@@ -1,887 +0,0 @@
1
- /*
2
- * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3
- * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
4
- *
5
- * All rights reserved.
6
- *
7
- * Redistribution and use in source and binary forms, with or without
8
- * modification, are permitted provided that the following conditions are met:
9
- *
10
- * * Redistributions of source code must retain the above copyright notice,
11
- * this list of conditions and the following disclaimer.
12
- * * Redistributions in binary form must reproduce the above copyright
13
- * notice, this list of conditions and the following disclaimer in the
14
- * documentation and/or other materials provided with the distribution.
15
- * * Neither the name of Redis nor the names of its contributors may be used
16
- * to endorse or promote products derived from this software without
17
- * specific prior written permission.
18
- *
19
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- * POSSIBILITY OF SUCH DAMAGE.
30
- */
31
-
32
- #include "fmacros.h"
33
- #include "alloc.h"
34
- #include <stdlib.h>
35
- #include <string.h>
36
- #ifndef _MSC_VER
37
- #include <strings.h>
38
- #endif
39
- #include <assert.h>
40
- #include <ctype.h>
41
- #include <errno.h>
42
- #include "async.h"
43
- #include "net.h"
44
- #include "dict.c"
45
- #include "sds.h"
46
- #include "win32.h"
47
-
48
- #include "async_private.h"
49
-
50
- /* Forward declarations of hiredis.c functions */
51
- int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
52
- void __redisSetError(redisContext *c, int type, const char *str);
53
-
54
- /* Functions managing dictionary of callbacks for pub/sub. */
55
- static unsigned int callbackHash(const void *key) {
56
- return dictGenHashFunction((const unsigned char *)key,
57
- sdslen((const sds)key));
58
- }
59
-
60
- static void *callbackValDup(void *privdata, const void *src) {
61
- ((void) privdata);
62
- redisCallback *dup;
63
-
64
- dup = hi_malloc(sizeof(*dup));
65
- if (dup == NULL)
66
- return NULL;
67
-
68
- memcpy(dup,src,sizeof(*dup));
69
- return dup;
70
- }
71
-
72
- static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
73
- int l1, l2;
74
- ((void) privdata);
75
-
76
- l1 = sdslen((const sds)key1);
77
- l2 = sdslen((const sds)key2);
78
- if (l1 != l2) return 0;
79
- return memcmp(key1,key2,l1) == 0;
80
- }
81
-
82
- static void callbackKeyDestructor(void *privdata, void *key) {
83
- ((void) privdata);
84
- sdsfree((sds)key);
85
- }
86
-
87
- static void callbackValDestructor(void *privdata, void *val) {
88
- ((void) privdata);
89
- hi_free(val);
90
- }
91
-
92
- static dictType callbackDict = {
93
- callbackHash,
94
- NULL,
95
- callbackValDup,
96
- callbackKeyCompare,
97
- callbackKeyDestructor,
98
- callbackValDestructor
99
- };
100
-
101
- static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
102
- redisAsyncContext *ac;
103
- dict *channels = NULL, *patterns = NULL;
104
-
105
- channels = dictCreate(&callbackDict,NULL);
106
- if (channels == NULL)
107
- goto oom;
108
-
109
- patterns = dictCreate(&callbackDict,NULL);
110
- if (patterns == NULL)
111
- goto oom;
112
-
113
- ac = hi_realloc(c,sizeof(redisAsyncContext));
114
- if (ac == NULL)
115
- goto oom;
116
-
117
- c = &(ac->c);
118
-
119
- /* The regular connect functions will always set the flag REDIS_CONNECTED.
120
- * For the async API, we want to wait until the first write event is
121
- * received up before setting this flag, so reset it here. */
122
- c->flags &= ~REDIS_CONNECTED;
123
-
124
- ac->err = 0;
125
- ac->errstr = NULL;
126
- ac->data = NULL;
127
- ac->dataCleanup = NULL;
128
-
129
- ac->ev.data = NULL;
130
- ac->ev.addRead = NULL;
131
- ac->ev.delRead = NULL;
132
- ac->ev.addWrite = NULL;
133
- ac->ev.delWrite = NULL;
134
- ac->ev.cleanup = NULL;
135
- ac->ev.scheduleTimer = NULL;
136
-
137
- ac->onConnect = NULL;
138
- ac->onDisconnect = NULL;
139
-
140
- ac->replies.head = NULL;
141
- ac->replies.tail = NULL;
142
- ac->sub.invalid.head = NULL;
143
- ac->sub.invalid.tail = NULL;
144
- ac->sub.channels = channels;
145
- ac->sub.patterns = patterns;
146
-
147
- return ac;
148
- oom:
149
- if (channels) dictRelease(channels);
150
- if (patterns) dictRelease(patterns);
151
- return NULL;
152
- }
153
-
154
- /* We want the error field to be accessible directly instead of requiring
155
- * an indirection to the redisContext struct. */
156
- static void __redisAsyncCopyError(redisAsyncContext *ac) {
157
- if (!ac)
158
- return;
159
-
160
- redisContext *c = &(ac->c);
161
- ac->err = c->err;
162
- ac->errstr = c->errstr;
163
- }
164
-
165
- redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
166
- redisOptions myOptions = *options;
167
- redisContext *c;
168
- redisAsyncContext *ac;
169
-
170
- /* Clear any erroneously set sync callback and flag that we don't want to
171
- * use freeReplyObject by default. */
172
- myOptions.push_cb = NULL;
173
- myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
174
-
175
- myOptions.options |= REDIS_OPT_NONBLOCK;
176
- c = redisConnectWithOptions(&myOptions);
177
- if (c == NULL) {
178
- return NULL;
179
- }
180
-
181
- ac = redisAsyncInitialize(c);
182
- if (ac == NULL) {
183
- redisFree(c);
184
- return NULL;
185
- }
186
-
187
- /* Set any configured async push handler */
188
- redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
189
-
190
- __redisAsyncCopyError(ac);
191
- return ac;
192
- }
193
-
194
- redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
195
- redisOptions options = {0};
196
- REDIS_OPTIONS_SET_TCP(&options, ip, port);
197
- return redisAsyncConnectWithOptions(&options);
198
- }
199
-
200
- redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
201
- const char *source_addr) {
202
- redisOptions options = {0};
203
- REDIS_OPTIONS_SET_TCP(&options, ip, port);
204
- options.endpoint.tcp.source_addr = source_addr;
205
- return redisAsyncConnectWithOptions(&options);
206
- }
207
-
208
- redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
209
- const char *source_addr) {
210
- redisOptions options = {0};
211
- REDIS_OPTIONS_SET_TCP(&options, ip, port);
212
- options.options |= REDIS_OPT_REUSEADDR;
213
- options.endpoint.tcp.source_addr = source_addr;
214
- return redisAsyncConnectWithOptions(&options);
215
- }
216
-
217
- redisAsyncContext *redisAsyncConnectUnix(const char *path) {
218
- redisOptions options = {0};
219
- REDIS_OPTIONS_SET_UNIX(&options, path);
220
- return redisAsyncConnectWithOptions(&options);
221
- }
222
-
223
- int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
224
- if (ac->onConnect == NULL) {
225
- ac->onConnect = fn;
226
-
227
- /* The common way to detect an established connection is to wait for
228
- * the first write event to be fired. This assumes the related event
229
- * library functions are already set. */
230
- _EL_ADD_WRITE(ac);
231
- return REDIS_OK;
232
- }
233
- return REDIS_ERR;
234
- }
235
-
236
- int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
237
- if (ac->onDisconnect == NULL) {
238
- ac->onDisconnect = fn;
239
- return REDIS_OK;
240
- }
241
- return REDIS_ERR;
242
- }
243
-
244
- /* Helper functions to push/shift callbacks */
245
- static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
246
- redisCallback *cb;
247
-
248
- /* Copy callback from stack to heap */
249
- cb = hi_malloc(sizeof(*cb));
250
- if (cb == NULL)
251
- return REDIS_ERR_OOM;
252
-
253
- if (source != NULL) {
254
- memcpy(cb,source,sizeof(*cb));
255
- cb->next = NULL;
256
- }
257
-
258
- /* Store callback in list */
259
- if (list->head == NULL)
260
- list->head = cb;
261
- if (list->tail != NULL)
262
- list->tail->next = cb;
263
- list->tail = cb;
264
- return REDIS_OK;
265
- }
266
-
267
- static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
268
- redisCallback *cb = list->head;
269
- if (cb != NULL) {
270
- list->head = cb->next;
271
- if (cb == list->tail)
272
- list->tail = NULL;
273
-
274
- /* Copy callback from heap to stack */
275
- if (target != NULL)
276
- memcpy(target,cb,sizeof(*cb));
277
- hi_free(cb);
278
- return REDIS_OK;
279
- }
280
- return REDIS_ERR;
281
- }
282
-
283
- static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
284
- redisContext *c = &(ac->c);
285
- if (cb->fn != NULL) {
286
- c->flags |= REDIS_IN_CALLBACK;
287
- cb->fn(ac,reply,cb->privdata);
288
- c->flags &= ~REDIS_IN_CALLBACK;
289
- }
290
- }
291
-
292
- static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
293
- if (ac->push_cb != NULL) {
294
- ac->c.flags |= REDIS_IN_CALLBACK;
295
- ac->push_cb(ac, reply);
296
- ac->c.flags &= ~REDIS_IN_CALLBACK;
297
- }
298
- }
299
-
300
- /* Helper function to free the context. */
301
- static void __redisAsyncFree(redisAsyncContext *ac) {
302
- redisContext *c = &(ac->c);
303
- redisCallback cb;
304
- dictIterator *it;
305
- dictEntry *de;
306
-
307
- /* Execute pending callbacks with NULL reply. */
308
- while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
309
- __redisRunCallback(ac,&cb,NULL);
310
-
311
- /* Execute callbacks for invalid commands */
312
- while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
313
- __redisRunCallback(ac,&cb,NULL);
314
-
315
- /* Run subscription callbacks with NULL reply */
316
- if (ac->sub.channels) {
317
- it = dictGetIterator(ac->sub.channels);
318
- if (it != NULL) {
319
- while ((de = dictNext(it)) != NULL)
320
- __redisRunCallback(ac,dictGetEntryVal(de),NULL);
321
- dictReleaseIterator(it);
322
- }
323
-
324
- dictRelease(ac->sub.channels);
325
- }
326
-
327
- if (ac->sub.patterns) {
328
- it = dictGetIterator(ac->sub.patterns);
329
- if (it != NULL) {
330
- while ((de = dictNext(it)) != NULL)
331
- __redisRunCallback(ac,dictGetEntryVal(de),NULL);
332
- dictReleaseIterator(it);
333
- }
334
-
335
- dictRelease(ac->sub.patterns);
336
- }
337
-
338
- /* Signal event lib to clean up */
339
- _EL_CLEANUP(ac);
340
-
341
- /* Execute disconnect callback. When redisAsyncFree() initiated destroying
342
- * this context, the status will always be REDIS_OK. */
343
- if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
344
- if (c->flags & REDIS_FREEING) {
345
- ac->onDisconnect(ac,REDIS_OK);
346
- } else {
347
- ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
348
- }
349
- }
350
-
351
- if (ac->dataCleanup) {
352
- ac->dataCleanup(ac->data);
353
- }
354
-
355
- /* Cleanup self */
356
- redisFree(c);
357
- }
358
-
359
- /* Free the async context. When this function is called from a callback,
360
- * control needs to be returned to redisProcessCallbacks() before actual
361
- * free'ing. To do so, a flag is set on the context which is picked up by
362
- * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
363
- void redisAsyncFree(redisAsyncContext *ac) {
364
- redisContext *c = &(ac->c);
365
- c->flags |= REDIS_FREEING;
366
- if (!(c->flags & REDIS_IN_CALLBACK))
367
- __redisAsyncFree(ac);
368
- }
369
-
370
- /* Helper function to make the disconnect happen and clean up. */
371
- void __redisAsyncDisconnect(redisAsyncContext *ac) {
372
- redisContext *c = &(ac->c);
373
-
374
- /* Make sure error is accessible if there is any */
375
- __redisAsyncCopyError(ac);
376
-
377
- if (ac->err == 0) {
378
- /* For clean disconnects, there should be no pending callbacks. */
379
- int ret = __redisShiftCallback(&ac->replies,NULL);
380
- assert(ret == REDIS_ERR);
381
- } else {
382
- /* Disconnection is caused by an error, make sure that pending
383
- * callbacks cannot call new commands. */
384
- c->flags |= REDIS_DISCONNECTING;
385
- }
386
-
387
- /* cleanup event library on disconnect.
388
- * this is safe to call multiple times */
389
- _EL_CLEANUP(ac);
390
-
391
- /* For non-clean disconnects, __redisAsyncFree() will execute pending
392
- * callbacks with a NULL-reply. */
393
- if (!(c->flags & REDIS_NO_AUTO_FREE)) {
394
- __redisAsyncFree(ac);
395
- }
396
- }
397
-
398
- /* Tries to do a clean disconnect from Redis, meaning it stops new commands
399
- * from being issued, but tries to flush the output buffer and execute
400
- * callbacks for all remaining replies. When this function is called from a
401
- * callback, there might be more replies and we can safely defer disconnecting
402
- * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
403
- * when there are no pending callbacks. */
404
- void redisAsyncDisconnect(redisAsyncContext *ac) {
405
- redisContext *c = &(ac->c);
406
- c->flags |= REDIS_DISCONNECTING;
407
-
408
- /** unset the auto-free flag here, because disconnect undoes this */
409
- c->flags &= ~REDIS_NO_AUTO_FREE;
410
- if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
411
- __redisAsyncDisconnect(ac);
412
- }
413
-
414
- static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
415
- redisContext *c = &(ac->c);
416
- dict *callbacks;
417
- redisCallback *cb;
418
- dictEntry *de;
419
- int pvariant;
420
- char *stype;
421
- sds sname;
422
-
423
- /* Custom reply functions are not supported for pub/sub. This will fail
424
- * very hard when they are used... */
425
- if (reply->type == REDIS_REPLY_ARRAY || reply->type == REDIS_REPLY_PUSH) {
426
- assert(reply->elements >= 2);
427
- assert(reply->element[0]->type == REDIS_REPLY_STRING);
428
- stype = reply->element[0]->str;
429
- pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
430
-
431
- if (pvariant)
432
- callbacks = ac->sub.patterns;
433
- else
434
- callbacks = ac->sub.channels;
435
-
436
- /* Locate the right callback */
437
- assert(reply->element[1]->type == REDIS_REPLY_STRING);
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);
445
-
446
- /* If this is an subscribe reply decrease pending counter. */
447
- if (strcasecmp(stype+pvariant,"subscribe") == 0) {
448
- cb->pending_subs -= 1;
449
- }
450
-
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
- }
469
- sdsfree(sname);
470
- } else {
471
- /* Shift callback for invalid commands. */
472
- __redisShiftCallback(&ac->sub.invalid,dstcb);
473
- }
474
- return REDIS_OK;
475
- oom:
476
- __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
477
- return REDIS_ERR;
478
- }
479
-
480
- #define redisIsSpontaneousPushReply(r) \
481
- (redisIsPushReply(r) && !redisIsSubscribeReply(r))
482
-
483
- static int redisIsSubscribeReply(redisReply *reply) {
484
- char *str;
485
- size_t len, off;
486
-
487
- /* We will always have at least one string with the subscribe/message type */
488
- if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||
489
- reply->element[0]->len < sizeof("message") - 1)
490
- {
491
- return 0;
492
- }
493
-
494
- /* Get the string/len moving past 'p' if needed */
495
- off = tolower(reply->element[0]->str[0]) == 'p';
496
- str = reply->element[0]->str + off;
497
- len = reply->element[0]->len - off;
498
-
499
- return !strncasecmp(str, "subscribe", len) ||
500
- !strncasecmp(str, "message", len);
501
-
502
- }
503
-
504
- void redisProcessCallbacks(redisAsyncContext *ac) {
505
- redisContext *c = &(ac->c);
506
- redisCallback cb = {NULL, NULL, 0, NULL};
507
- void *reply = NULL;
508
- int status;
509
-
510
- while((status = redisGetReply(c,&reply)) == REDIS_OK) {
511
- if (reply == NULL) {
512
- /* When the connection is being disconnected and there are
513
- * no more replies, this is the cue to really disconnect. */
514
- if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
515
- && ac->replies.head == NULL) {
516
- __redisAsyncDisconnect(ac);
517
- return;
518
- }
519
-
520
- /* If monitor mode, repush callback */
521
- if(c->flags & REDIS_MONITORING) {
522
- __redisPushCallback(&ac->replies,&cb);
523
- }
524
-
525
- /* When the connection is not being disconnected, simply stop
526
- * trying to get replies and wait for the next loop tick. */
527
- break;
528
- }
529
-
530
- /* Send any non-subscribe related PUSH messages to our PUSH handler
531
- * while allowing subscribe related PUSH messages to pass through.
532
- * This allows existing code to be backward compatible and work in
533
- * either RESP2 or RESP3 mode. */
534
- if (redisIsSpontaneousPushReply(reply)) {
535
- __redisRunPushCallback(ac, reply);
536
- c->reader->fn->freeObject(reply);
537
- continue;
538
- }
539
-
540
- /* Even if the context is subscribed, pending regular
541
- * callbacks will get a reply before pub/sub messages arrive. */
542
- if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
543
- /*
544
- * A spontaneous reply in a not-subscribed context can be the error
545
- * reply that is sent when a new connection exceeds the maximum
546
- * number of allowed connections on the server side.
547
- *
548
- * This is seen as an error instead of a regular reply because the
549
- * server closes the connection after sending it.
550
- *
551
- * To prevent the error from being overwritten by an EOF error the
552
- * connection is closed here. See issue #43.
553
- *
554
- * Another possibility is that the server is loading its dataset.
555
- * In this case we also want to close the connection, and have the
556
- * user wait until the server is ready to take our request.
557
- */
558
- if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
559
- c->err = REDIS_ERR_OTHER;
560
- snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
561
- c->reader->fn->freeObject(reply);
562
- __redisAsyncDisconnect(ac);
563
- return;
564
- }
565
- /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
566
- assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
567
- if(c->flags & REDIS_SUBSCRIBED)
568
- __redisGetSubscribeCallback(ac,reply,&cb);
569
- }
570
-
571
- if (cb.fn != NULL) {
572
- __redisRunCallback(ac,&cb,reply);
573
- c->reader->fn->freeObject(reply);
574
-
575
- /* Proceed with free'ing when redisAsyncFree() was called. */
576
- if (c->flags & REDIS_FREEING) {
577
- __redisAsyncFree(ac);
578
- return;
579
- }
580
- } else {
581
- /* No callback for this reply. This can either be a NULL callback,
582
- * or there were no callbacks to begin with. Either way, don't
583
- * abort with an error, but simply ignore it because the client
584
- * doesn't know what the server will spit out over the wire. */
585
- c->reader->fn->freeObject(reply);
586
- }
587
- }
588
-
589
- /* Disconnect when there was an error reading the reply */
590
- if (status != REDIS_OK)
591
- __redisAsyncDisconnect(ac);
592
- }
593
-
594
- static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
595
- if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
596
- __redisAsyncDisconnect(ac);
597
- }
598
-
599
- /* Internal helper function to detect socket status the first time a read or
600
- * write event fires. When connecting was not successful, the connect callback
601
- * is called with a REDIS_ERR status and the context is free'd. */
602
- static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
603
- int completed = 0;
604
- redisContext *c = &(ac->c);
605
-
606
- if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
607
- /* Error! */
608
- redisCheckSocketError(c);
609
- __redisAsyncHandleConnectFailure(ac);
610
- return REDIS_ERR;
611
- } else if (completed == 1) {
612
- /* connected! */
613
- if (c->connection_type == REDIS_CONN_TCP &&
614
- redisSetTcpNoDelay(c) == REDIS_ERR) {
615
- __redisAsyncHandleConnectFailure(ac);
616
- return REDIS_ERR;
617
- }
618
-
619
- if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
620
- c->flags |= REDIS_CONNECTED;
621
- return REDIS_OK;
622
- } else {
623
- return REDIS_OK;
624
- }
625
- }
626
-
627
- void redisAsyncRead(redisAsyncContext *ac) {
628
- redisContext *c = &(ac->c);
629
-
630
- if (redisBufferRead(c) == REDIS_ERR) {
631
- __redisAsyncDisconnect(ac);
632
- } else {
633
- /* Always re-schedule reads */
634
- _EL_ADD_READ(ac);
635
- redisProcessCallbacks(ac);
636
- }
637
- }
638
-
639
- /* This function should be called when the socket is readable.
640
- * It processes all replies that can be read and executes their callbacks.
641
- */
642
- void redisAsyncHandleRead(redisAsyncContext *ac) {
643
- redisContext *c = &(ac->c);
644
-
645
- if (!(c->flags & REDIS_CONNECTED)) {
646
- /* Abort connect was not successful. */
647
- if (__redisAsyncHandleConnect(ac) != REDIS_OK)
648
- return;
649
- /* Try again later when the context is still not connected. */
650
- if (!(c->flags & REDIS_CONNECTED))
651
- return;
652
- }
653
-
654
- c->funcs->async_read(ac);
655
- }
656
-
657
- void redisAsyncWrite(redisAsyncContext *ac) {
658
- redisContext *c = &(ac->c);
659
- int done = 0;
660
-
661
- if (redisBufferWrite(c,&done) == REDIS_ERR) {
662
- __redisAsyncDisconnect(ac);
663
- } else {
664
- /* Continue writing when not done, stop writing otherwise */
665
- if (!done)
666
- _EL_ADD_WRITE(ac);
667
- else
668
- _EL_DEL_WRITE(ac);
669
-
670
- /* Always schedule reads after writes */
671
- _EL_ADD_READ(ac);
672
- }
673
- }
674
-
675
- void redisAsyncHandleWrite(redisAsyncContext *ac) {
676
- redisContext *c = &(ac->c);
677
-
678
- if (!(c->flags & REDIS_CONNECTED)) {
679
- /* Abort connect was not successful. */
680
- if (__redisAsyncHandleConnect(ac) != REDIS_OK)
681
- return;
682
- /* Try again later when the context is still not connected. */
683
- if (!(c->flags & REDIS_CONNECTED))
684
- return;
685
- }
686
-
687
- c->funcs->async_write(ac);
688
- }
689
-
690
- void redisAsyncHandleTimeout(redisAsyncContext *ac) {
691
- redisContext *c = &(ac->c);
692
- redisCallback cb;
693
-
694
- if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
695
- /* Nothing to do - just an idle timeout */
696
- return;
697
- }
698
-
699
- if (!c->err) {
700
- __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
701
- }
702
-
703
- if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
704
- ac->onConnect(ac, REDIS_ERR);
705
- }
706
-
707
- while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
708
- __redisRunCallback(ac, &cb, NULL);
709
- }
710
-
711
- /**
712
- * TODO: Don't automatically sever the connection,
713
- * rather, allow to ignore <x> responses before the queue is clear
714
- */
715
- __redisAsyncDisconnect(ac);
716
- }
717
-
718
- /* Sets a pointer to the first argument and its length starting at p. Returns
719
- * the number of bytes to skip to get to the following argument. */
720
- static const char *nextArgument(const char *start, const char **str, size_t *len) {
721
- const char *p = start;
722
- if (p[0] != '$') {
723
- p = strchr(p,'$');
724
- if (p == NULL) return NULL;
725
- }
726
-
727
- *len = (int)strtol(p+1,NULL,10);
728
- p = strchr(p,'\r');
729
- assert(p);
730
- *str = p+2;
731
- return p+2+(*len)+2;
732
- }
733
-
734
- /* Helper function for the redisAsyncCommand* family of functions. Writes a
735
- * formatted command to the output buffer and registers the provided callback
736
- * function with the context. */
737
- static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
738
- redisContext *c = &(ac->c);
739
- redisCallback cb;
740
- struct dict *cbdict;
741
- dictEntry *de;
742
- redisCallback *existcb;
743
- int pvariant, hasnext;
744
- const char *cstr, *astr;
745
- size_t clen, alen;
746
- const char *p;
747
- sds sname;
748
- int ret;
749
-
750
- /* Don't accept new commands when the connection is about to be closed. */
751
- if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
752
-
753
- /* Setup callback */
754
- cb.fn = fn;
755
- cb.privdata = privdata;
756
- cb.pending_subs = 1;
757
-
758
- /* Find out which command will be appended. */
759
- p = nextArgument(cmd,&cstr,&clen);
760
- assert(p != NULL);
761
- hasnext = (p[0] == '$');
762
- pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
763
- cstr += pvariant;
764
- clen -= pvariant;
765
-
766
- if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
767
- c->flags |= REDIS_SUBSCRIBED;
768
-
769
- /* Add every channel/pattern to the list of subscription callbacks. */
770
- while ((p = nextArgument(p,&astr,&alen)) != NULL) {
771
- sname = sdsnewlen(astr,alen);
772
- if (sname == NULL)
773
- goto oom;
774
-
775
- if (pvariant)
776
- cbdict = ac->sub.patterns;
777
- else
778
- cbdict = ac->sub.channels;
779
-
780
- de = dictFind(cbdict,sname);
781
-
782
- if (de != NULL) {
783
- existcb = dictGetEntryVal(de);
784
- cb.pending_subs = existcb->pending_subs + 1;
785
- }
786
-
787
- ret = dictReplace(cbdict,sname,&cb);
788
-
789
- if (ret == 0) sdsfree(sname);
790
- }
791
- } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
792
- /* It is only useful to call (P)UNSUBSCRIBE when the context is
793
- * subscribed to one or more channels or patterns. */
794
- if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
795
-
796
- /* (P)UNSUBSCRIBE does not have its own response: every channel or
797
- * pattern that is unsubscribed will receive a message. This means we
798
- * should not append a callback function for this command. */
799
- } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
800
- /* Set monitor flag and push callback */
801
- c->flags |= REDIS_MONITORING;
802
- __redisPushCallback(&ac->replies,&cb);
803
- } else {
804
- if (c->flags & REDIS_SUBSCRIBED)
805
- /* This will likely result in an error reply, but it needs to be
806
- * received and passed to the callback. */
807
- __redisPushCallback(&ac->sub.invalid,&cb);
808
- else
809
- __redisPushCallback(&ac->replies,&cb);
810
- }
811
-
812
- __redisAppendCommand(c,cmd,len);
813
-
814
- /* Always schedule a write when the write buffer is non-empty */
815
- _EL_ADD_WRITE(ac);
816
-
817
- return REDIS_OK;
818
- oom:
819
- __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
820
- return REDIS_ERR;
821
- }
822
-
823
- int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
824
- char *cmd;
825
- int len;
826
- int status;
827
- len = redisvFormatCommand(&cmd,format,ap);
828
-
829
- /* We don't want to pass -1 or -2 to future functions as a length. */
830
- if (len < 0)
831
- return REDIS_ERR;
832
-
833
- status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
834
- hi_free(cmd);
835
- return status;
836
- }
837
-
838
- int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
839
- va_list ap;
840
- int status;
841
- va_start(ap,format);
842
- status = redisvAsyncCommand(ac,fn,privdata,format,ap);
843
- va_end(ap);
844
- return status;
845
- }
846
-
847
- int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
848
- sds cmd;
849
- int len;
850
- int status;
851
- len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
852
- if (len < 0)
853
- return REDIS_ERR;
854
- status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
855
- sdsfree(cmd);
856
- return status;
857
- }
858
-
859
- int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
860
- int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
861
- return status;
862
- }
863
-
864
- redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
865
- redisAsyncPushFn *old = ac->push_cb;
866
- ac->push_cb = fn;
867
- return old;
868
- }
869
-
870
- int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
871
- if (!ac->c.command_timeout) {
872
- ac->c.command_timeout = hi_calloc(1, sizeof(tv));
873
- if (ac->c.command_timeout == NULL) {
874
- __redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
875
- __redisAsyncCopyError(ac);
876
- return REDIS_ERR;
877
- }
878
- }
879
-
880
- if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
881
- tv.tv_usec != ac->c.command_timeout->tv_usec)
882
- {
883
- *ac->c.command_timeout = tv;
884
- }
885
-
886
- return REDIS_OK;
887
- }