methodmissing_hiredis 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,183 @@
1
+ require "hiredis/version"
2
+
3
+ module Hiredis
4
+ module Ruby
5
+ class Reader
6
+
7
+ def initialize
8
+ @buffer = Buffer.new
9
+ @task = Task.new(@buffer)
10
+ end
11
+
12
+ def feed(data)
13
+ @buffer << data
14
+ end
15
+
16
+ def gets
17
+ reply = @task.process
18
+ @buffer.discard!
19
+ reply
20
+ end
21
+
22
+ protected
23
+
24
+ class Task
25
+
26
+ # Use lookup table to map reply types to methods
27
+ method_index = {}
28
+ method_index[?-] = :process_error_reply
29
+ method_index[?+] = :process_status_reply
30
+ method_index[?:] = :process_integer_reply
31
+ method_index[?$] = :process_bulk_reply
32
+ method_index[?*] = :process_multi_bulk_reply
33
+ METHOD_INDEX = method_index.freeze
34
+
35
+ attr_reader :parent
36
+ attr_reader :depth
37
+ attr_accessor :multi_bulk
38
+
39
+ def initialize(buffer, parent = nil, depth = 0)
40
+ @buffer, @parent, @depth = buffer, parent, depth
41
+ end
42
+
43
+ # Note: task depth is not checked.
44
+ def child
45
+ @child ||= Task.new(@buffer, self, depth + 1)
46
+ end
47
+
48
+ def root
49
+ parent ? parent.root : self
50
+ end
51
+
52
+ def reset!
53
+ @line = @type = @multi_bulk = nil
54
+ end
55
+
56
+ def process_error_reply
57
+ RuntimeError.new(@line)
58
+ end
59
+
60
+ def process_status_reply
61
+ @line
62
+ end
63
+
64
+ def process_integer_reply
65
+ @line.to_i
66
+ end
67
+
68
+ def process_bulk_reply
69
+ bulk_length = @line.to_i
70
+ return nil if bulk_length < 0
71
+
72
+ # Caught by caller function when false
73
+ @buffer.read(bulk_length, 2)
74
+ end
75
+
76
+ def process_multi_bulk_reply
77
+ multi_bulk_length = @line.to_i
78
+
79
+ if multi_bulk_length > 0
80
+ @multi_bulk ||= []
81
+
82
+ # We know the multi bulk is not complete when this path is taken.
83
+ while (element = child.process) != false
84
+ @multi_bulk << element
85
+ return @multi_bulk if @multi_bulk.length == multi_bulk_length
86
+ end
87
+
88
+ false
89
+ elsif multi_bulk_length == 0
90
+ []
91
+ else
92
+ nil
93
+ end
94
+ end
95
+
96
+ def process_protocol_error
97
+ raise "Protocol error"
98
+ end
99
+
100
+ def process
101
+ @line ||= @buffer.read_line
102
+ return false if @line == false
103
+
104
+ @type ||= @line.slice!(0)
105
+ reply = send(METHOD_INDEX[@type] || :process_protocol_error)
106
+
107
+ reset! if reply != false
108
+ reply
109
+ end
110
+ end
111
+
112
+ class Buffer
113
+
114
+ CRLF = "\r\n".freeze
115
+
116
+ def initialize
117
+ @buffer = ""
118
+ @length = @pos = 0
119
+ end
120
+
121
+ def <<(data)
122
+ @length += data.length
123
+ @buffer << data
124
+ end
125
+
126
+ def length
127
+ @length
128
+ end
129
+
130
+ def empty?
131
+ @length == 0
132
+ end
133
+
134
+ def discard!
135
+ if @length == 0
136
+ @buffer = ""
137
+ @length = @pos = 0
138
+ else
139
+ if @pos >= 1024
140
+ @buffer.slice!(0, @pos)
141
+ @length -= @pos
142
+ @pos = 0
143
+ end
144
+ end
145
+ end
146
+
147
+ def read(bytes, skip = 0)
148
+ start = @pos
149
+ stop = start + bytes + skip
150
+ return false if @length < stop
151
+
152
+ @pos = stop
153
+ force_encoding @buffer[start, bytes]
154
+ end
155
+
156
+ def read_line
157
+ start = @pos
158
+ stop = @buffer.index(CRLF, @pos)
159
+ return false unless stop
160
+
161
+ @pos = stop + 2 # include CRLF
162
+ force_encoding @buffer[start, stop - start]
163
+ end
164
+
165
+ private
166
+
167
+ if "".respond_to?(:force_encoding)
168
+
169
+ def force_encoding(str)
170
+ str.force_encoding(Encoding.default_external)
171
+ end
172
+
173
+ else
174
+
175
+ def force_encoding(str)
176
+ str
177
+ end
178
+
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,3 @@
1
+ module Hiredis
2
+ VERSION = "0.4.6"
3
+ end
@@ -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,647 @@
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;
106
+
107
+ ac = realloc(c,sizeof(redisAsyncContext));
108
+ if (ac == NULL)
109
+ return NULL;
110
+
111
+ c = &(ac->c);
112
+
113
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
114
+ * For the async API, we want to wait until the first write event is
115
+ * received up before setting this flag, so reset it here. */
116
+ c->flags &= ~REDIS_CONNECTED;
117
+
118
+ ac->err = 0;
119
+ ac->errstr = NULL;
120
+ ac->data = NULL;
121
+
122
+ ac->ev.data = NULL;
123
+ ac->ev.addRead = NULL;
124
+ ac->ev.delRead = NULL;
125
+ ac->ev.addWrite = NULL;
126
+ ac->ev.delWrite = NULL;
127
+ ac->ev.cleanup = NULL;
128
+
129
+ ac->onConnect = NULL;
130
+ ac->onDisconnect = NULL;
131
+
132
+ ac->replies.head = NULL;
133
+ ac->replies.tail = NULL;
134
+ ac->sub.invalid.head = NULL;
135
+ ac->sub.invalid.tail = NULL;
136
+ ac->sub.channels = dictCreate(&callbackDict,NULL);
137
+ ac->sub.patterns = dictCreate(&callbackDict,NULL);
138
+ return ac;
139
+ }
140
+
141
+ /* We want the error field to be accessible directly instead of requiring
142
+ * an indirection to the redisContext struct. */
143
+ static void __redisAsyncCopyError(redisAsyncContext *ac) {
144
+ redisContext *c = &(ac->c);
145
+ ac->err = c->err;
146
+ ac->errstr = c->errstr;
147
+ }
148
+
149
+ redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
150
+ redisContext *c;
151
+ redisAsyncContext *ac;
152
+
153
+ c = redisConnectNonBlock(ip,port);
154
+ if (c == NULL)
155
+ return NULL;
156
+
157
+ ac = redisAsyncInitialize(c);
158
+ if (ac == NULL) {
159
+ redisFree(c);
160
+ return NULL;
161
+ }
162
+
163
+ __redisAsyncCopyError(ac);
164
+ return ac;
165
+ }
166
+
167
+ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
168
+ redisContext *c;
169
+ redisAsyncContext *ac;
170
+
171
+ c = redisConnectUnixNonBlock(path);
172
+ if (c == NULL)
173
+ return NULL;
174
+
175
+ ac = redisAsyncInitialize(c);
176
+ __redisAsyncCopyError(ac);
177
+ return ac;
178
+ }
179
+
180
+ int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
181
+ if (ac->onConnect == NULL) {
182
+ ac->onConnect = fn;
183
+
184
+ /* The common way to detect an established connection is to wait for
185
+ * the first write event to be fired. This assumes the related event
186
+ * library functions are already set. */
187
+ _EL_ADD_WRITE(ac);
188
+ return REDIS_OK;
189
+ }
190
+ return REDIS_ERR;
191
+ }
192
+
193
+ int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
194
+ if (ac->onDisconnect == NULL) {
195
+ ac->onDisconnect = fn;
196
+ return REDIS_OK;
197
+ }
198
+ return REDIS_ERR;
199
+ }
200
+
201
+ /* Helper functions to push/shift callbacks */
202
+ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
203
+ redisCallback *cb;
204
+
205
+ /* Copy callback from stack to heap */
206
+ cb = malloc(sizeof(*cb));
207
+ if (cb == NULL)
208
+ return REDIS_ERR_OOM;
209
+
210
+ if (source != NULL) {
211
+ memcpy(cb,source,sizeof(*cb));
212
+ cb->next = NULL;
213
+ }
214
+
215
+ /* Store callback in list */
216
+ if (list->head == NULL)
217
+ list->head = cb;
218
+ if (list->tail != NULL)
219
+ list->tail->next = cb;
220
+ list->tail = cb;
221
+ return REDIS_OK;
222
+ }
223
+
224
+ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
225
+ redisCallback *cb = list->head;
226
+ if (cb != NULL) {
227
+ list->head = cb->next;
228
+ if (cb == list->tail)
229
+ list->tail = NULL;
230
+
231
+ /* Copy callback from heap to stack */
232
+ if (target != NULL)
233
+ memcpy(target,cb,sizeof(*cb));
234
+ free(cb);
235
+ return REDIS_OK;
236
+ }
237
+ return REDIS_ERR;
238
+ }
239
+
240
+ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
241
+ redisContext *c = &(ac->c);
242
+ if (cb->fn != NULL) {
243
+ c->flags |= REDIS_IN_CALLBACK;
244
+ cb->fn(ac,reply,cb->privdata);
245
+ c->flags &= ~REDIS_IN_CALLBACK;
246
+ }
247
+ }
248
+
249
+ /* Helper function to free the context. */
250
+ static void __redisAsyncFree(redisAsyncContext *ac) {
251
+ redisContext *c = &(ac->c);
252
+ redisCallback cb;
253
+ dictIterator *it;
254
+ dictEntry *de;
255
+
256
+ /* Execute pending callbacks with NULL reply. */
257
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
258
+ __redisRunCallback(ac,&cb,NULL);
259
+
260
+ /* Execute callbacks for invalid commands */
261
+ while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
262
+ __redisRunCallback(ac,&cb,NULL);
263
+
264
+ /* Run subscription callbacks callbacks with NULL reply */
265
+ it = dictGetIterator(ac->sub.channels);
266
+ while ((de = dictNext(it)) != NULL)
267
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
268
+ dictReleaseIterator(it);
269
+ dictRelease(ac->sub.channels);
270
+
271
+ it = dictGetIterator(ac->sub.patterns);
272
+ while ((de = dictNext(it)) != NULL)
273
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
274
+ dictReleaseIterator(it);
275
+ dictRelease(ac->sub.patterns);
276
+
277
+ /* Signal event lib to clean up */
278
+ _EL_CLEANUP(ac);
279
+
280
+ /* Execute disconnect callback. When redisAsyncFree() initiated destroying
281
+ * this context, the status will always be REDIS_OK. */
282
+ if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
283
+ if (c->flags & REDIS_FREEING) {
284
+ ac->onDisconnect(ac,REDIS_OK);
285
+ } else {
286
+ ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
287
+ }
288
+ }
289
+
290
+ /* Cleanup self */
291
+ redisFree(c);
292
+ }
293
+
294
+ /* Free the async context. When this function is called from a callback,
295
+ * control needs to be returned to redisProcessCallbacks() before actual
296
+ * free'ing. To do so, a flag is set on the context which is picked up by
297
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
298
+ void redisAsyncFree(redisAsyncContext *ac) {
299
+ redisContext *c = &(ac->c);
300
+ c->flags |= REDIS_FREEING;
301
+ if (!(c->flags & REDIS_IN_CALLBACK))
302
+ __redisAsyncFree(ac);
303
+ }
304
+
305
+ /* Helper function to make the disconnect happen and clean up. */
306
+ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
307
+ redisContext *c = &(ac->c);
308
+
309
+ /* Make sure error is accessible if there is any */
310
+ __redisAsyncCopyError(ac);
311
+
312
+ if (ac->err == 0) {
313
+ /* For clean disconnects, there should be no pending callbacks. */
314
+ assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
315
+ } else {
316
+ /* Disconnection is caused by an error, make sure that pending
317
+ * callbacks cannot call new commands. */
318
+ c->flags |= REDIS_DISCONNECTING;
319
+ }
320
+
321
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
322
+ * callbacks with a NULL-reply. */
323
+ __redisAsyncFree(ac);
324
+ }
325
+
326
+ /* Tries to do a clean disconnect from Redis, meaning it stops new commands
327
+ * from being issued, but tries to flush the output buffer and execute
328
+ * callbacks for all remaining replies. When this function is called from a
329
+ * callback, there might be more replies and we can safely defer disconnecting
330
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
331
+ * when there are no pending callbacks. */
332
+ void redisAsyncDisconnect(redisAsyncContext *ac) {
333
+ redisContext *c = &(ac->c);
334
+ c->flags |= REDIS_DISCONNECTING;
335
+ if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
336
+ __redisAsyncDisconnect(ac);
337
+ }
338
+
339
+ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
340
+ redisContext *c = &(ac->c);
341
+ dict *callbacks;
342
+ dictEntry *de;
343
+ int pvariant;
344
+ char *stype;
345
+ sds sname;
346
+
347
+ /* Custom reply functions are not supported for pub/sub. This will fail
348
+ * very hard when they are used... */
349
+ if (reply->type == REDIS_REPLY_ARRAY) {
350
+ assert(reply->elements >= 2);
351
+ assert(reply->element[0]->type == REDIS_REPLY_STRING);
352
+ stype = reply->element[0]->str;
353
+ pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
354
+
355
+ if (pvariant)
356
+ callbacks = ac->sub.patterns;
357
+ else
358
+ callbacks = ac->sub.channels;
359
+
360
+ /* Locate the right callback */
361
+ assert(reply->element[1]->type == REDIS_REPLY_STRING);
362
+ sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
363
+ de = dictFind(callbacks,sname);
364
+ if (de != NULL) {
365
+ memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
366
+
367
+ /* If this is an unsubscribe message, remove it. */
368
+ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
369
+ dictDelete(callbacks,sname);
370
+
371
+ /* If this was the last unsubscribe message, revert to
372
+ * non-subscribe mode. */
373
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
374
+ if (reply->element[2]->integer == 0)
375
+ c->flags &= ~REDIS_SUBSCRIBED;
376
+ }
377
+ }
378
+ sdsfree(sname);
379
+ } else {
380
+ /* Shift callback for invalid commands. */
381
+ __redisShiftCallback(&ac->sub.invalid,dstcb);
382
+ }
383
+ return REDIS_OK;
384
+ }
385
+
386
+ void redisProcessCallbacks(redisAsyncContext *ac) {
387
+ redisContext *c = &(ac->c);
388
+ redisCallback cb;
389
+ void *reply = NULL;
390
+ int status;
391
+
392
+ while((status = redisGetReply(c,&reply)) == REDIS_OK) {
393
+ if (reply == NULL) {
394
+ /* When the connection is being disconnected and there are
395
+ * no more replies, this is the cue to really disconnect. */
396
+ if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
397
+ __redisAsyncDisconnect(ac);
398
+ return;
399
+ }
400
+
401
+ /* If monitor mode, repush callback */
402
+ if(c->flags & REDIS_MONITORING) {
403
+ __redisPushCallback(&ac->replies,&cb);
404
+ }
405
+
406
+ /* When the connection is not being disconnected, simply stop
407
+ * trying to get replies and wait for the next loop tick. */
408
+ break;
409
+ }
410
+
411
+ /* Even if the context is subscribed, pending regular callbacks will
412
+ * get a reply before pub/sub messages arrive. */
413
+ if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
414
+ /*
415
+ * A spontaneous reply in a not-subscribed context can be the error
416
+ * reply that is sent when a new connection exceeds the maximum
417
+ * number of allowed connections on the server side.
418
+ *
419
+ * This is seen as an error instead of a regular reply because the
420
+ * server closes the connection after sending it.
421
+ *
422
+ * To prevent the error from being overwritten by an EOF error the
423
+ * connection is closed here. See issue #43.
424
+ *
425
+ * Another possibility is that the server is loading its dataset.
426
+ * In this case we also want to close the connection, and have the
427
+ * user wait until the server is ready to take our request.
428
+ */
429
+ if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
430
+ c->err = REDIS_ERR_OTHER;
431
+ snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
432
+ __redisAsyncDisconnect(ac);
433
+ return;
434
+ }
435
+ /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
436
+ assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
437
+ if(c->flags & REDIS_SUBSCRIBED)
438
+ __redisGetSubscribeCallback(ac,reply,&cb);
439
+ }
440
+
441
+ if (cb.fn != NULL) {
442
+ __redisRunCallback(ac,&cb,reply);
443
+ c->reader->fn->freeObject(reply);
444
+
445
+ /* Proceed with free'ing when redisAsyncFree() was called. */
446
+ if (c->flags & REDIS_FREEING) {
447
+ __redisAsyncFree(ac);
448
+ return;
449
+ }
450
+ } else {
451
+ /* No callback for this reply. This can either be a NULL callback,
452
+ * or there were no callbacks to begin with. Either way, don't
453
+ * abort with an error, but simply ignore it because the client
454
+ * doesn't know what the server will spit out over the wire. */
455
+ c->reader->fn->freeObject(reply);
456
+ }
457
+ }
458
+
459
+ /* Disconnect when there was an error reading the reply */
460
+ if (status != REDIS_OK)
461
+ __redisAsyncDisconnect(ac);
462
+ }
463
+
464
+ /* Internal helper function to detect socket status the first time a read or
465
+ * write event fires. When connecting was not succesful, the connect callback
466
+ * is called with a REDIS_ERR status and the context is free'd. */
467
+ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
468
+ redisContext *c = &(ac->c);
469
+
470
+ if (redisCheckSocketError(c,c->fd) == REDIS_ERR) {
471
+ /* Try again later when connect(2) is still in progress. */
472
+ if (errno == EINPROGRESS)
473
+ return REDIS_OK;
474
+
475
+ if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
476
+ __redisAsyncDisconnect(ac);
477
+ return REDIS_ERR;
478
+ }
479
+
480
+ /* Mark context as connected. */
481
+ c->flags |= REDIS_CONNECTED;
482
+ if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
483
+ return REDIS_OK;
484
+ }
485
+
486
+ /* This function should be called when the socket is readable.
487
+ * It processes all replies that can be read and executes their callbacks.
488
+ */
489
+ void redisAsyncHandleRead(redisAsyncContext *ac) {
490
+ redisContext *c = &(ac->c);
491
+
492
+ if (!(c->flags & REDIS_CONNECTED)) {
493
+ /* Abort connect was not successful. */
494
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
495
+ return;
496
+ /* Try again later when the context is still not connected. */
497
+ if (!(c->flags & REDIS_CONNECTED))
498
+ return;
499
+ }
500
+
501
+ if (redisBufferRead(c) == REDIS_ERR) {
502
+ __redisAsyncDisconnect(ac);
503
+ } else {
504
+ /* Always re-schedule reads */
505
+ _EL_ADD_READ(ac);
506
+ redisProcessCallbacks(ac);
507
+ }
508
+ }
509
+
510
+ void redisAsyncHandleWrite(redisAsyncContext *ac) {
511
+ redisContext *c = &(ac->c);
512
+ int done = 0;
513
+
514
+ if (!(c->flags & REDIS_CONNECTED)) {
515
+ /* Abort connect was not successful. */
516
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
517
+ return;
518
+ /* Try again later when the context is still not connected. */
519
+ if (!(c->flags & REDIS_CONNECTED))
520
+ return;
521
+ }
522
+
523
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
524
+ __redisAsyncDisconnect(ac);
525
+ } else {
526
+ /* Continue writing when not done, stop writing otherwise */
527
+ if (!done)
528
+ _EL_ADD_WRITE(ac);
529
+ else
530
+ _EL_DEL_WRITE(ac);
531
+
532
+ /* Always schedule reads after writes */
533
+ _EL_ADD_READ(ac);
534
+ }
535
+ }
536
+
537
+ /* Sets a pointer to the first argument and its length starting at p. Returns
538
+ * the number of bytes to skip to get to the following argument. */
539
+ static char *nextArgument(char *start, char **str, size_t *len) {
540
+ char *p = start;
541
+ if (p[0] != '$') {
542
+ p = strchr(p,'$');
543
+ if (p == NULL) return NULL;
544
+ }
545
+
546
+ *len = (int)strtol(p+1,NULL,10);
547
+ p = strchr(p,'\r');
548
+ assert(p);
549
+ *str = p+2;
550
+ return p+2+(*len)+2;
551
+ }
552
+
553
+ /* Helper function for the redisAsyncCommand* family of functions. Writes a
554
+ * formatted command to the output buffer and registers the provided callback
555
+ * function with the context. */
556
+ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
557
+ redisContext *c = &(ac->c);
558
+ redisCallback cb;
559
+ int pvariant, hasnext;
560
+ char *cstr, *astr;
561
+ size_t clen, alen;
562
+ char *p;
563
+ sds sname;
564
+
565
+ /* Don't accept new commands when the connection is about to be closed. */
566
+ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
567
+
568
+ /* Setup callback */
569
+ cb.fn = fn;
570
+ cb.privdata = privdata;
571
+
572
+ /* Find out which command will be appended. */
573
+ p = nextArgument(cmd,&cstr,&clen);
574
+ assert(p != NULL);
575
+ hasnext = (p[0] == '$');
576
+ pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
577
+ cstr += pvariant;
578
+ clen -= pvariant;
579
+
580
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
581
+ c->flags |= REDIS_SUBSCRIBED;
582
+
583
+ /* Add every channel/pattern to the list of subscription callbacks. */
584
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
585
+ sname = sdsnewlen(astr,alen);
586
+ if (pvariant)
587
+ dictReplace(ac->sub.patterns,sname,&cb);
588
+ else
589
+ dictReplace(ac->sub.channels,sname,&cb);
590
+ }
591
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
592
+ /* It is only useful to call (P)UNSUBSCRIBE when the context is
593
+ * subscribed to one or more channels or patterns. */
594
+ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
595
+
596
+ /* (P)UNSUBSCRIBE does not have its own response: every channel or
597
+ * pattern that is unsubscribed will receive a message. This means we
598
+ * should not append a callback function for this command. */
599
+ } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
600
+ /* Set monitor flag and push callback */
601
+ c->flags |= REDIS_MONITORING;
602
+ __redisPushCallback(&ac->replies,&cb);
603
+ } else {
604
+ if (c->flags & REDIS_SUBSCRIBED)
605
+ /* This will likely result in an error reply, but it needs to be
606
+ * received and passed to the callback. */
607
+ __redisPushCallback(&ac->sub.invalid,&cb);
608
+ else
609
+ __redisPushCallback(&ac->replies,&cb);
610
+ }
611
+
612
+ __redisAppendCommand(c,cmd,len);
613
+
614
+ /* Always schedule a write when the write buffer is non-empty */
615
+ _EL_ADD_WRITE(ac);
616
+
617
+ return REDIS_OK;
618
+ }
619
+
620
+ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
621
+ char *cmd;
622
+ int len;
623
+ int status;
624
+ len = redisvFormatCommand(&cmd,format,ap);
625
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
626
+ free(cmd);
627
+ return status;
628
+ }
629
+
630
+ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
631
+ va_list ap;
632
+ int status;
633
+ va_start(ap,format);
634
+ status = redisvAsyncCommand(ac,fn,privdata,format,ap);
635
+ va_end(ap);
636
+ return status;
637
+ }
638
+
639
+ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
640
+ char *cmd;
641
+ int len;
642
+ int status;
643
+ len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
644
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
645
+ free(cmd);
646
+ return status;
647
+ }