durable_rules 0.31.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cfaeb0a27a18ec5fc8ce5443708b4cb89d9cc3cf
4
+ data.tar.gz: 619f0527a376f0a85958ee978da745ae4ac59959
5
+ SHA512:
6
+ metadata.gz: 06522d1386a71dffd0ffa1fa6a34f13c603c8ca50e3b2d6f592cb980211df6fb4b7e61080fc1d6e150b071f1eb6d4196f3634a01e657dbecb4ac55858473cac3
7
+ data.tar.gz: bda6a67d2483fe10a1b1a9350dddfb57d3f1eec6c734d5c6096221e5a7f2c0d9db900a350638f832b5050156508c87b5ea8615b22e780126fe0b1b41d4c67d3b
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Jesus Ruiz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rbconfig"
5
+ require "rake/testtask"
6
+ require "rake/extensiontask"
7
+
8
+
9
+ Rake::ExtensionTask.new('rules') do |task|
10
+ task.config_options = ARGV[1..-1] || []
11
+ task.lib_dir = File.join(*['src', 'rulesrb'])
12
+ task.ext_dir = File.join(*['src', 'rulesrb'])
13
+ end
14
+
15
+ namespace :rules do
16
+ task :clean do
17
+ RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
18
+ make_program = $1 || ENV['make']
19
+ unless make_program then
20
+ make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
21
+ end
22
+ system("cd deps/hiredis && #{make_program} clean")
23
+ system("cd src/rules && #{make_program} clean")
24
+ end
25
+ end
26
+
27
+ # "rake clean" should also clean bundled rules
28
+ Rake::Task[:clean].enhance(['rules:clean'])
29
+
30
+ # Build from scratch
31
+ task :rebuild => [:clean, :compile]
32
+
33
+
34
+ task :default => [:rebuild]
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
2
+ Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice,
10
+ this list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of Redis nor the names of its contributors may be used
17
+ to endorse or promote products derived from this software without specific
18
+ prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,148 @@
1
+ # Hiredis Makefile
2
+ # Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
3
+ # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
4
+ # This file is released under the BSD license, see the COPYING file
5
+
6
+ OBJ=net.o hiredis.o sds.o async.o
7
+ BINS=hiredis-example hiredis-test
8
+ LIBNAME=libhiredis
9
+
10
+ HIREDIS_MAJOR=0
11
+ HIREDIS_MINOR=10
12
+
13
+ # Fallback to gcc when $CC is not in $PATH.
14
+ CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
15
+ OPTIMIZATION?=-O3
16
+ WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
17
+ DEBUG?= -g -ggdb
18
+ REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
19
+ REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
20
+
21
+ DYLIBSUFFIX=so
22
+ STLIBSUFFIX=a
23
+ DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
24
+ DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
25
+ DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
26
+ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
27
+ STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
28
+ STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
29
+
30
+ # Platform-specific overrides
31
+ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
32
+ ifeq ($(uname_S),SunOS)
33
+ REAL_LDFLAGS+= -ldl -lnsl -lsocket
34
+ DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
35
+ INSTALL= cp -r
36
+ endif
37
+ ifeq ($(uname_S),Darwin)
38
+ DYLIBSUFFIX=dylib
39
+ DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
40
+ DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
41
+ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
42
+ endif
43
+
44
+ all: $(DYLIBNAME) $(BINS)
45
+
46
+ # Deps (use make dep to generate this)
47
+ net.o: net.c fmacros.h net.h hiredis.h
48
+ async.o: async.c async.h hiredis.h sds.h dict.c dict.h
49
+ example.o: example.c hiredis.h
50
+ hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h
51
+ sds.o: sds.c sds.h
52
+ test.o: test.c hiredis.h
53
+
54
+ $(DYLIBNAME): $(OBJ)
55
+ $(DYLIB_MAKE_CMD) $(OBJ)
56
+
57
+ $(STLIBNAME): $(OBJ)
58
+ $(STLIB_MAKE_CMD) $(OBJ)
59
+
60
+ dynamic: $(DYLIBNAME)
61
+ static: $(STLIBNAME)
62
+
63
+ # Binaries:
64
+ hiredis-example-libevent: example-libevent.c adapters/libevent.h $(STLIBNAME)
65
+ $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -levent example-libevent.c $(STLIBNAME)
66
+
67
+ hiredis-example-libev: example-libev.c adapters/libev.h $(STLIBNAME)
68
+ $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -lev example-libev.c $(STLIBNAME)
69
+
70
+ ifndef AE_DIR
71
+ hiredis-example-ae:
72
+ @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
73
+ @false
74
+ else
75
+ hiredis-example-ae: example-ae.c adapters/ae.h $(STLIBNAME)
76
+ $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I$(AE_DIR) $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o example-ae.c $(STLIBNAME)
77
+ endif
78
+
79
+ hiredis-%: %.o $(STLIBNAME)
80
+ $(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
81
+
82
+ test: hiredis-test
83
+ ./hiredis-test
84
+
85
+ check: hiredis-test
86
+ echo \
87
+ "daemonize yes\n" \
88
+ "pidfile /tmp/hiredis-test-redis.pid\n" \
89
+ "port 56379\n" \
90
+ "bind 127.0.0.1\n" \
91
+ "unixsocket /tmp/hiredis-test-redis.sock" \
92
+ | redis-server -
93
+ ./hiredis-test -h 127.0.0.1 -p 56379 -s /tmp/hiredis-test-redis.sock || \
94
+ ( kill `cat /tmp/hiredis-test-redis.pid` && false )
95
+ kill `cat /tmp/hiredis-test-redis.pid`
96
+
97
+ .c.o:
98
+ $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
99
+
100
+ clean:
101
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
102
+
103
+ dep:
104
+ $(CC) -MM *.c
105
+
106
+ # Installation related variables and target
107
+ PREFIX?=/usr/local
108
+ INCLUDE_PATH?=include/hiredis
109
+ LIBRARY_PATH?=lib
110
+ INSTALL_INCLUDE_PATH= $(PREFIX)/$(INCLUDE_PATH)
111
+ INSTALL_LIBRARY_PATH= $(PREFIX)/$(LIBRARY_PATH)
112
+
113
+ ifeq ($(uname_S),SunOS)
114
+ INSTALL?= cp -r
115
+ endif
116
+
117
+ INSTALL?= cp -a
118
+
119
+ install: $(DYLIBNAME) $(STLIBNAME)
120
+ mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
121
+ $(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH)
122
+ $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
123
+ cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
124
+ cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
125
+ $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
126
+
127
+ 32bit:
128
+ @echo ""
129
+ @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
130
+ @echo ""
131
+ $(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
132
+
133
+ gprof:
134
+ $(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
135
+
136
+ gcov:
137
+ $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
138
+
139
+ coverage: gcov
140
+ make check
141
+ mkdir -p tmp/lcov
142
+ lcov -d . -c -o tmp/lcov/hiredis.info
143
+ genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
144
+
145
+ noopt:
146
+ $(MAKE) OPTIMIZATION=""
147
+
148
+ .PHONY: all test check clean dep install 32bit gprof gcov noopt
@@ -0,0 +1,622 @@
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 <stdlib.h>
34
+ #include <string.h>
35
+ #include <strings.h>
36
+ #include <assert.h>
37
+ #include <ctype.h>
38
+ #include <errno.h>
39
+ #include "async.h"
40
+ #include "net.h"
41
+ #include "dict.c"
42
+ #include "sds.h"
43
+
44
+ #define _EL_ADD_READ(ctx) do { \
45
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
46
+ } while(0)
47
+ #define _EL_DEL_READ(ctx) do { \
48
+ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
49
+ } while(0)
50
+ #define _EL_ADD_WRITE(ctx) do { \
51
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
52
+ } while(0)
53
+ #define _EL_DEL_WRITE(ctx) do { \
54
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
55
+ } while(0)
56
+ #define _EL_CLEANUP(ctx) do { \
57
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
58
+ } while(0);
59
+
60
+ /* Forward declaration of function in hiredis.c */
61
+ void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
62
+
63
+ /* Functions managing dictionary of callbacks for pub/sub. */
64
+ static unsigned int callbackHash(const void *key) {
65
+ return dictGenHashFunction((unsigned char*)key,sdslen((char*)key));
66
+ }
67
+
68
+ static void *callbackValDup(void *privdata, const void *src) {
69
+ ((void) privdata);
70
+ redisCallback *dup = malloc(sizeof(*dup));
71
+ memcpy(dup,src,sizeof(*dup));
72
+ return dup;
73
+ }
74
+
75
+ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
76
+ int l1, l2;
77
+ ((void) privdata);
78
+
79
+ l1 = sdslen((sds)key1);
80
+ l2 = sdslen((sds)key2);
81
+ if (l1 != l2) return 0;
82
+ return memcmp(key1,key2,l1) == 0;
83
+ }
84
+
85
+ static void callbackKeyDestructor(void *privdata, void *key) {
86
+ ((void) privdata);
87
+ sdsfree((sds)key);
88
+ }
89
+
90
+ static void callbackValDestructor(void *privdata, void *val) {
91
+ ((void) privdata);
92
+ free(val);
93
+ }
94
+
95
+ static dictType callbackDict = {
96
+ callbackHash,
97
+ NULL,
98
+ callbackValDup,
99
+ callbackKeyCompare,
100
+ callbackKeyDestructor,
101
+ callbackValDestructor
102
+ };
103
+
104
+ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
105
+ redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
106
+ c = &(ac->c);
107
+
108
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
109
+ * For the async API, we want to wait until the first write event is
110
+ * received up before setting this flag, so reset it here. */
111
+ c->flags &= ~REDIS_CONNECTED;
112
+
113
+ ac->err = 0;
114
+ ac->errstr = NULL;
115
+ ac->data = NULL;
116
+
117
+ ac->ev.data = NULL;
118
+ ac->ev.addRead = NULL;
119
+ ac->ev.delRead = NULL;
120
+ ac->ev.addWrite = NULL;
121
+ ac->ev.delWrite = NULL;
122
+ ac->ev.cleanup = NULL;
123
+
124
+ ac->onConnect = NULL;
125
+ ac->onDisconnect = NULL;
126
+
127
+ ac->replies.head = NULL;
128
+ ac->replies.tail = NULL;
129
+ ac->sub.invalid.head = NULL;
130
+ ac->sub.invalid.tail = NULL;
131
+ ac->sub.channels = dictCreate(&callbackDict,NULL);
132
+ ac->sub.patterns = dictCreate(&callbackDict,NULL);
133
+ return ac;
134
+ }
135
+
136
+ /* We want the error field to be accessible directly instead of requiring
137
+ * an indirection to the redisContext struct. */
138
+ static void __redisAsyncCopyError(redisAsyncContext *ac) {
139
+ redisContext *c = &(ac->c);
140
+ ac->err = c->err;
141
+ ac->errstr = c->errstr;
142
+ }
143
+
144
+ redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
145
+ redisContext *c = redisConnectNonBlock(ip,port);
146
+ redisAsyncContext *ac = redisAsyncInitialize(c);
147
+ __redisAsyncCopyError(ac);
148
+ return ac;
149
+ }
150
+
151
+ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
152
+ redisContext *c = redisConnectUnixNonBlock(path);
153
+ redisAsyncContext *ac = redisAsyncInitialize(c);
154
+ __redisAsyncCopyError(ac);
155
+ return ac;
156
+ }
157
+
158
+ int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
159
+ if (ac->onConnect == NULL) {
160
+ ac->onConnect = fn;
161
+
162
+ /* The common way to detect an established connection is to wait for
163
+ * the first write event to be fired. This assumes the related event
164
+ * library functions are already set. */
165
+ _EL_ADD_WRITE(ac);
166
+ return REDIS_OK;
167
+ }
168
+ return REDIS_ERR;
169
+ }
170
+
171
+ int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
172
+ if (ac->onDisconnect == NULL) {
173
+ ac->onDisconnect = fn;
174
+ return REDIS_OK;
175
+ }
176
+ return REDIS_ERR;
177
+ }
178
+
179
+ /* Helper functions to push/shift callbacks */
180
+ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
181
+ redisCallback *cb;
182
+
183
+ /* Copy callback from stack to heap */
184
+ cb = malloc(sizeof(*cb));
185
+ if (source != NULL) {
186
+ memcpy(cb,source,sizeof(*cb));
187
+ cb->next = NULL;
188
+ }
189
+
190
+ /* Store callback in list */
191
+ if (list->head == NULL)
192
+ list->head = cb;
193
+ if (list->tail != NULL)
194
+ list->tail->next = cb;
195
+ list->tail = cb;
196
+ return REDIS_OK;
197
+ }
198
+
199
+ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
200
+ redisCallback *cb = list->head;
201
+ if (cb != NULL) {
202
+ list->head = cb->next;
203
+ if (cb == list->tail)
204
+ list->tail = NULL;
205
+
206
+ /* Copy callback from heap to stack */
207
+ if (target != NULL)
208
+ memcpy(target,cb,sizeof(*cb));
209
+ free(cb);
210
+ return REDIS_OK;
211
+ }
212
+ return REDIS_ERR;
213
+ }
214
+
215
+ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
216
+ redisContext *c = &(ac->c);
217
+ if (cb->fn != NULL) {
218
+ c->flags |= REDIS_IN_CALLBACK;
219
+ cb->fn(ac,reply,cb->privdata);
220
+ c->flags &= ~REDIS_IN_CALLBACK;
221
+ }
222
+ }
223
+
224
+ /* Helper function to free the context. */
225
+ static void __redisAsyncFree(redisAsyncContext *ac) {
226
+ redisContext *c = &(ac->c);
227
+ redisCallback cb;
228
+ dictIterator *it;
229
+ dictEntry *de;
230
+
231
+ /* Execute pending callbacks with NULL reply. */
232
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
233
+ __redisRunCallback(ac,&cb,NULL);
234
+
235
+ /* Execute callbacks for invalid commands */
236
+ while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
237
+ __redisRunCallback(ac,&cb,NULL);
238
+
239
+ /* Run subscription callbacks callbacks with NULL reply */
240
+ it = dictGetIterator(ac->sub.channels);
241
+ while ((de = dictNext(it)) != NULL)
242
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
243
+ dictReleaseIterator(it);
244
+ dictRelease(ac->sub.channels);
245
+
246
+ it = dictGetIterator(ac->sub.patterns);
247
+ while ((de = dictNext(it)) != NULL)
248
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
249
+ dictReleaseIterator(it);
250
+ dictRelease(ac->sub.patterns);
251
+
252
+ /* Signal event lib to clean up */
253
+ _EL_CLEANUP(ac);
254
+
255
+ /* Execute disconnect callback. When redisAsyncFree() initiated destroying
256
+ * this context, the status will always be REDIS_OK. */
257
+ if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
258
+ if (c->flags & REDIS_FREEING) {
259
+ ac->onDisconnect(ac,REDIS_OK);
260
+ } else {
261
+ ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
262
+ }
263
+ }
264
+
265
+ /* Cleanup self */
266
+ redisFree(c);
267
+ }
268
+
269
+ /* Free the async context. When this function is called from a callback,
270
+ * control needs to be returned to redisProcessCallbacks() before actual
271
+ * free'ing. To do so, a flag is set on the context which is picked up by
272
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
273
+ void redisAsyncFree(redisAsyncContext *ac) {
274
+ redisContext *c = &(ac->c);
275
+ c->flags |= REDIS_FREEING;
276
+ if (!(c->flags & REDIS_IN_CALLBACK))
277
+ __redisAsyncFree(ac);
278
+ }
279
+
280
+ /* Helper function to make the disconnect happen and clean up. */
281
+ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
282
+ redisContext *c = &(ac->c);
283
+
284
+ /* Make sure error is accessible if there is any */
285
+ __redisAsyncCopyError(ac);
286
+
287
+ if (ac->err == 0) {
288
+ /* For clean disconnects, there should be no pending callbacks. */
289
+ assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
290
+ } else {
291
+ /* Disconnection is caused by an error, make sure that pending
292
+ * callbacks cannot call new commands. */
293
+ c->flags |= REDIS_DISCONNECTING;
294
+ }
295
+
296
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
297
+ * callbacks with a NULL-reply. */
298
+ __redisAsyncFree(ac);
299
+ }
300
+
301
+ /* Tries to do a clean disconnect from Redis, meaning it stops new commands
302
+ * from being issued, but tries to flush the output buffer and execute
303
+ * callbacks for all remaining replies. When this function is called from a
304
+ * callback, there might be more replies and we can safely defer disconnecting
305
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
306
+ * when there are no pending callbacks. */
307
+ void redisAsyncDisconnect(redisAsyncContext *ac) {
308
+ redisContext *c = &(ac->c);
309
+ c->flags |= REDIS_DISCONNECTING;
310
+ if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
311
+ __redisAsyncDisconnect(ac);
312
+ }
313
+
314
+ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
315
+ redisContext *c = &(ac->c);
316
+ dict *callbacks;
317
+ dictEntry *de;
318
+ int pvariant;
319
+ char *stype;
320
+ sds sname;
321
+
322
+ /* Custom reply functions are not supported for pub/sub. This will fail
323
+ * very hard when they are used... */
324
+ if (reply->type == REDIS_REPLY_ARRAY) {
325
+ assert(reply->elements >= 2);
326
+ assert(reply->element[0]->type == REDIS_REPLY_STRING);
327
+ stype = reply->element[0]->str;
328
+ pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
329
+
330
+ if (pvariant)
331
+ callbacks = ac->sub.patterns;
332
+ else
333
+ callbacks = ac->sub.channels;
334
+
335
+ /* Locate the right callback */
336
+ assert(reply->element[1]->type == REDIS_REPLY_STRING);
337
+ sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
338
+ de = dictFind(callbacks,sname);
339
+ if (de != NULL) {
340
+ memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
341
+
342
+ /* If this is an unsubscribe message, remove it. */
343
+ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
344
+ dictDelete(callbacks,sname);
345
+
346
+ /* If this was the last unsubscribe message, revert to
347
+ * non-subscribe mode. */
348
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
349
+ if (reply->element[2]->integer == 0)
350
+ c->flags &= ~REDIS_SUBSCRIBED;
351
+ }
352
+ }
353
+ sdsfree(sname);
354
+ } else {
355
+ /* Shift callback for invalid commands. */
356
+ __redisShiftCallback(&ac->sub.invalid,dstcb);
357
+ }
358
+ return REDIS_OK;
359
+ }
360
+
361
+ void redisProcessCallbacks(redisAsyncContext *ac) {
362
+ redisContext *c = &(ac->c);
363
+ redisCallback cb;
364
+ void *reply = NULL;
365
+ int status;
366
+
367
+ while((status = redisGetReply(c,&reply)) == REDIS_OK) {
368
+ if (reply == NULL) {
369
+ /* When the connection is being disconnected and there are
370
+ * no more replies, this is the cue to really disconnect. */
371
+ if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
372
+ __redisAsyncDisconnect(ac);
373
+ return;
374
+ }
375
+
376
+ /* If monitor mode, repush callback */
377
+ if(c->flags & REDIS_MONITORING) {
378
+ __redisPushCallback(&ac->replies,&cb);
379
+ }
380
+
381
+ /* When the connection is not being disconnected, simply stop
382
+ * trying to get replies and wait for the next loop tick. */
383
+ break;
384
+ }
385
+
386
+ /* Even if the context is subscribed, pending regular callbacks will
387
+ * get a reply before pub/sub messages arrive. */
388
+ if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
389
+ /*
390
+ * A spontaneous reply in a not-subscribed context can be the error
391
+ * reply that is sent when a new connection exceeds the maximum
392
+ * number of allowed connections on the server side.
393
+ *
394
+ * This is seen as an error instead of a regular reply because the
395
+ * server closes the connection after sending it.
396
+ *
397
+ * To prevent the error from being overwritten by an EOF error the
398
+ * connection is closed here. See issue #43.
399
+ *
400
+ * Another possibility is that the server is loading its dataset.
401
+ * In this case we also want to close the connection, and have the
402
+ * user wait until the server is ready to take our request.
403
+ */
404
+ if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
405
+ c->err = REDIS_ERR_OTHER;
406
+ snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
407
+ __redisAsyncDisconnect(ac);
408
+ return;
409
+ }
410
+ /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
411
+ assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
412
+ if(c->flags & REDIS_SUBSCRIBED)
413
+ __redisGetSubscribeCallback(ac,reply,&cb);
414
+ }
415
+
416
+ if (cb.fn != NULL) {
417
+ __redisRunCallback(ac,&cb,reply);
418
+ c->reader->fn->freeObject(reply);
419
+
420
+ /* Proceed with free'ing when redisAsyncFree() was called. */
421
+ if (c->flags & REDIS_FREEING) {
422
+ __redisAsyncFree(ac);
423
+ return;
424
+ }
425
+ } else {
426
+ /* No callback for this reply. This can either be a NULL callback,
427
+ * or there were no callbacks to begin with. Either way, don't
428
+ * abort with an error, but simply ignore it because the client
429
+ * doesn't know what the server will spit out over the wire. */
430
+ c->reader->fn->freeObject(reply);
431
+ }
432
+ }
433
+
434
+ /* Disconnect when there was an error reading the reply */
435
+ if (status != REDIS_OK)
436
+ __redisAsyncDisconnect(ac);
437
+ }
438
+
439
+ /* Internal helper function to detect socket status the first time a read or
440
+ * write event fires. When connecting was not succesful, the connect callback
441
+ * is called with a REDIS_ERR status and the context is free'd. */
442
+ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
443
+ redisContext *c = &(ac->c);
444
+
445
+ if (redisCheckSocketError(c,c->fd) == REDIS_ERR) {
446
+ /* Try again later when connect(2) is still in progress. */
447
+ if (errno == EINPROGRESS)
448
+ return REDIS_OK;
449
+
450
+ if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
451
+ __redisAsyncDisconnect(ac);
452
+ return REDIS_ERR;
453
+ }
454
+
455
+ /* Mark context as connected. */
456
+ c->flags |= REDIS_CONNECTED;
457
+ if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
458
+ return REDIS_OK;
459
+ }
460
+
461
+ /* This function should be called when the socket is readable.
462
+ * It processes all replies that can be read and executes their callbacks.
463
+ */
464
+ void redisAsyncHandleRead(redisAsyncContext *ac) {
465
+ redisContext *c = &(ac->c);
466
+
467
+ if (!(c->flags & REDIS_CONNECTED)) {
468
+ /* Abort connect was not successful. */
469
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
470
+ return;
471
+ /* Try again later when the context is still not connected. */
472
+ if (!(c->flags & REDIS_CONNECTED))
473
+ return;
474
+ }
475
+
476
+ if (redisBufferRead(c) == REDIS_ERR) {
477
+ __redisAsyncDisconnect(ac);
478
+ } else {
479
+ /* Always re-schedule reads */
480
+ _EL_ADD_READ(ac);
481
+ redisProcessCallbacks(ac);
482
+ }
483
+ }
484
+
485
+ void redisAsyncHandleWrite(redisAsyncContext *ac) {
486
+ redisContext *c = &(ac->c);
487
+ int done = 0;
488
+
489
+ if (!(c->flags & REDIS_CONNECTED)) {
490
+ /* Abort connect was not successful. */
491
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
492
+ return;
493
+ /* Try again later when the context is still not connected. */
494
+ if (!(c->flags & REDIS_CONNECTED))
495
+ return;
496
+ }
497
+
498
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
499
+ __redisAsyncDisconnect(ac);
500
+ } else {
501
+ /* Continue writing when not done, stop writing otherwise */
502
+ if (!done)
503
+ _EL_ADD_WRITE(ac);
504
+ else
505
+ _EL_DEL_WRITE(ac);
506
+
507
+ /* Always schedule reads after writes */
508
+ _EL_ADD_READ(ac);
509
+ }
510
+ }
511
+
512
+ /* Sets a pointer to the first argument and its length starting at p. Returns
513
+ * the number of bytes to skip to get to the following argument. */
514
+ static char *nextArgument(char *start, char **str, size_t *len) {
515
+ char *p = start;
516
+ if (p[0] != '$') {
517
+ p = strchr(p,'$');
518
+ if (p == NULL) return NULL;
519
+ }
520
+
521
+ *len = (int)strtol(p+1,NULL,10);
522
+ p = strchr(p,'\r');
523
+ assert(p);
524
+ *str = p+2;
525
+ return p+2+(*len)+2;
526
+ }
527
+
528
+ /* Helper function for the redisAsyncCommand* family of functions. Writes a
529
+ * formatted command to the output buffer and registers the provided callback
530
+ * function with the context. */
531
+ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
532
+ redisContext *c = &(ac->c);
533
+ redisCallback cb;
534
+ int pvariant, hasnext;
535
+ char *cstr, *astr;
536
+ size_t clen, alen;
537
+ char *p;
538
+ sds sname;
539
+
540
+ /* Don't accept new commands when the connection is about to be closed. */
541
+ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
542
+
543
+ /* Setup callback */
544
+ cb.fn = fn;
545
+ cb.privdata = privdata;
546
+
547
+ /* Find out which command will be appended. */
548
+ p = nextArgument(cmd,&cstr,&clen);
549
+ assert(p != NULL);
550
+ hasnext = (p[0] == '$');
551
+ pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
552
+ cstr += pvariant;
553
+ clen -= pvariant;
554
+
555
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
556
+ c->flags |= REDIS_SUBSCRIBED;
557
+
558
+ /* Add every channel/pattern to the list of subscription callbacks. */
559
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
560
+ sname = sdsnewlen(astr,alen);
561
+ if (pvariant)
562
+ dictReplace(ac->sub.patterns,sname,&cb);
563
+ else
564
+ dictReplace(ac->sub.channels,sname,&cb);
565
+ }
566
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
567
+ /* It is only useful to call (P)UNSUBSCRIBE when the context is
568
+ * subscribed to one or more channels or patterns. */
569
+ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
570
+
571
+ /* (P)UNSUBSCRIBE does not have its own response: every channel or
572
+ * pattern that is unsubscribed will receive a message. This means we
573
+ * should not append a callback function for this command. */
574
+ } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
575
+ /* Set monitor flag and push callback */
576
+ c->flags |= REDIS_MONITORING;
577
+ __redisPushCallback(&ac->replies,&cb);
578
+ } else {
579
+ if (c->flags & REDIS_SUBSCRIBED)
580
+ /* This will likely result in an error reply, but it needs to be
581
+ * received and passed to the callback. */
582
+ __redisPushCallback(&ac->sub.invalid,&cb);
583
+ else
584
+ __redisPushCallback(&ac->replies,&cb);
585
+ }
586
+
587
+ __redisAppendCommand(c,cmd,len);
588
+
589
+ /* Always schedule a write when the write buffer is non-empty */
590
+ _EL_ADD_WRITE(ac);
591
+
592
+ return REDIS_OK;
593
+ }
594
+
595
+ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
596
+ char *cmd;
597
+ int len;
598
+ int status;
599
+ len = redisvFormatCommand(&cmd,format,ap);
600
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
601
+ free(cmd);
602
+ return status;
603
+ }
604
+
605
+ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
606
+ va_list ap;
607
+ int status;
608
+ va_start(ap,format);
609
+ status = redisvAsyncCommand(ac,fn,privdata,format,ap);
610
+ va_end(ap);
611
+ return status;
612
+ }
613
+
614
+ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
615
+ char *cmd;
616
+ int len;
617
+ int status;
618
+ len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
619
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
620
+ free(cmd);
621
+ return status;
622
+ }