hiredis 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 (setsockopt(pc->context->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1)
196
- rb_sys_fail(0);
197
- if (setsockopt(pc->context->fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1)
198
- rb_sys_fail(0);
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
 
@@ -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
 
@@ -5,7 +5,11 @@ module Hiredis
5
5
  # Raise CONNRESET on EOF
6
6
  alias :_read :read
7
7
  def read
8
- _read
8
+ reply = _read
9
+ error = reply.instance_variable_get(:@__hiredis_error)
10
+ raise error if error
11
+
12
+ reply
9
13
  rescue EOFError
10
14
  raise Errno::ECONNRESET
11
15
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Hiredis
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
12
- CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
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?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
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?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
19
- CCLINK?= -lm -pthread
20
- OBJARCH?= -arch i386 -arch x86_64
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?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
27
- CCLINK?= -lm -pthread
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) $(ARCH) $(PROF)
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) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
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) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
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) -L. -lhiredis -Wl,-rpath,. example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
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) -L. -lhiredis -Wl,-rpath,. $<
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
- make ARCH="-m32"
104
+ $(MAKE) ARCH="-m32"
101
105
 
102
106
  gprof:
103
- make PROF="-pg"
107
+ $(MAKE) PROF="-pg"
104
108
 
105
109
  gcov:
106
- make PROF="-fprofile-arcs -ftest-coverage"
110
+ $(MAKE) PROF="-fprofile-arcs -ftest-coverage"
107
111
 
108
112
  noopt:
109
- make OPTIMIZATION=""
113
+ $(MAKE) OPTIMIZATION=""
@@ -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
- ac->_adapter_data = NULL;
45
- ac->evAddRead = NULL;
46
- ac->evDelRead = NULL;
47
- ac->evAddWrite = NULL;
48
- ac->evDelWrite = NULL;
49
- ac->evCleanup = NULL;
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 = calloc(1,sizeof(*cb));
168
+ cb = malloc(sizeof(*cb));
97
169
  if (!cb) redisOOM();
98
170
  if (source != NULL) {
99
- cb->fn = source->fn;
100
- cb->privdata = source->privdata;
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
- /* Tries to do a clean disconnect from Redis, meaning it stops new commands
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
- c->flags |= REDIS_DISCONNECTING;
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 (status == REDIS_OK) {
151
- /* When the connection is cleanly disconnected, there should not
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
- /* Callbacks should not be able to issue new commands. */
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
- /* Signal event lib to clean up */
166
- if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
281
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
282
+ * callbacks with a NULL-reply. */
283
+ __redisAsyncFree(ac);
284
+ }
167
285
 
168
- /* Execute callback with proper status */
169
- if (ac->onDisconnect) ac->onDisconnect(ac,status);
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
- /* Cleanup self */
172
- redisFree(c);
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
- /* Shift callback and execute it */
196
- assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK);
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
- cb.fn(ac,reply,cb.privdata);
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->evAddRead) ac->evAddRead(ac->_adapter_data);
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->evAddWrite) ac->evAddWrite(ac->_adapter_data);
421
+ if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data);
234
422
  } else {
235
- if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
423
+ if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data);
236
424
  }
237
425
 
238
- /* Always schedule reads when something was written */
239
- if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
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
- /* Helper function for the redisAsyncCommand* family of functions.
244
- *
245
- * Write a formatted command to the output buffer and register the provided
246
- * callback function with the context.
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 lazily closed. */
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
- /* Store callback */
468
+ /* Setup callback */
257
469
  cb.fn = fn;
258
470
  cb.privdata = privdata;
259
- __redisPushCallback(&ac->replies,&cb);
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->evAddWrite) ac->evAddWrite(ac->_adapter_data);
511
+ if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data);
263
512
 
264
513
  return REDIS_OK;
265
514
  }