hiredis 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/hiredis_ext/connection.c +7 -10
- data/ext/hiredis_ext/reader.c +3 -6
- data/lib/hiredis/connection.rb +5 -1
- data/lib/hiredis/em.rb +1 -0
- data/lib/hiredis/em/base.rb +36 -0
- data/lib/hiredis/em/connection.rb +25 -0
- data/lib/hiredis/version.rb +1 -1
- data/vendor/hiredis/Makefile +22 -18
- data/vendor/hiredis/async.c +305 -56
- data/vendor/hiredis/async.h +30 -11
- data/vendor/hiredis/dict.c +388 -0
- data/vendor/hiredis/dict.h +130 -0
- data/vendor/hiredis/hiredis.c +12 -4
- data/vendor/hiredis/hiredis.h +16 -2
- data/vendor/hiredis/net.c +17 -1
- data/vendor/hiredis/net.h +9 -0
- data/vendor/hiredis/test.c +10 -1
- metadata +10 -5
@@ -15,7 +15,6 @@ static void parent_context_try_free(redisParentContext *pc) {
|
|
15
15
|
|
16
16
|
static void parent_context_mark(redisParentContext *pc) {
|
17
17
|
VALUE root;
|
18
|
-
fflush(stdout);
|
19
18
|
if (pc->context && pc->context->reader) {
|
20
19
|
root = (VALUE)redisReplyReaderGetObject(pc->context->reader);
|
21
20
|
if (root != 0 && TYPE(root) == T_ARRAY) {
|
@@ -174,11 +173,6 @@ static VALUE connection_read(VALUE self) {
|
|
174
173
|
}
|
175
174
|
}
|
176
175
|
|
177
|
-
if (rb_ivar_defined(reply,ivar_hiredis_error)) {
|
178
|
-
VALUE error = rb_ivar_get(reply,ivar_hiredis_error);
|
179
|
-
rb_raise(rb_eRuntimeError,"%s",RSTRING_PTR(error));
|
180
|
-
}
|
181
|
-
|
182
176
|
return reply;
|
183
177
|
}
|
184
178
|
|
@@ -187,15 +181,18 @@ static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
|
|
187
181
|
int s = NUM2INT(usecs)/1000000;
|
188
182
|
int us = NUM2INT(usecs)-(s*1000000);
|
189
183
|
struct timeval timeout = { s, us };
|
184
|
+
char errstr[1024];
|
190
185
|
|
191
186
|
Data_Get_Struct(self,redisParentContext,pc);
|
192
187
|
if (!pc->context)
|
193
188
|
rb_raise(rb_eRuntimeError, "not connected");
|
194
189
|
|
195
|
-
if (
|
196
|
-
|
197
|
-
|
198
|
-
|
190
|
+
if (redisSetTimeout(pc->context,timeout) == REDIS_ERR) {
|
191
|
+
snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
|
192
|
+
parent_context_try_free(pc);
|
193
|
+
rb_raise(rb_eRuntimeError,"%s",errstr);
|
194
|
+
}
|
195
|
+
|
199
196
|
return usecs;
|
200
197
|
}
|
201
198
|
|
data/ext/hiredis_ext/reader.c
CHANGED
@@ -35,13 +35,15 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
|
35
35
|
}
|
36
36
|
|
37
37
|
if (task->type == REDIS_REPLY_ERROR) {
|
38
|
+
v = rb_funcall(rb_eRuntimeError,rb_intern("new"),1,v);
|
38
39
|
rb_ivar_set(v,ivar_hiredis_error,v);
|
40
|
+
|
39
41
|
if (task && task->parent != NULL) {
|
40
42
|
/* Also make the parent respond to this method. Redis currently
|
41
43
|
* only emits nested multi bulks of depth 2, so we don't need
|
42
44
|
* to cascade setting this ivar. Make sure to only set the first
|
43
45
|
* error reply on the parent. */
|
44
|
-
VALUE parent = (VALUE)task->parent;
|
46
|
+
VALUE parent = (VALUE)task->parent->obj;
|
45
47
|
if (!rb_ivar_defined(parent,ivar_hiredis_error))
|
46
48
|
rb_ivar_set(parent,ivar_hiredis_error,v);
|
47
49
|
}
|
@@ -111,11 +113,6 @@ static VALUE reader_gets(VALUE klass) {
|
|
111
113
|
rb_raise(rb_eRuntimeError,"%s",errstr);
|
112
114
|
}
|
113
115
|
|
114
|
-
if (rb_ivar_defined(reply,ivar_hiredis_error)) {
|
115
|
-
VALUE error = rb_ivar_get(reply,ivar_hiredis_error);
|
116
|
-
rb_raise(rb_eRuntimeError,"%s",RSTRING_PTR(error));
|
117
|
-
}
|
118
|
-
|
119
116
|
return reply;
|
120
117
|
}
|
121
118
|
|
data/lib/hiredis/connection.rb
CHANGED
data/lib/hiredis/em.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'hiredis/em/connection'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'hiredis/reader'
|
2
|
+
|
3
|
+
module Hiredis
|
4
|
+
|
5
|
+
module EM
|
6
|
+
|
7
|
+
class Base < ::EM::Connection
|
8
|
+
|
9
|
+
CRLF = "\r\n".freeze
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@reader = Reader.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive_data(data)
|
17
|
+
@reader.feed(data)
|
18
|
+
while reply = @reader.gets
|
19
|
+
receive_reply(reply)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive_reply(reply)
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_command(*args)
|
27
|
+
args = args.flatten
|
28
|
+
send_data("*" + args.size.to_s + CRLF)
|
29
|
+
args.each do |arg|
|
30
|
+
arg = arg.to_s
|
31
|
+
send_data("$" + arg.size.to_s + CRLF + arg + CRLF)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'hiredis/em/base'
|
2
|
+
|
3
|
+
module Hiredis
|
4
|
+
|
5
|
+
module EM
|
6
|
+
|
7
|
+
class Connection < Base
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
@callbacks = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def receive_reply(reply)
|
15
|
+
callback = @callbacks.shift
|
16
|
+
callback.call(reply) if callback
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(sym, *args, &callback)
|
20
|
+
send_command(sym, *args)
|
21
|
+
@callbacks.push callback
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/hiredis/version.rb
CHANGED
data/vendor/hiredis/Makefile
CHANGED
@@ -2,35 +2,38 @@
|
|
2
2
|
# Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com>
|
3
3
|
# This file is released under the BSD license, see the COPYING file
|
4
4
|
|
5
|
-
OBJ = net.o hiredis.o sds.o async.o
|
5
|
+
OBJ = net.o hiredis.o sds.o async.o dict.o
|
6
6
|
BINS = hiredis-example hiredis-test
|
7
7
|
|
8
8
|
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
9
9
|
OPTIMIZATION?=-O3
|
10
10
|
ifeq ($(uname_S),SunOS)
|
11
|
-
CFLAGS
|
12
|
-
CCLINK
|
11
|
+
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF)
|
12
|
+
CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
|
13
|
+
LDFLAGS?=-L. -Wl,-R,.
|
13
14
|
DYLIBNAME?=libhiredis.so
|
14
|
-
DYLIB_MAKE_CMD
|
15
|
+
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
|
15
16
|
STLIBNAME?=libhiredis.a
|
16
17
|
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
17
18
|
else ifeq ($(uname_S),Darwin)
|
18
|
-
CFLAGS
|
19
|
-
CCLINK
|
20
|
-
|
19
|
+
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
20
|
+
CCLINK?=-lm -pthread
|
21
|
+
LDFLAGS?=-L. -Wl,-rpath,.
|
22
|
+
OBJARCH?=-arch i386 -arch x86_64
|
21
23
|
DYLIBNAME?=libhiredis.dylib
|
22
24
|
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
|
23
25
|
STLIBNAME?=libhiredis.a
|
24
26
|
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
|
25
27
|
else
|
26
|
-
CFLAGS
|
27
|
-
CCLINK
|
28
|
+
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
29
|
+
CCLINK?=-lm -pthread
|
30
|
+
LDFLAGS?=-L. -Wl,-rpath,.
|
28
31
|
DYLIBNAME?=libhiredis.so
|
29
32
|
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
30
33
|
STLIBNAME?=libhiredis.a
|
31
34
|
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
32
35
|
endif
|
33
|
-
CCOPT= $(CFLAGS) $(CCLINK)
|
36
|
+
CCOPT= $(CFLAGS) $(CCLINK)
|
34
37
|
DEBUG?= -g -ggdb
|
35
38
|
|
36
39
|
PREFIX?= /usr/local
|
@@ -46,6 +49,7 @@ async.o: async.c async.h hiredis.h sds.h util.h
|
|
46
49
|
example.o: example.c hiredis.h
|
47
50
|
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
|
48
51
|
sds.o: sds.c sds.h
|
52
|
+
dict.o: dict.c dict.h
|
49
53
|
test.o: test.c hiredis.h
|
50
54
|
|
51
55
|
${DYLIBNAME}: ${OBJ}
|
@@ -59,10 +63,10 @@ static: ${STLIBNAME}
|
|
59
63
|
|
60
64
|
# Binaries:
|
61
65
|
hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
|
62
|
-
$(CC) -o $@ $(CCOPT) $(DEBUG)
|
66
|
+
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c
|
63
67
|
|
64
68
|
hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
|
65
|
-
$(CC) -o $@ $(CCOPT) $(DEBUG)
|
69
|
+
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c
|
66
70
|
|
67
71
|
ifndef AE_DIR
|
68
72
|
hiredis-example-ae:
|
@@ -70,11 +74,11 @@ hiredis-example-ae:
|
|
70
74
|
@false
|
71
75
|
else
|
72
76
|
hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
|
73
|
-
$(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR)
|
77
|
+
$(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
|
74
78
|
endif
|
75
79
|
|
76
80
|
hiredis-%: %.o ${DYLIBNAME}
|
77
|
-
$(CC) -o $@ $(CCOPT) $(DEBUG)
|
81
|
+
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $<
|
78
82
|
|
79
83
|
test: hiredis-test
|
80
84
|
./hiredis-test
|
@@ -97,13 +101,13 @@ install: ${DYLIBNAME} ${STLIBNAME}
|
|
97
101
|
@echo ""
|
98
102
|
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
99
103
|
@echo ""
|
100
|
-
|
104
|
+
$(MAKE) ARCH="-m32"
|
101
105
|
|
102
106
|
gprof:
|
103
|
-
|
107
|
+
$(MAKE) PROF="-pg"
|
104
108
|
|
105
109
|
gcov:
|
106
|
-
|
110
|
+
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
107
111
|
|
108
112
|
noopt:
|
109
|
-
|
113
|
+
$(MAKE) OPTIMIZATION=""
|
data/vendor/hiredis/async.c
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
/*
|
2
2
|
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
3
|
+
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
4
|
+
*
|
3
5
|
* All rights reserved.
|
4
6
|
*
|
5
7
|
* Redistribution and use in source and binary forms, with or without
|
@@ -29,6 +31,7 @@
|
|
29
31
|
|
30
32
|
#include <string.h>
|
31
33
|
#include <assert.h>
|
34
|
+
#include <ctype.h>
|
32
35
|
#include "async.h"
|
33
36
|
#include "sds.h"
|
34
37
|
#include "util.h"
|
@@ -36,20 +39,76 @@
|
|
36
39
|
/* Forward declaration of function in hiredis.c */
|
37
40
|
void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
|
38
41
|
|
42
|
+
/* Functions managing dictionary of callbacks for pub/sub. */
|
43
|
+
static unsigned int callbackHash(const void *key) {
|
44
|
+
return dictGenHashFunction((unsigned char*)key,sdslen((char*)key));
|
45
|
+
}
|
46
|
+
|
47
|
+
static void *callbackValDup(void *privdata, const void *src) {
|
48
|
+
((void) privdata);
|
49
|
+
redisCallback *dup = malloc(sizeof(*dup));
|
50
|
+
memcpy(dup,src,sizeof(*dup));
|
51
|
+
return dup;
|
52
|
+
}
|
53
|
+
|
54
|
+
static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
|
55
|
+
int l1, l2;
|
56
|
+
((void) privdata);
|
57
|
+
|
58
|
+
l1 = sdslen((sds)key1);
|
59
|
+
l2 = sdslen((sds)key2);
|
60
|
+
if (l1 != l2) return 0;
|
61
|
+
return memcmp(key1,key2,l1) == 0;
|
62
|
+
}
|
63
|
+
|
64
|
+
static void callbackKeyDestructor(void *privdata, void *key) {
|
65
|
+
((void) privdata);
|
66
|
+
sdsfree((sds)key);
|
67
|
+
}
|
68
|
+
|
69
|
+
static void callbackValDestructor(void *privdata, void *val) {
|
70
|
+
((void) privdata);
|
71
|
+
free(val);
|
72
|
+
}
|
73
|
+
|
74
|
+
static dictType callbackDict = {
|
75
|
+
callbackHash,
|
76
|
+
NULL,
|
77
|
+
callbackValDup,
|
78
|
+
callbackKeyCompare,
|
79
|
+
callbackKeyDestructor,
|
80
|
+
callbackValDestructor
|
81
|
+
};
|
82
|
+
|
39
83
|
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
40
84
|
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
|
85
|
+
c = &(ac->c);
|
86
|
+
|
87
|
+
/* The regular connect functions will always set the flag REDIS_CONNECTED.
|
88
|
+
* For the async API, we want to wait until the first write event is
|
89
|
+
* received up before setting this flag, so reset it here. */
|
90
|
+
c->flags &= ~REDIS_CONNECTED;
|
91
|
+
|
41
92
|
ac->err = 0;
|
42
93
|
ac->errstr = NULL;
|
43
94
|
ac->data = NULL;
|
44
|
-
|
45
|
-
ac->
|
46
|
-
ac->
|
47
|
-
ac->
|
48
|
-
ac->
|
49
|
-
ac->
|
95
|
+
|
96
|
+
ac->ev.data = NULL;
|
97
|
+
ac->ev.addRead = NULL;
|
98
|
+
ac->ev.delRead = NULL;
|
99
|
+
ac->ev.addWrite = NULL;
|
100
|
+
ac->ev.delWrite = NULL;
|
101
|
+
ac->ev.cleanup = NULL;
|
102
|
+
|
103
|
+
ac->onConnect = NULL;
|
50
104
|
ac->onDisconnect = NULL;
|
105
|
+
|
51
106
|
ac->replies.head = NULL;
|
52
107
|
ac->replies.tail = NULL;
|
108
|
+
ac->sub.invalid.head = NULL;
|
109
|
+
ac->sub.invalid.tail = NULL;
|
110
|
+
ac->sub.channels = dictCreate(&callbackDict,NULL);
|
111
|
+
ac->sub.patterns = dictCreate(&callbackDict,NULL);
|
53
112
|
return ac;
|
54
113
|
}
|
55
114
|
|
@@ -80,6 +139,19 @@ int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFun
|
|
80
139
|
return redisSetReplyObjectFunctions(c,fn);
|
81
140
|
}
|
82
141
|
|
142
|
+
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
143
|
+
if (ac->onConnect == NULL) {
|
144
|
+
ac->onConnect = fn;
|
145
|
+
|
146
|
+
/* The common way to detect an established connection is to wait for
|
147
|
+
* the first write event to be fired. This assumes the related event
|
148
|
+
* library functions are already set. */
|
149
|
+
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data);
|
150
|
+
return REDIS_OK;
|
151
|
+
}
|
152
|
+
return REDIS_ERR;
|
153
|
+
}
|
154
|
+
|
83
155
|
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
|
84
156
|
if (ac->onDisconnect == NULL) {
|
85
157
|
ac->onDisconnect = fn;
|
@@ -93,11 +165,11 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
|
|
93
165
|
redisCallback *cb;
|
94
166
|
|
95
167
|
/* Copy callback from stack to heap */
|
96
|
-
cb =
|
168
|
+
cb = malloc(sizeof(*cb));
|
97
169
|
if (!cb) redisOOM();
|
98
170
|
if (source != NULL) {
|
99
|
-
cb
|
100
|
-
cb->
|
171
|
+
memcpy(cb,source,sizeof(*cb));
|
172
|
+
cb->next = NULL;
|
101
173
|
}
|
102
174
|
|
103
175
|
/* Store callback in list */
|
@@ -125,51 +197,150 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
|
|
125
197
|
return REDIS_ERR;
|
126
198
|
}
|
127
199
|
|
128
|
-
|
129
|
-
* from being issued, but tries to flush the output buffer and execute
|
130
|
-
* callbacks for all remaining replies.
|
131
|
-
*
|
132
|
-
* This functions is generally called from within a callback, so the
|
133
|
-
* processCallbacks function will pick up the flag when there are no
|
134
|
-
* more replies. */
|
135
|
-
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
200
|
+
static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
|
136
201
|
redisContext *c = &(ac->c);
|
137
|
-
|
202
|
+
if (cb->fn != NULL) {
|
203
|
+
c->flags |= REDIS_IN_CALLBACK;
|
204
|
+
cb->fn(ac,reply,cb->privdata);
|
205
|
+
c->flags &= ~REDIS_IN_CALLBACK;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
/* Helper function to free the context. */
|
210
|
+
static void __redisAsyncFree(redisAsyncContext *ac) {
|
211
|
+
redisContext *c = &(ac->c);
|
212
|
+
redisCallback cb;
|
213
|
+
dictIterator *it;
|
214
|
+
dictEntry *de;
|
215
|
+
|
216
|
+
/* Execute pending callbacks with NULL reply. */
|
217
|
+
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
|
218
|
+
__redisRunCallback(ac,&cb,NULL);
|
219
|
+
|
220
|
+
/* Execute callbacks for invalid commands */
|
221
|
+
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
|
222
|
+
__redisRunCallback(ac,&cb,NULL);
|
223
|
+
|
224
|
+
/* Run subscription callbacks callbacks with NULL reply */
|
225
|
+
it = dictGetIterator(ac->sub.channels);
|
226
|
+
while ((de = dictNext(it)) != NULL)
|
227
|
+
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
228
|
+
dictReleaseIterator(it);
|
229
|
+
dictRelease(ac->sub.channels);
|
230
|
+
|
231
|
+
it = dictGetIterator(ac->sub.patterns);
|
232
|
+
while ((de = dictNext(it)) != NULL)
|
233
|
+
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
234
|
+
dictReleaseIterator(it);
|
235
|
+
dictRelease(ac->sub.patterns);
|
236
|
+
|
237
|
+
/* Signal event lib to clean up */
|
238
|
+
if (ac->ev.cleanup) ac->ev.cleanup(ac->ev.data);
|
239
|
+
|
240
|
+
/* Execute disconnect callback. When redisAsyncFree() initiated destroying
|
241
|
+
* this context, the status will always be REDIS_OK. */
|
242
|
+
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
|
243
|
+
if (c->flags & REDIS_FREEING) {
|
244
|
+
ac->onDisconnect(ac,REDIS_OK);
|
245
|
+
} else {
|
246
|
+
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
/* Cleanup self */
|
251
|
+
redisFree(c);
|
252
|
+
}
|
253
|
+
|
254
|
+
/* Free the async context. When this function is called from a callback,
|
255
|
+
* control needs to be returned to redisProcessCallbacks() before actual
|
256
|
+
* free'ing. To do so, a flag is set on the context which is picked up by
|
257
|
+
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
|
258
|
+
void redisAsyncFree(redisAsyncContext *ac) {
|
259
|
+
redisContext *c = &(ac->c);
|
260
|
+
c->flags |= REDIS_FREEING;
|
261
|
+
if (!(c->flags & REDIS_IN_CALLBACK))
|
262
|
+
__redisAsyncFree(ac);
|
138
263
|
}
|
139
264
|
|
140
265
|
/* Helper function to make the disconnect happen and clean up. */
|
141
266
|
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
142
267
|
redisContext *c = &(ac->c);
|
143
|
-
redisCallback cb;
|
144
|
-
int status;
|
145
268
|
|
146
269
|
/* Make sure error is accessible if there is any */
|
147
270
|
__redisAsyncCopyError(ac);
|
148
|
-
status = (ac->err == 0) ? REDIS_OK : REDIS_ERR;
|
149
271
|
|
150
|
-
if (
|
151
|
-
/*
|
152
|
-
* be pending callbacks. */
|
272
|
+
if (ac->err == 0) {
|
273
|
+
/* For clean disconnects, there should be no pending callbacks. */
|
153
274
|
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
154
275
|
} else {
|
155
|
-
/*
|
276
|
+
/* Disconnection is caused by an error, make sure that pending
|
277
|
+
* callbacks cannot call new commands. */
|
156
278
|
c->flags |= REDIS_DISCONNECTING;
|
157
|
-
|
158
|
-
/* Execute pending callbacks with NULL reply. */
|
159
|
-
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) {
|
160
|
-
if (cb.fn != NULL)
|
161
|
-
cb.fn(ac,NULL,cb.privdata);
|
162
|
-
}
|
163
279
|
}
|
164
280
|
|
165
|
-
/*
|
166
|
-
|
281
|
+
/* For non-clean disconnects, __redisAsyncFree() will execute pending
|
282
|
+
* callbacks with a NULL-reply. */
|
283
|
+
__redisAsyncFree(ac);
|
284
|
+
}
|
167
285
|
|
168
|
-
|
169
|
-
|
286
|
+
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
287
|
+
* from being issued, but tries to flush the output buffer and execute
|
288
|
+
* callbacks for all remaining replies. When this function is called from a
|
289
|
+
* callback, there might be more replies and we can safely defer disconnecting
|
290
|
+
* to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
|
291
|
+
* when there are no pending callbacks. */
|
292
|
+
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
293
|
+
redisContext *c = &(ac->c);
|
294
|
+
c->flags |= REDIS_DISCONNECTING;
|
295
|
+
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
|
296
|
+
__redisAsyncDisconnect(ac);
|
297
|
+
}
|
170
298
|
|
171
|
-
|
172
|
-
|
299
|
+
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
|
300
|
+
redisContext *c = &(ac->c);
|
301
|
+
dict *callbacks;
|
302
|
+
dictEntry *de;
|
303
|
+
int pvariant;
|
304
|
+
char *stype;
|
305
|
+
sds sname;
|
306
|
+
|
307
|
+
/* Custom reply functions are not supported for pub/sub. This will fail
|
308
|
+
* very hard when they are used... */
|
309
|
+
if (reply->type == REDIS_REPLY_ARRAY) {
|
310
|
+
assert(reply->elements >= 2);
|
311
|
+
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
312
|
+
stype = reply->element[0]->str;
|
313
|
+
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
|
314
|
+
|
315
|
+
if (pvariant)
|
316
|
+
callbacks = ac->sub.patterns;
|
317
|
+
else
|
318
|
+
callbacks = ac->sub.channels;
|
319
|
+
|
320
|
+
/* Locate the right callback */
|
321
|
+
assert(reply->element[1]->type == REDIS_REPLY_STRING);
|
322
|
+
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
323
|
+
de = dictFind(callbacks,sname);
|
324
|
+
if (de != NULL) {
|
325
|
+
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
|
326
|
+
|
327
|
+
/* If this is an unsubscribe message, remove it. */
|
328
|
+
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
|
329
|
+
dictDelete(callbacks,sname);
|
330
|
+
|
331
|
+
/* If this was the last unsubscribe message, revert to
|
332
|
+
* non-subscribe mode. */
|
333
|
+
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
334
|
+
if (reply->element[2]->integer == 0)
|
335
|
+
c->flags &= ~REDIS_SUBSCRIBED;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
sdsfree(sname);
|
339
|
+
} else {
|
340
|
+
/* Shift callback for invalid commands. */
|
341
|
+
__redisShiftCallback(&ac->sub.invalid,dstcb);
|
342
|
+
}
|
343
|
+
return REDIS_OK;
|
173
344
|
}
|
174
345
|
|
175
346
|
void redisProcessCallbacks(redisAsyncContext *ac) {
|
@@ -192,11 +363,28 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
|
192
363
|
break;
|
193
364
|
}
|
194
365
|
|
195
|
-
/*
|
196
|
-
|
366
|
+
/* Even if the context is subscribed, pending regular callbacks will
|
367
|
+
* get a reply before pub/sub messages arrive. */
|
368
|
+
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
|
369
|
+
/* No more regular callbacks, the context *must* be subscribed. */
|
370
|
+
assert(c->flags & REDIS_SUBSCRIBED);
|
371
|
+
__redisGetSubscribeCallback(ac,reply,&cb);
|
372
|
+
}
|
373
|
+
|
197
374
|
if (cb.fn != NULL) {
|
198
|
-
|
375
|
+
__redisRunCallback(ac,&cb,reply);
|
376
|
+
c->fn->freeObject(reply);
|
377
|
+
|
378
|
+
/* Proceed with free'ing when redisAsyncFree() was called. */
|
379
|
+
if (c->flags & REDIS_FREEING) {
|
380
|
+
__redisAsyncFree(ac);
|
381
|
+
return;
|
382
|
+
}
|
199
383
|
} else {
|
384
|
+
/* No callback for this reply. This can either be a NULL callback,
|
385
|
+
* or there were no callbacks to begin with. Either way, don't
|
386
|
+
* abort with an error, but simply ignore it because the client
|
387
|
+
* doesn't know what the server will spit out over the wire. */
|
200
388
|
c->fn->freeObject(reply);
|
201
389
|
}
|
202
390
|
}
|
@@ -216,7 +404,7 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
|
216
404
|
__redisAsyncDisconnect(ac);
|
217
405
|
} else {
|
218
406
|
/* Always re-schedule reads */
|
219
|
-
if (ac->
|
407
|
+
if (ac->ev.addRead) ac->ev.addRead(ac->ev.data);
|
220
408
|
redisProcessCallbacks(ac);
|
221
409
|
}
|
222
410
|
}
|
@@ -230,36 +418,97 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
|
230
418
|
} else {
|
231
419
|
/* Continue writing when not done, stop writing otherwise */
|
232
420
|
if (!done) {
|
233
|
-
if (ac->
|
421
|
+
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data);
|
234
422
|
} else {
|
235
|
-
if (ac->
|
423
|
+
if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data);
|
236
424
|
}
|
237
425
|
|
238
|
-
/* Always schedule reads
|
239
|
-
if (ac->
|
426
|
+
/* Always schedule reads after writes */
|
427
|
+
if (ac->ev.addRead) ac->ev.addRead(ac->ev.data);
|
428
|
+
|
429
|
+
/* Fire onConnect when this is the first write event. */
|
430
|
+
if (!(c->flags & REDIS_CONNECTED)) {
|
431
|
+
c->flags |= REDIS_CONNECTED;
|
432
|
+
if (ac->onConnect) ac->onConnect(ac);
|
433
|
+
}
|
240
434
|
}
|
241
435
|
}
|
242
436
|
|
243
|
-
/*
|
244
|
-
*
|
245
|
-
*
|
246
|
-
*
|
247
|
-
|
437
|
+
/* Sets a pointer to the first argument and its length starting at p. Returns
|
438
|
+
* the number of bytes to skip to get to the following argument. */
|
439
|
+
static char *nextArgument(char *start, char **str, size_t *len) {
|
440
|
+
char *p = start;
|
441
|
+
if (p[0] != '$') {
|
442
|
+
p = strchr(p,'$');
|
443
|
+
if (p == NULL) return NULL;
|
444
|
+
}
|
445
|
+
|
446
|
+
*len = (int)strtol(p+1,NULL,10);
|
447
|
+
p = strchr(p,'\r');
|
448
|
+
assert(p);
|
449
|
+
*str = p+2;
|
450
|
+
return p+2+(*len)+2;
|
451
|
+
}
|
452
|
+
|
453
|
+
/* Helper function for the redisAsyncCommand* family of functions. Writes a
|
454
|
+
* formatted command to the output buffer and registers the provided callback
|
455
|
+
* function with the context. */
|
248
456
|
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
|
249
457
|
redisContext *c = &(ac->c);
|
250
458
|
redisCallback cb;
|
459
|
+
int pvariant, hasnext;
|
460
|
+
char *cstr, *astr;
|
461
|
+
size_t clen, alen;
|
462
|
+
char *p;
|
463
|
+
sds sname;
|
251
464
|
|
252
|
-
/* Don't accept new commands when the connection is
|
253
|
-
if (c->flags & REDIS_DISCONNECTING) return REDIS_ERR;
|
254
|
-
__redisAppendCommand(c,cmd,len);
|
465
|
+
/* Don't accept new commands when the connection is about to be closed. */
|
466
|
+
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
|
255
467
|
|
256
|
-
/*
|
468
|
+
/* Setup callback */
|
257
469
|
cb.fn = fn;
|
258
470
|
cb.privdata = privdata;
|
259
|
-
|
471
|
+
|
472
|
+
/* Find out which command will be appended. */
|
473
|
+
p = nextArgument(cmd,&cstr,&clen);
|
474
|
+
assert(p != NULL);
|
475
|
+
hasnext = (p[0] == '$');
|
476
|
+
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
|
477
|
+
cstr += pvariant;
|
478
|
+
clen -= pvariant;
|
479
|
+
|
480
|
+
if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
|
481
|
+
c->flags |= REDIS_SUBSCRIBED;
|
482
|
+
|
483
|
+
/* Add every channel/pattern to the list of subscription callbacks. */
|
484
|
+
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
485
|
+
sname = sdsnewlen(astr,alen);
|
486
|
+
if (pvariant)
|
487
|
+
dictReplace(ac->sub.patterns,sname,&cb);
|
488
|
+
else
|
489
|
+
dictReplace(ac->sub.channels,sname,&cb);
|
490
|
+
}
|
491
|
+
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
|
492
|
+
/* It is only useful to call (P)UNSUBSCRIBE when the context is
|
493
|
+
* subscribed to one or more channels or patterns. */
|
494
|
+
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
|
495
|
+
|
496
|
+
/* (P)UNSUBSCRIBE does not have its own response: every channel or
|
497
|
+
* pattern that is unsubscribed will receive a message. This means we
|
498
|
+
* should not append a callback function for this command. */
|
499
|
+
} else {
|
500
|
+
if (c->flags & REDIS_SUBSCRIBED)
|
501
|
+
/* This will likely result in an error reply, but it needs to be
|
502
|
+
* received and passed to the callback. */
|
503
|
+
__redisPushCallback(&ac->sub.invalid,&cb);
|
504
|
+
else
|
505
|
+
__redisPushCallback(&ac->replies,&cb);
|
506
|
+
}
|
507
|
+
|
508
|
+
__redisAppendCommand(c,cmd,len);
|
260
509
|
|
261
510
|
/* Always schedule a write when the write buffer is non-empty */
|
262
|
-
if (ac->
|
511
|
+
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data);
|
263
512
|
|
264
513
|
return REDIS_OK;
|
265
514
|
}
|