methodmissing_hiredis 0.4.6

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.
@@ -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
+ }