hiredis 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3677528ff01fba750831fbc49dd7a6dac178ad46ecd19ab9dbc63d15f924fac4
4
- data.tar.gz: 94d08177ef96016ff252864e6baba1730c2885e44466da01275bf35e1c13425e
3
+ metadata.gz: 1f07127ae47561dfa27e69e75ec20141aea1ec0f25c9e0a6ebf85d28c0fd0ab5
4
+ data.tar.gz: 71b64366a9ec6eb8eaef1ca8c6dee1aaab91c1608198d2fbc0dd928d31e36cca
5
5
  SHA512:
6
- metadata.gz: 9e0e6550901bc8c02c8b93e38d61e2d38ac8a094bdb01d2b4e64db82dd6d3c17eac4b9ea72cfd4743217435dcf2637f15e42d7ac666d6241c8c603faf1917e71
7
- data.tar.gz: d6f45386a24e3e383260cf5539212c6c7412733a891740b45422062b3eb33f1655066956d5a90f47415ec6f90da71b4832c81a9a4f3e957d0343716811c4d372
6
+ metadata.gz: e3e5d4387c7e4d625fc81349d1224ac34cc43aae5a6d49c648515462eab5404bd18bb0deae1a166ab42e0ab93f91ef7344dd1a8a0c4ea54fd040bfe97a4a983f
7
+ data.tar.gz: 59504af0292497549b26ae7f970e2495363d0a66b9e15cb8c98b1fdcc748805a11a2c7e800fc9cce41dc81a85dc08273d62558d5481f89b2625f8611bbb50019
@@ -1,3 +1,3 @@
1
1
  module Hiredis
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.3"
3
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,213 @@
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 read.o
7
+ EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
8
+ TESTS=hiredis-test
9
+ LIBNAME=libhiredis
10
+ PKGCONFNAME=hiredis.pc
11
+
12
+ HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
13
+ HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
14
+ HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
15
+ HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
16
+
17
+ # Installation related variables and target
18
+ PREFIX?=/usr/local
19
+ INCLUDE_PATH?=include/hiredis
20
+ LIBRARY_PATH?=lib
21
+ PKGCONF_PATH?=pkgconfig
22
+ INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
23
+ INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
24
+ INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
25
+
26
+ # redis-server configuration used for testing
27
+ REDIS_PORT=56379
28
+ REDIS_SERVER=redis-server
29
+ define REDIS_TEST_CONFIG
30
+ daemonize yes
31
+ pidfile /tmp/hiredis-test-redis.pid
32
+ port $(REDIS_PORT)
33
+ bind 127.0.0.1
34
+ unixsocket /tmp/hiredis-test-redis.sock
35
+ endef
36
+ export REDIS_TEST_CONFIG
37
+
38
+ # Fallback to gcc when $CC is not in $PATH.
39
+ CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
40
+ CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
41
+ OPTIMIZATION?=-O3
42
+ WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
43
+ DEBUG_FLAGS?= -g -ggdb
44
+ REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
45
+ REAL_LDFLAGS=$(LDFLAGS)
46
+
47
+ DYLIBSUFFIX=so
48
+ STLIBSUFFIX=a
49
+ DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
50
+ DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
51
+ DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
52
+ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
53
+ STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
54
+ STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
55
+
56
+ # Platform-specific overrides
57
+ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
58
+ ifeq ($(uname_S),SunOS)
59
+ REAL_LDFLAGS+= -ldl -lnsl -lsocket
60
+ DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
61
+ endif
62
+ ifeq ($(uname_S),Darwin)
63
+ DYLIBSUFFIX=dylib
64
+ DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
65
+ DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
66
+ endif
67
+
68
+ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
69
+
70
+ # Deps (use make dep to generate this)
71
+ async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
72
+ dict.o: dict.c fmacros.h dict.h
73
+ hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
74
+ net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
75
+ read.o: read.c fmacros.h read.h sds.h
76
+ sds.o: sds.c sds.h
77
+ test.o: test.c fmacros.h hiredis.h read.h sds.h
78
+
79
+ $(DYLIBNAME): $(OBJ)
80
+ $(DYLIB_MAKE_CMD) $(OBJ)
81
+
82
+ $(STLIBNAME): $(OBJ)
83
+ $(STLIB_MAKE_CMD) $(OBJ)
84
+
85
+ dynamic: $(DYLIBNAME)
86
+ static: $(STLIBNAME)
87
+
88
+ # Binaries:
89
+ hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
90
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
91
+
92
+ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
93
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
94
+
95
+ hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
96
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
97
+
98
+ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
99
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
100
+
101
+ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
102
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
103
+
104
+ ifndef AE_DIR
105
+ hiredis-example-ae:
106
+ @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
107
+ @false
108
+ else
109
+ hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)
110
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME)
111
+ endif
112
+
113
+ ifndef LIBUV_DIR
114
+ hiredis-example-libuv:
115
+ @echo "Please specify LIBUV_DIR (e.g. ../libuv/)"
116
+ @false
117
+ else
118
+ hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
119
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
120
+ endif
121
+
122
+ ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
123
+ hiredis-example-qt:
124
+ @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
125
+ @false
126
+ else
127
+ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
128
+ $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
129
+ $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
130
+ $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
131
+ $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
132
+ $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
133
+ endif
134
+
135
+ hiredis-example: examples/example.c $(STLIBNAME)
136
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
137
+
138
+ examples: $(EXAMPLES)
139
+
140
+ hiredis-test: test.o $(STLIBNAME)
141
+
142
+ hiredis-%: %.o $(STLIBNAME)
143
+ $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
144
+
145
+ test: hiredis-test
146
+ ./hiredis-test
147
+
148
+ check: hiredis-test
149
+ @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
150
+ $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
151
+ ( kill `cat /tmp/hiredis-test-redis.pid` && false )
152
+ kill `cat /tmp/hiredis-test-redis.pid`
153
+
154
+ .c.o:
155
+ $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
156
+
157
+ clean:
158
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
159
+
160
+ dep:
161
+ $(CC) -MM *.c
162
+
163
+ INSTALL?= cp -pPR
164
+
165
+ $(PKGCONFNAME): hiredis.h
166
+ @echo "Generating $@ for pkgconfig..."
167
+ @echo prefix=$(PREFIX) > $@
168
+ @echo exec_prefix=\$${prefix} >> $@
169
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
170
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
171
+ @echo >> $@
172
+ @echo Name: hiredis >> $@
173
+ @echo Description: Minimalistic C client library for Redis. >> $@
174
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
175
+ @echo Libs: -L\$${libdir} -lhiredis >> $@
176
+ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
177
+
178
+ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
179
+ mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
180
+ $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
181
+ $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
182
+ $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
183
+ cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
184
+ $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
185
+ mkdir -p $(INSTALL_PKGCONF_PATH)
186
+ $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
187
+
188
+ 32bit:
189
+ @echo ""
190
+ @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
191
+ @echo ""
192
+ $(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
193
+
194
+ 32bit-vars:
195
+ $(eval CFLAGS=-m32)
196
+ $(eval LDFLAGS=-m32)
197
+
198
+ gprof:
199
+ $(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
200
+
201
+ gcov:
202
+ $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
203
+
204
+ coverage: gcov
205
+ make check
206
+ mkdir -p tmp/lcov
207
+ lcov -d . -c -o tmp/lcov/hiredis.info
208
+ genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
209
+
210
+ noopt:
211
+ $(MAKE) OPTIMIZATION=""
212
+
213
+ .PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt
@@ -0,0 +1,716 @@
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
+ int __redisAppendCommand(redisContext *c, const 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((const unsigned char *)key,
66
+ sdslen((const sds)key));
67
+ }
68
+
69
+ static void *callbackValDup(void *privdata, const void *src) {
70
+ ((void) privdata);
71
+ redisCallback *dup = malloc(sizeof(*dup));
72
+ memcpy(dup,src,sizeof(*dup));
73
+ return dup;
74
+ }
75
+
76
+ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
77
+ int l1, l2;
78
+ ((void) privdata);
79
+
80
+ l1 = sdslen((const sds)key1);
81
+ l2 = sdslen((const sds)key2);
82
+ if (l1 != l2) return 0;
83
+ return memcmp(key1,key2,l1) == 0;
84
+ }
85
+
86
+ static void callbackKeyDestructor(void *privdata, void *key) {
87
+ ((void) privdata);
88
+ sdsfree((sds)key);
89
+ }
90
+
91
+ static void callbackValDestructor(void *privdata, void *val) {
92
+ ((void) privdata);
93
+ free(val);
94
+ }
95
+
96
+ static dictType callbackDict = {
97
+ callbackHash,
98
+ NULL,
99
+ callbackValDup,
100
+ callbackKeyCompare,
101
+ callbackKeyDestructor,
102
+ callbackValDestructor
103
+ };
104
+
105
+ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
106
+ redisAsyncContext *ac;
107
+
108
+ ac = realloc(c,sizeof(redisAsyncContext));
109
+ if (ac == NULL)
110
+ return NULL;
111
+
112
+ c = &(ac->c);
113
+
114
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
115
+ * For the async API, we want to wait until the first write event is
116
+ * received up before setting this flag, so reset it here. */
117
+ c->flags &= ~REDIS_CONNECTED;
118
+
119
+ ac->err = 0;
120
+ ac->errstr = NULL;
121
+ ac->data = NULL;
122
+
123
+ ac->ev.data = NULL;
124
+ ac->ev.addRead = NULL;
125
+ ac->ev.delRead = NULL;
126
+ ac->ev.addWrite = NULL;
127
+ ac->ev.delWrite = NULL;
128
+ ac->ev.cleanup = NULL;
129
+
130
+ ac->onConnect = NULL;
131
+ ac->onDisconnect = NULL;
132
+
133
+ ac->replies.head = NULL;
134
+ ac->replies.tail = NULL;
135
+ ac->sub.invalid.head = NULL;
136
+ ac->sub.invalid.tail = NULL;
137
+ ac->sub.channels = dictCreate(&callbackDict,NULL);
138
+ ac->sub.patterns = dictCreate(&callbackDict,NULL);
139
+ return ac;
140
+ }
141
+
142
+ /* We want the error field to be accessible directly instead of requiring
143
+ * an indirection to the redisContext struct. */
144
+ static void __redisAsyncCopyError(redisAsyncContext *ac) {
145
+ if (!ac)
146
+ return;
147
+
148
+ redisContext *c = &(ac->c);
149
+ ac->err = c->err;
150
+ ac->errstr = c->errstr;
151
+ }
152
+
153
+ redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
154
+ redisContext *c;
155
+ redisAsyncContext *ac;
156
+
157
+ c = redisConnectNonBlock(ip,port);
158
+ if (c == NULL)
159
+ return NULL;
160
+
161
+ ac = redisAsyncInitialize(c);
162
+ if (ac == NULL) {
163
+ redisFree(c);
164
+ return NULL;
165
+ }
166
+
167
+ __redisAsyncCopyError(ac);
168
+ return ac;
169
+ }
170
+
171
+ redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
172
+ const char *source_addr) {
173
+ redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
174
+ redisAsyncContext *ac = redisAsyncInitialize(c);
175
+ __redisAsyncCopyError(ac);
176
+ return ac;
177
+ }
178
+
179
+ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
180
+ const char *source_addr) {
181
+ redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
182
+ redisAsyncContext *ac = redisAsyncInitialize(c);
183
+ __redisAsyncCopyError(ac);
184
+ return ac;
185
+ }
186
+
187
+ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
188
+ redisContext *c;
189
+ redisAsyncContext *ac;
190
+
191
+ c = redisConnectUnixNonBlock(path);
192
+ if (c == NULL)
193
+ return NULL;
194
+
195
+ ac = redisAsyncInitialize(c);
196
+ if (ac == NULL) {
197
+ redisFree(c);
198
+ return NULL;
199
+ }
200
+
201
+ __redisAsyncCopyError(ac);
202
+ return ac;
203
+ }
204
+
205
+ int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
206
+ if (ac->onConnect == NULL) {
207
+ ac->onConnect = fn;
208
+
209
+ /* The common way to detect an established connection is to wait for
210
+ * the first write event to be fired. This assumes the related event
211
+ * library functions are already set. */
212
+ _EL_ADD_WRITE(ac);
213
+ return REDIS_OK;
214
+ }
215
+ return REDIS_ERR;
216
+ }
217
+
218
+ int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
219
+ if (ac->onDisconnect == NULL) {
220
+ ac->onDisconnect = fn;
221
+ return REDIS_OK;
222
+ }
223
+ return REDIS_ERR;
224
+ }
225
+
226
+ /* Helper functions to push/shift callbacks */
227
+ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
228
+ redisCallback *cb;
229
+
230
+ /* Copy callback from stack to heap */
231
+ cb = malloc(sizeof(*cb));
232
+ if (cb == NULL)
233
+ return REDIS_ERR_OOM;
234
+
235
+ if (source != NULL) {
236
+ memcpy(cb,source,sizeof(*cb));
237
+ cb->next = NULL;
238
+ }
239
+
240
+ /* Store callback in list */
241
+ if (list->head == NULL)
242
+ list->head = cb;
243
+ if (list->tail != NULL)
244
+ list->tail->next = cb;
245
+ list->tail = cb;
246
+ return REDIS_OK;
247
+ }
248
+
249
+ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
250
+ redisCallback *cb = list->head;
251
+ if (cb != NULL) {
252
+ list->head = cb->next;
253
+ if (cb == list->tail)
254
+ list->tail = NULL;
255
+
256
+ /* Copy callback from heap to stack */
257
+ if (target != NULL)
258
+ memcpy(target,cb,sizeof(*cb));
259
+ free(cb);
260
+ return REDIS_OK;
261
+ }
262
+ return REDIS_ERR;
263
+ }
264
+
265
+ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
266
+ redisContext *c = &(ac->c);
267
+ if (cb->fn != NULL) {
268
+ c->flags |= REDIS_IN_CALLBACK;
269
+ cb->fn(ac,reply,cb->privdata);
270
+ c->flags &= ~REDIS_IN_CALLBACK;
271
+ }
272
+ }
273
+
274
+ /* Helper function to free the context. */
275
+ static void __redisAsyncFree(redisAsyncContext *ac) {
276
+ redisContext *c = &(ac->c);
277
+ redisCallback cb;
278
+ dictIterator *it;
279
+ dictEntry *de;
280
+
281
+ /* Execute pending callbacks with NULL reply. */
282
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
283
+ __redisRunCallback(ac,&cb,NULL);
284
+
285
+ /* Execute callbacks for invalid commands */
286
+ while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
287
+ __redisRunCallback(ac,&cb,NULL);
288
+
289
+ /* Run subscription callbacks callbacks with NULL reply */
290
+ it = dictGetIterator(ac->sub.channels);
291
+ while ((de = dictNext(it)) != NULL)
292
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
293
+ dictReleaseIterator(it);
294
+ dictRelease(ac->sub.channels);
295
+
296
+ it = dictGetIterator(ac->sub.patterns);
297
+ while ((de = dictNext(it)) != NULL)
298
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
299
+ dictReleaseIterator(it);
300
+ dictRelease(ac->sub.patterns);
301
+
302
+ /* Signal event lib to clean up */
303
+ _EL_CLEANUP(ac);
304
+
305
+ /* Execute disconnect callback. When redisAsyncFree() initiated destroying
306
+ * this context, the status will always be REDIS_OK. */
307
+ if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
308
+ if (c->flags & REDIS_FREEING) {
309
+ ac->onDisconnect(ac,REDIS_OK);
310
+ } else {
311
+ ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
312
+ }
313
+ }
314
+
315
+ /* Cleanup self */
316
+ redisFree(c);
317
+ }
318
+
319
+ /* Free the async context. When this function is called from a callback,
320
+ * control needs to be returned to redisProcessCallbacks() before actual
321
+ * free'ing. To do so, a flag is set on the context which is picked up by
322
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
323
+ void redisAsyncFree(redisAsyncContext *ac) {
324
+ redisContext *c = &(ac->c);
325
+ c->flags |= REDIS_FREEING;
326
+ if (!(c->flags & REDIS_IN_CALLBACK))
327
+ __redisAsyncFree(ac);
328
+ }
329
+
330
+ /* Helper function to make the disconnect happen and clean up. */
331
+ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
332
+ redisContext *c = &(ac->c);
333
+
334
+ /* Make sure error is accessible if there is any */
335
+ __redisAsyncCopyError(ac);
336
+
337
+ if (ac->err == 0) {
338
+ /* For clean disconnects, there should be no pending callbacks. */
339
+ int ret = __redisShiftCallback(&ac->replies,NULL);
340
+ assert(ret == REDIS_ERR);
341
+ } else {
342
+ /* Disconnection is caused by an error, make sure that pending
343
+ * callbacks cannot call new commands. */
344
+ c->flags |= REDIS_DISCONNECTING;
345
+ }
346
+
347
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
348
+ * callbacks with a NULL-reply. */
349
+ __redisAsyncFree(ac);
350
+ }
351
+
352
+ /* Tries to do a clean disconnect from Redis, meaning it stops new commands
353
+ * from being issued, but tries to flush the output buffer and execute
354
+ * callbacks for all remaining replies. When this function is called from a
355
+ * callback, there might be more replies and we can safely defer disconnecting
356
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
357
+ * when there are no pending callbacks. */
358
+ void redisAsyncDisconnect(redisAsyncContext *ac) {
359
+ redisContext *c = &(ac->c);
360
+ c->flags |= REDIS_DISCONNECTING;
361
+ if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
362
+ __redisAsyncDisconnect(ac);
363
+ }
364
+
365
+ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
366
+ redisContext *c = &(ac->c);
367
+ dict *callbacks;
368
+ redisCallback *cb;
369
+ dictEntry *de;
370
+ int pvariant;
371
+ char *stype;
372
+ sds sname;
373
+
374
+ /* Custom reply functions are not supported for pub/sub. This will fail
375
+ * very hard when they are used... */
376
+ if (reply->type == REDIS_REPLY_ARRAY) {
377
+ assert(reply->elements >= 2);
378
+ assert(reply->element[0]->type == REDIS_REPLY_STRING);
379
+ stype = reply->element[0]->str;
380
+ pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
381
+
382
+ if (pvariant)
383
+ callbacks = ac->sub.patterns;
384
+ else
385
+ callbacks = ac->sub.channels;
386
+
387
+ /* Locate the right callback */
388
+ assert(reply->element[1]->type == REDIS_REPLY_STRING);
389
+ sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
390
+ de = dictFind(callbacks,sname);
391
+ if (de != NULL) {
392
+ cb = dictGetEntryVal(de);
393
+
394
+ /* If this is an subscribe reply decrease pending counter. */
395
+ if (strcasecmp(stype+pvariant,"subscribe") == 0) {
396
+ cb->pending_subs -= 1;
397
+ }
398
+
399
+ memcpy(dstcb,cb,sizeof(*dstcb));
400
+
401
+ /* If this is an unsubscribe message, remove it. */
402
+ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
403
+ if (cb->pending_subs == 0)
404
+ dictDelete(callbacks,sname);
405
+
406
+ /* If this was the last unsubscribe message, revert to
407
+ * non-subscribe mode. */
408
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
409
+
410
+ /* Unset subscribed flag only when no pipelined pending subscribe. */
411
+ if (reply->element[2]->integer == 0
412
+ && dictSize(ac->sub.channels) == 0
413
+ && dictSize(ac->sub.patterns) == 0)
414
+ c->flags &= ~REDIS_SUBSCRIBED;
415
+ }
416
+ }
417
+ sdsfree(sname);
418
+ } else {
419
+ /* Shift callback for invalid commands. */
420
+ __redisShiftCallback(&ac->sub.invalid,dstcb);
421
+ }
422
+ return REDIS_OK;
423
+ }
424
+
425
+ void redisProcessCallbacks(redisAsyncContext *ac) {
426
+ redisContext *c = &(ac->c);
427
+ redisCallback cb = {NULL, NULL, 0, NULL};
428
+ void *reply = NULL;
429
+ int status;
430
+
431
+ while((status = redisGetReply(c,&reply)) == REDIS_OK) {
432
+ if (reply == NULL) {
433
+ /* When the connection is being disconnected and there are
434
+ * no more replies, this is the cue to really disconnect. */
435
+ if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
436
+ && ac->replies.head == NULL) {
437
+ __redisAsyncDisconnect(ac);
438
+ return;
439
+ }
440
+
441
+ /* If monitor mode, repush callback */
442
+ if(c->flags & REDIS_MONITORING) {
443
+ __redisPushCallback(&ac->replies,&cb);
444
+ }
445
+
446
+ /* When the connection is not being disconnected, simply stop
447
+ * trying to get replies and wait for the next loop tick. */
448
+ break;
449
+ }
450
+
451
+ /* Even if the context is subscribed, pending regular callbacks will
452
+ * get a reply before pub/sub messages arrive. */
453
+ if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
454
+ /*
455
+ * A spontaneous reply in a not-subscribed context can be the error
456
+ * reply that is sent when a new connection exceeds the maximum
457
+ * number of allowed connections on the server side.
458
+ *
459
+ * This is seen as an error instead of a regular reply because the
460
+ * server closes the connection after sending it.
461
+ *
462
+ * To prevent the error from being overwritten by an EOF error the
463
+ * connection is closed here. See issue #43.
464
+ *
465
+ * Another possibility is that the server is loading its dataset.
466
+ * In this case we also want to close the connection, and have the
467
+ * user wait until the server is ready to take our request.
468
+ */
469
+ if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
470
+ c->err = REDIS_ERR_OTHER;
471
+ snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
472
+ c->reader->fn->freeObject(reply);
473
+ __redisAsyncDisconnect(ac);
474
+ return;
475
+ }
476
+ /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
477
+ assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
478
+ if(c->flags & REDIS_SUBSCRIBED)
479
+ __redisGetSubscribeCallback(ac,reply,&cb);
480
+ }
481
+
482
+ if (cb.fn != NULL) {
483
+ __redisRunCallback(ac,&cb,reply);
484
+ c->reader->fn->freeObject(reply);
485
+
486
+ /* Proceed with free'ing when redisAsyncFree() was called. */
487
+ if (c->flags & REDIS_FREEING) {
488
+ __redisAsyncFree(ac);
489
+ return;
490
+ }
491
+ } else {
492
+ /* No callback for this reply. This can either be a NULL callback,
493
+ * or there were no callbacks to begin with. Either way, don't
494
+ * abort with an error, but simply ignore it because the client
495
+ * doesn't know what the server will spit out over the wire. */
496
+ c->reader->fn->freeObject(reply);
497
+ }
498
+ }
499
+
500
+ /* Disconnect when there was an error reading the reply */
501
+ if (status != REDIS_OK)
502
+ __redisAsyncDisconnect(ac);
503
+ }
504
+
505
+ /* Internal helper function to detect socket status the first time a read or
506
+ * write event fires. When connecting was not successful, the connect callback
507
+ * is called with a REDIS_ERR status and the context is free'd. */
508
+ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
509
+ redisContext *c = &(ac->c);
510
+
511
+ if (redisCheckSocketError(c) == REDIS_ERR) {
512
+ /* Try again later when connect(2) is still in progress. */
513
+ if (errno == EINPROGRESS)
514
+ return REDIS_OK;
515
+
516
+ if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
517
+ __redisAsyncDisconnect(ac);
518
+ return REDIS_ERR;
519
+ }
520
+
521
+ /* Mark context as connected. */
522
+ c->flags |= REDIS_CONNECTED;
523
+ if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
524
+ return REDIS_OK;
525
+ }
526
+
527
+ /* This function should be called when the socket is readable.
528
+ * It processes all replies that can be read and executes their callbacks.
529
+ */
530
+ void redisAsyncHandleRead(redisAsyncContext *ac) {
531
+ redisContext *c = &(ac->c);
532
+
533
+ if (!(c->flags & REDIS_CONNECTED)) {
534
+ /* Abort connect was not successful. */
535
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
536
+ return;
537
+ /* Try again later when the context is still not connected. */
538
+ if (!(c->flags & REDIS_CONNECTED))
539
+ return;
540
+ }
541
+
542
+ if (redisBufferRead(c) == REDIS_ERR) {
543
+ __redisAsyncDisconnect(ac);
544
+ } else {
545
+ /* Always re-schedule reads */
546
+ _EL_ADD_READ(ac);
547
+ redisProcessCallbacks(ac);
548
+ }
549
+ }
550
+
551
+ void redisAsyncHandleWrite(redisAsyncContext *ac) {
552
+ redisContext *c = &(ac->c);
553
+ int done = 0;
554
+
555
+ if (!(c->flags & REDIS_CONNECTED)) {
556
+ /* Abort connect was not successful. */
557
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
558
+ return;
559
+ /* Try again later when the context is still not connected. */
560
+ if (!(c->flags & REDIS_CONNECTED))
561
+ return;
562
+ }
563
+
564
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
565
+ __redisAsyncDisconnect(ac);
566
+ } else {
567
+ /* Continue writing when not done, stop writing otherwise */
568
+ if (!done)
569
+ _EL_ADD_WRITE(ac);
570
+ else
571
+ _EL_DEL_WRITE(ac);
572
+
573
+ /* Always schedule reads after writes */
574
+ _EL_ADD_READ(ac);
575
+ }
576
+ }
577
+
578
+ /* Sets a pointer to the first argument and its length starting at p. Returns
579
+ * the number of bytes to skip to get to the following argument. */
580
+ static const char *nextArgument(const char *start, const char **str, size_t *len) {
581
+ const char *p = start;
582
+ if (p[0] != '$') {
583
+ p = strchr(p,'$');
584
+ if (p == NULL) return NULL;
585
+ }
586
+
587
+ *len = (int)strtol(p+1,NULL,10);
588
+ p = strchr(p,'\r');
589
+ assert(p);
590
+ *str = p+2;
591
+ return p+2+(*len)+2;
592
+ }
593
+
594
+ /* Helper function for the redisAsyncCommand* family of functions. Writes a
595
+ * formatted command to the output buffer and registers the provided callback
596
+ * function with the context. */
597
+ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
598
+ redisContext *c = &(ac->c);
599
+ redisCallback cb;
600
+ struct dict *cbdict;
601
+ dictEntry *de;
602
+ redisCallback *existcb;
603
+ int pvariant, hasnext;
604
+ const char *cstr, *astr;
605
+ size_t clen, alen;
606
+ const char *p;
607
+ sds sname;
608
+ int ret;
609
+
610
+ /* Don't accept new commands when the connection is about to be closed. */
611
+ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
612
+
613
+ /* Setup callback */
614
+ cb.fn = fn;
615
+ cb.privdata = privdata;
616
+ cb.pending_subs = 1;
617
+
618
+ /* Find out which command will be appended. */
619
+ p = nextArgument(cmd,&cstr,&clen);
620
+ assert(p != NULL);
621
+ hasnext = (p[0] == '$');
622
+ pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
623
+ cstr += pvariant;
624
+ clen -= pvariant;
625
+
626
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
627
+ c->flags |= REDIS_SUBSCRIBED;
628
+
629
+ /* Add every channel/pattern to the list of subscription callbacks. */
630
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
631
+ sname = sdsnewlen(astr,alen);
632
+ if (pvariant)
633
+ cbdict = ac->sub.patterns;
634
+ else
635
+ cbdict = ac->sub.channels;
636
+
637
+ de = dictFind(cbdict,sname);
638
+
639
+ if (de != NULL) {
640
+ existcb = dictGetEntryVal(de);
641
+ cb.pending_subs = existcb->pending_subs + 1;
642
+ }
643
+
644
+ ret = dictReplace(cbdict,sname,&cb);
645
+
646
+ if (ret == 0) sdsfree(sname);
647
+ }
648
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
649
+ /* It is only useful to call (P)UNSUBSCRIBE when the context is
650
+ * subscribed to one or more channels or patterns. */
651
+ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
652
+
653
+ /* (P)UNSUBSCRIBE does not have its own response: every channel or
654
+ * pattern that is unsubscribed will receive a message. This means we
655
+ * should not append a callback function for this command. */
656
+ } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
657
+ /* Set monitor flag and push callback */
658
+ c->flags |= REDIS_MONITORING;
659
+ __redisPushCallback(&ac->replies,&cb);
660
+ } else {
661
+ if (c->flags & REDIS_SUBSCRIBED)
662
+ /* This will likely result in an error reply, but it needs to be
663
+ * received and passed to the callback. */
664
+ __redisPushCallback(&ac->sub.invalid,&cb);
665
+ else
666
+ __redisPushCallback(&ac->replies,&cb);
667
+ }
668
+
669
+ __redisAppendCommand(c,cmd,len);
670
+
671
+ /* Always schedule a write when the write buffer is non-empty */
672
+ _EL_ADD_WRITE(ac);
673
+
674
+ return REDIS_OK;
675
+ }
676
+
677
+ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
678
+ char *cmd;
679
+ int len;
680
+ int status;
681
+ len = redisvFormatCommand(&cmd,format,ap);
682
+
683
+ /* We don't want to pass -1 or -2 to future functions as a length. */
684
+ if (len < 0)
685
+ return REDIS_ERR;
686
+
687
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
688
+ free(cmd);
689
+ return status;
690
+ }
691
+
692
+ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
693
+ va_list ap;
694
+ int status;
695
+ va_start(ap,format);
696
+ status = redisvAsyncCommand(ac,fn,privdata,format,ap);
697
+ va_end(ap);
698
+ return status;
699
+ }
700
+
701
+ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
702
+ sds cmd;
703
+ int len;
704
+ int status;
705
+ len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
706
+ if (len < 0)
707
+ return REDIS_ERR;
708
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
709
+ sdsfree(cmd);
710
+ return status;
711
+ }
712
+
713
+ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
714
+ int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
715
+ return status;
716
+ }