Commit 1f72ec7d authored by flowly's avatar flowly Committed by GitHub
Browse files

Merge pull request #1 from antirez/unstable

update to upstream
parents dfc98dcc f917e0da
/*-
* Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef __HIREDIS_QT_H__
#define __HIREDIS_QT_H__
#include <QSocketNotifier>
#include "../async.h"
static void RedisQtAddRead(void *);
static void RedisQtDelRead(void *);
static void RedisQtAddWrite(void *);
static void RedisQtDelWrite(void *);
static void RedisQtCleanup(void *);
class RedisQtAdapter : public QObject {
Q_OBJECT
friend
void RedisQtAddRead(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->addRead();
}
friend
void RedisQtDelRead(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->delRead();
}
friend
void RedisQtAddWrite(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->addWrite();
}
friend
void RedisQtDelWrite(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->delWrite();
}
friend
void RedisQtCleanup(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->cleanup();
}
public:
RedisQtAdapter(QObject * parent = 0)
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
~RedisQtAdapter() {
if (m_ctx != 0) {
m_ctx->ev.data = NULL;
}
}
int setContext(redisAsyncContext * ac) {
if (ac->ev.data != NULL) {
return REDIS_ERR;
}
m_ctx = ac;
m_ctx->ev.data = this;
m_ctx->ev.addRead = RedisQtAddRead;
m_ctx->ev.delRead = RedisQtDelRead;
m_ctx->ev.addWrite = RedisQtAddWrite;
m_ctx->ev.delWrite = RedisQtDelWrite;
m_ctx->ev.cleanup = RedisQtCleanup;
return REDIS_OK;
}
private:
void addRead() {
if (m_read) return;
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
}
void delRead() {
if (!m_read) return;
delete m_read;
m_read = 0;
}
void addWrite() {
if (m_write) return;
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
}
void delWrite() {
if (!m_write) return;
delete m_write;
m_write = 0;
}
void cleanup() {
delRead();
delWrite();
}
private slots:
void read() { redisAsyncHandleRead(m_ctx); }
void write() { redisAsyncHandleWrite(m_ctx); }
private:
redisAsyncContext * m_ctx;
QSocketNotifier * m_read;
QSocketNotifier * m_write;
};
#endif /* !__HIREDIS_QT_H__ */
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
environment:
matrix:
- CYG_ROOT: C:\cygwin64
CYG_SETUP: setup-x86_64.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin64\var\cache\setup
CYG_BASH: C:\cygwin64\bin\bash
CC: gcc
- CYG_ROOT: C:\cygwin
CYG_SETUP: setup-x86.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin\var\cache\setup
CYG_BASH: C:\cygwin\bin\bash
CC: gcc
TARGET: 32bit
TARGET_VARS: 32bit-vars
# Cache Cygwin files to speed up build
cache:
- '%CYG_CACHE%'
clone_depth: 1
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
init:
- git config --global core.autocrlf input
# Install needed build dependencies
install:
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script:
- 'echo building...'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
...@@ -58,11 +58,12 @@ ...@@ -58,11 +58,12 @@
} while(0); } while(0);
/* Forward declaration of function in hiredis.c */ /* Forward declaration of function in hiredis.c */
void __redisAppendCommand(redisContext *c, char *cmd, size_t len); int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
/* Functions managing dictionary of callbacks for pub/sub. */ /* Functions managing dictionary of callbacks for pub/sub. */
static unsigned int callbackHash(const void *key) { static unsigned int callbackHash(const void *key) {
return dictGenHashFunction((unsigned char*)key,sdslen((char*)key)); return dictGenHashFunction((const unsigned char *)key,
sdslen((const sds)key));
} }
static void *callbackValDup(void *privdata, const void *src) { static void *callbackValDup(void *privdata, const void *src) {
...@@ -76,8 +77,8 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2 ...@@ -76,8 +77,8 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2
int l1, l2; int l1, l2;
((void) privdata); ((void) privdata);
l1 = sdslen((sds)key1); l1 = sdslen((const sds)key1);
l2 = sdslen((sds)key2); l2 = sdslen((const sds)key2);
if (l1 != l2) return 0; if (l1 != l2) return 0;
return memcmp(key1,key2,l1) == 0; return memcmp(key1,key2,l1) == 0;
} }
...@@ -102,7 +103,12 @@ static dictType callbackDict = { ...@@ -102,7 +103,12 @@ static dictType callbackDict = {
}; };
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext)); redisAsyncContext *ac;
ac = realloc(c,sizeof(redisAsyncContext));
if (ac == NULL)
return NULL;
c = &(ac->c); c = &(ac->c);
/* The regular connect functions will always set the flag REDIS_CONNECTED. /* The regular connect functions will always set the flag REDIS_CONNECTED.
...@@ -136,25 +142,66 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ...@@ -136,25 +142,66 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
/* We want the error field to be accessible directly instead of requiring /* We want the error field to be accessible directly instead of requiring
* an indirection to the redisContext struct. */ * an indirection to the redisContext struct. */
static void __redisAsyncCopyError(redisAsyncContext *ac) { static void __redisAsyncCopyError(redisAsyncContext *ac) {
if (!ac)
return;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
ac->err = c->err; ac->err = c->err;
ac->errstr = c->errstr; ac->errstr = c->errstr;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
redisContext *c = redisConnectNonBlock(ip,port); redisContext *c;
redisAsyncContext *ac;
c = redisConnectNonBlock(ip,port);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
}
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) {
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
redisAsyncContext *ac = redisAsyncInitialize(c); redisAsyncContext *ac = redisAsyncInitialize(c);
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisContext *c = redisConnectUnixNonBlock(path); const char *source_addr) {
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
redisAsyncContext *ac = redisAsyncInitialize(c); redisAsyncContext *ac = redisAsyncInitialize(c);
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
redisContext *c;
redisAsyncContext *ac;
c = redisConnectUnixNonBlock(path);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
if (ac->onConnect == NULL) { if (ac->onConnect == NULL) {
ac->onConnect = fn; ac->onConnect = fn;
...@@ -182,6 +229,9 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { ...@@ -182,6 +229,9 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
/* Copy callback from stack to heap */ /* Copy callback from stack to heap */
cb = malloc(sizeof(*cb)); cb = malloc(sizeof(*cb));
if (cb == NULL)
return REDIS_ERR_OOM;
if (source != NULL) { if (source != NULL) {
memcpy(cb,source,sizeof(*cb)); memcpy(cb,source,sizeof(*cb));
cb->next = NULL; cb->next = NULL;
...@@ -360,7 +410,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -360,7 +410,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb = {NULL, NULL, NULL};
void *reply = NULL; void *reply = NULL;
int status; int status;
...@@ -368,7 +418,8 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -368,7 +418,8 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
if (reply == NULL) { if (reply == NULL) {
/* When the connection is being disconnected and there are /* When the connection is being disconnected and there are
* no more replies, this is the cue to really disconnect. */ * no more replies, this is the cue to really disconnect. */
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
&& ac->replies.head == NULL) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return; return;
} }
...@@ -404,6 +455,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -404,6 +455,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
c->err = REDIS_ERR_OTHER; c->err = REDIS_ERR_OTHER;
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
c->reader->fn->freeObject(reply);
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return; return;
} }
...@@ -437,12 +489,12 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -437,12 +489,12 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
} }
/* Internal helper function to detect socket status the first time a read or /* Internal helper function to detect socket status the first time a read or
* write event fires. When connecting was not succesful, the connect callback * write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */ * is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (redisCheckSocketError(c,c->fd) == REDIS_ERR) { if (redisCheckSocketError(c) == REDIS_ERR) {
/* Try again later when connect(2) is still in progress. */ /* Try again later when connect(2) is still in progress. */
if (errno == EINPROGRESS) if (errno == EINPROGRESS)
return REDIS_OK; return REDIS_OK;
...@@ -511,8 +563,8 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { ...@@ -511,8 +563,8 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
/* Sets a pointer to the first argument and its length starting at p. Returns /* Sets a pointer to the first argument and its length starting at p. Returns
* the number of bytes to skip to get to the following argument. */ * the number of bytes to skip to get to the following argument. */
static char *nextArgument(char *start, char **str, size_t *len) { static const char *nextArgument(const char *start, const char **str, size_t *len) {
char *p = start; const char *p = start;
if (p[0] != '$') { if (p[0] != '$') {
p = strchr(p,'$'); p = strchr(p,'$');
if (p == NULL) return NULL; if (p == NULL) return NULL;
...@@ -528,14 +580,15 @@ static char *nextArgument(char *start, char **str, size_t *len) { ...@@ -528,14 +580,15 @@ static char *nextArgument(char *start, char **str, size_t *len) {
/* Helper function for the redisAsyncCommand* family of functions. Writes a /* Helper function for the redisAsyncCommand* family of functions. Writes a
* formatted command to the output buffer and registers the provided callback * formatted command to the output buffer and registers the provided callback
* function with the context. */ * function with the context. */
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) { static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
int pvariant, hasnext; int pvariant, hasnext;
char *cstr, *astr; const char *cstr, *astr;
size_t clen, alen; size_t clen, alen;
char *p; const char *p;
sds sname; sds sname;
int ret;
/* Don't accept new commands when the connection is about to be closed. */ /* Don't accept new commands when the connection is about to be closed. */
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
...@@ -559,9 +612,11 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -559,9 +612,11 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
while ((p = nextArgument(p,&astr,&alen)) != NULL) { while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen); sname = sdsnewlen(astr,alen);
if (pvariant) if (pvariant)
dictReplace(ac->sub.patterns,sname,&cb); ret = dictReplace(ac->sub.patterns,sname,&cb);
else else
dictReplace(ac->sub.channels,sname,&cb); ret = dictReplace(ac->sub.channels,sname,&cb);
if (ret == 0) sdsfree(sname);
} }
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
/* It is only useful to call (P)UNSUBSCRIBE when the context is /* It is only useful to call (P)UNSUBSCRIBE when the context is
...@@ -597,6 +652,11 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat ...@@ -597,6 +652,11 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
int len; int len;
int status; int status;
len = redisvFormatCommand(&cmd,format,ap); len = redisvFormatCommand(&cmd,format,ap);
/* We don't want to pass -1 or -2 to future functions as a length. */
if (len < 0)
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
free(cmd); free(cmd);
return status; return status;
...@@ -612,11 +672,16 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata ...@@ -612,11 +672,16 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
} }
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
char *cmd; sds cmd;
int len; int len;
int status; int status;
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
free(cmd); sdsfree(cmd);
return status;
}
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status; return status;
} }
...@@ -102,6 +102,9 @@ typedef struct redisAsyncContext { ...@@ -102,6 +102,9 @@ typedef struct redisAsyncContext {
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr);
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
...@@ -117,6 +120,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac); ...@@ -117,6 +120,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac);
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
#ifdef __cplusplus #ifdef __cplusplus
} }
......
...@@ -161,7 +161,7 @@ static int dictReplace(dict *ht, void *key, void *val) { ...@@ -161,7 +161,7 @@ static int dictReplace(dict *ht, void *key, void *val) {
dictEntry *entry, auxentry; dictEntry *entry, auxentry;
/* Try to add the element. If the key /* Try to add the element. If the key
* does not exists dictAdd will suceed. */ * does not exists dictAdd will succeed. */
if (dictAdd(ht, key, val) == DICT_OK) if (dictAdd(ht, key, val) == DICT_OK)
return 1; return 1;
/* It already exists, get the entry */ /* It already exists, get the entry */
...@@ -293,7 +293,7 @@ static void dictReleaseIterator(dictIterator *iter) { ...@@ -293,7 +293,7 @@ static void dictReleaseIterator(dictIterator *iter) {
/* Expand the hash table if needed */ /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) { static int _dictExpandIfNeeded(dict *ht) {
/* If the hash table is empty expand it to the intial size, /* If the hash table is empty expand it to the initial size,
* if the table is "full" dobule its size. */ * if the table is "full" dobule its size. */
if (ht->size == 0) if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE); return dictExpand(ht, DICT_HT_INITIAL_SIZE);
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include "hiredis.h"
#include "async.h" #include <hiredis.h>
#include "adapters/ae.h" #include <async.h>
#include <adapters/ae.h>
/* Put event loop in the global scope, so it can be explicitly stopped */ /* Put event loop in the global scope, so it can be explicitly stopped */
static aeEventLoop *loop; static aeEventLoop *loop;
...@@ -21,17 +22,22 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { ...@@ -21,17 +22,22 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) {
void connectCallback(const redisAsyncContext *c, int status) { void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) { if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
aeStop(loop);
return; return;
} }
printf("Connected...\n"); printf("Connected...\n");
} }
void disconnectCallback(const redisAsyncContext *c, int status) { void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) { if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
aeStop(loop);
return; return;
} }
printf("Disconnected...\n"); printf("Disconnected...\n");
aeStop(loop);
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
...@@ -44,7 +50,7 @@ int main (int argc, char **argv) { ...@@ -44,7 +50,7 @@ int main (int argc, char **argv) {
return 1; return 1;
} }
loop = aeCreateEventLoop(); loop = aeCreateEventLoop(64);
redisAeAttach(loop, c); redisAeAttach(loop, c);
redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback);
......
#include <stdlib.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/glib.h>
static GMainLoop *mainloop;
static void
connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
int status)
{
if (status != REDIS_OK) {
g_printerr("Failed to connect: %s\n", ac->errstr);
g_main_loop_quit(mainloop);
} else {
g_printerr("Connected...\n");
}
}
static void
disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
int status)
{
if (status != REDIS_OK) {
g_error("Failed to disconnect: %s", ac->errstr);
} else {
g_printerr("Disconnected...\n");
g_main_loop_quit(mainloop);
}
}
static void
command_cb(redisAsyncContext *ac,
gpointer r,
gpointer user_data G_GNUC_UNUSED)
{
redisReply *reply = r;
if (reply) {
g_print("REPLY: %s\n", reply->str);
}
redisAsyncDisconnect(ac);
}
gint
main (gint argc G_GNUC_UNUSED,
gchar *argv[] G_GNUC_UNUSED)
{
redisAsyncContext *ac;
GMainContext *context = NULL;
GSource *source;
ac = redisAsyncConnect("127.0.0.1", 6379);
if (ac->err) {
g_printerr("%s\n", ac->errstr);
exit(EXIT_FAILURE);
}
source = redis_source_new(ac);
mainloop = g_main_loop_new(context, FALSE);
g_source_attach(source, context);
redisAsyncSetConnectCallback(ac, connect_cb);
redisAsyncSetDisconnectCallback(ac, disconnect_cb);
redisAsyncCommand(ac, command_cb, NULL, "SET key 1234");
redisAsyncCommand(ac, command_cb, NULL, "GET key");
g_main_loop_run(mainloop);
return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/ivykis.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
iv_init();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisIvykisAttach(c);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
iv_main();
iv_deinit();
return 0;
}
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include "hiredis.h"
#include "async.h" #include <hiredis.h>
#include "adapters/libev.h" #include <async.h>
#include <adapters/libev.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) { void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include "hiredis.h"
#include "async.h" #include <hiredis.h>
#include "adapters/libevent.h" #include <async.h>
#include <adapters/libevent.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) { void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/libuv.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
uv_loop_t* loop = uv_default_loop();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisLibuvAttach(c,loop);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
//
// Created by Дмитрий Бахвалов on 13.07.15.
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//
#include <stdio.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/macosx.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
CFRunLoopStop(CFRunLoopGetCurrent());
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
CFRunLoopRef loop = CFRunLoopGetCurrent();
if( !loop ) {
printf("Error: Cannot get current run loop\n");
return 1;
}
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisMacOSAttach(c, loop);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
CFRunLoopRun();
return 0;
}
#include <iostream>
using namespace std;
#include <QCoreApplication>
#include <QTimer>
#include "example-qt.h"
void getCallback(redisAsyncContext *, void * r, void * privdata) {
redisReply * reply = static_cast<redisReply *>(r);
ExampleQt * ex = static_cast<ExampleQt *>(privdata);
if (reply == nullptr || ex == nullptr) return;
cout << "key: " << reply->str << endl;
ex->finish();
}
void ExampleQt::run() {
m_ctx = redisAsyncConnect("localhost", 6379);
if (m_ctx->err) {
cerr << "Error: " << m_ctx->errstr << endl;
redisAsyncFree(m_ctx);
emit finished();
}
m_adapter.setContext(m_ctx);
redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
redisAsyncCommand(m_ctx, getCallback, this, "GET key");
}
int main (int argc, char **argv) {
QCoreApplication app(argc, argv);
ExampleQt example(argv[argc-1]);
QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
QTimer::singleShot(0, &example, SLOT(run()));
return app.exec();
}
#ifndef __HIREDIS_EXAMPLE_QT_H
#define __HIREDIS_EXAMPLE_QT_H
#include <adapters/qt.h>
class ExampleQt : public QObject {
Q_OBJECT
public:
ExampleQt(const char * value, QObject * parent = 0)
: QObject(parent), m_value(value) {}
signals:
void finished();
public slots:
void run();
private:
void finish() { emit finished(); }
private:
const char * m_value;
redisAsyncContext * m_ctx;
RedisQtAdapter m_adapter;
friend
void getCallback(redisAsyncContext *, void *, void *);
};
#endif /* !__HIREDIS_EXAMPLE_QT_H */
...@@ -2,17 +2,24 @@ ...@@ -2,17 +2,24 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "hiredis.h" #include <hiredis.h>
int main(void) { int main(int argc, char **argv) {
unsigned int j; unsigned int j;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout((char*)"127.0.0.2", 6379, timeout); c = redisConnectWithTimeout(hostname, port, timeout);
if (c->err) { if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr); printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1); exit(1);
} }
...@@ -27,7 +34,7 @@ int main(void) { ...@@ -27,7 +34,7 @@ int main(void) {
freeReplyObject(reply); freeReplyObject(reply);
/* Set a key using binary safe API */ /* Set a key using binary safe API */
reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5); reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
printf("SET (binary API): %s\n", reply->str); printf("SET (binary API): %s\n", reply->str);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -50,7 +57,7 @@ int main(void) { ...@@ -50,7 +57,7 @@ int main(void) {
for (j = 0; j < 10; j++) { for (j = 0; j < 10; j++) {
char buf[64]; char buf[64];
snprintf(buf,64,"%d",j); snprintf(buf,64,"%u",j);
reply = redisCommand(c,"LPUSH mylist element-%s", buf); reply = redisCommand(c,"LPUSH mylist element-%s", buf);
freeReplyObject(reply); freeReplyObject(reply);
} }
...@@ -64,5 +71,8 @@ int main(void) { ...@@ -64,5 +71,8 @@ int main(void) {
} }
freeReplyObject(reply); freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0; return 0;
} }
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#if !defined(_BSD_SOURCE) #if defined(__linux__)
#define _BSD_SOURCE #define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif
#if defined(__CYGWIN__)
#include <sys/cdefs.h>
#endif #endif
#if defined(__sun__) #if defined(__sun__)
#define _POSIX_C_SOURCE 200112L #define _POSIX_C_SOURCE 200112L
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
#define _XOPEN_SOURCE 600
#else #else
#define _XOPEN_SOURCE #if !(defined(__APPLE__) && defined(__MACH__))
#define _XOPEN_SOURCE 600
#endif
#endif
#if defined(__APPLE__) && defined(__MACH__)
#define _OSX
#endif #endif
#endif #endif
This diff is collapsed.
/* /*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -31,26 +33,16 @@ ...@@ -31,26 +33,16 @@
#ifndef __HIREDIS_H #ifndef __HIREDIS_H
#define __HIREDIS_H #define __HIREDIS_H
#include <stdio.h> /* for size_t */ #include "read.h"
#include <stdarg.h> /* for va_list */ #include <stdarg.h> /* for va_list */
#include <sys/time.h> /* for struct timeval */ #include <sys/time.h> /* for struct timeval */
#include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 10 #define HIREDIS_MINOR 13
#define HIREDIS_PATCH 1 #define HIREDIS_PATCH 3
#define HIREDIS_SONAME 0.13
#define REDIS_ERR -1
#define REDIS_OK 0
/* When an error occurs, the err flag in a context is set to hold the type of
* error that occured. REDIS_ERR_IO means there was an I/O error and you
* should use the "errno" variable to find out what is wrong.
* For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_OTHER 2 /* Everything else... */
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
...@@ -79,14 +71,38 @@ ...@@ -79,14 +71,38 @@
/* Flag that is set when monitor mode is active */ /* Flag that is set when monitor mode is active */
#define REDIS_MONITORING 0x40 #define REDIS_MONITORING 0x40
#define REDIS_REPLY_STRING 1 /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REPLY_ARRAY 2 #define REDIS_REUSEADDR 0x80
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4 #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6 /* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ #define REDIS_CONNECT_RETRIES 10
/* strerror_r has two completely different prototypes and behaviors
* depending on system issues, so we need to operate on the error buffer
* differently depending on which strerror_r we're using. */
#ifndef _GNU_SOURCE
/* "regular" POSIX strerror_r that does the right thing. */
#define __redis_strerror_r(errno, buf, len) \
do { \
strerror_r((errno), (buf), (len)); \
} while (0)
#else
/* "bad" GNU strerror_r we need to clean up after. */
#define __redis_strerror_r(errno, buf, len) \
do { \
char *err_str = strerror_r((errno), (buf), (len)); \
/* If return value _isn't_ the start of the buffer we passed in, \
* then GNU strerror_r returned an internal static buffer and we \
* need to copy the result into our private buffer. */ \
if (err_str != (buf)) { \
strncpy((buf), err_str, ((len) - 1)); \
buf[(len)-1] = '\0'; \
} \
} while (0)
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
...@@ -96,61 +112,13 @@ extern "C" { ...@@ -96,61 +112,13 @@ extern "C" {
typedef struct redisReply { typedef struct redisReply {
int type; /* REDIS_REPLY_* */ int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
int len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply; } redisReply;
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
/* State for the protocol parser */
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
/* Public API for the protocol parser. */
redisReader *redisReaderCreate(void); redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
/* Backwards compatibility, can be removed on big version bump. */
#define redisReplyReaderCreate redisReaderCreate
#define redisReplyReaderFree redisReaderFree
#define redisReplyReaderFeed redisReaderFeed
#define redisReplyReaderGetReply redisReaderGetReply
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
/* Function to free the reply objects hiredis returns by default. */ /* Function to free the reply objects hiredis returns by default. */
void freeReplyObject(void *reply); void freeReplyObject(void *reply);
...@@ -159,6 +127,14 @@ void freeReplyObject(void *reply); ...@@ -159,6 +127,14 @@ void freeReplyObject(void *reply);
int redisvFormatCommand(char **target, const char *format, va_list ap); int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(sds cmd);
enum redisConnectionType {
REDIS_CONN_TCP,
REDIS_CONN_UNIX
};
/* Context for a connection to Redis */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
...@@ -168,16 +144,49 @@ typedef struct redisContext { ...@@ -168,16 +144,49 @@ typedef struct redisContext {
int flags; int flags;
char *obuf; /* Write buffer */ char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */ redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type;
struct timeval *timeout;
struct {
char *host;
char *source_addr;
int port;
} tcp;
struct {
char *path;
} unix_sock;
} redisContext; } redisContext;
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectNonBlock(const char *ip, int port);
redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr);
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr);
redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnixNonBlock(const char *path);
int redisSetTimeout(redisContext *c, struct timeval tv); redisContext *redisConnectFd(int fd);
/**
* Reconnect the given context using the saved information.
*
* This re-uses the exact same connect options as in the initial connection.
* host, ip (or path), timeout and bind address are reused,
* flags are used unmodified from the existing context.
*
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
*/
int redisReconnect(redisContext *c);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c); void redisFree(redisContext *c);
int redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c); int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done); int redisBufferWrite(redisContext *c, int *done);
...@@ -188,6 +197,10 @@ int redisBufferWrite(redisContext *c, int *done); ...@@ -188,6 +197,10 @@ int redisBufferWrite(redisContext *c, int *done);
int redisGetReply(redisContext *c, void **reply); int redisGetReply(redisContext *c, void **reply);
int redisGetReplyFromReader(redisContext *c, void **reply); int redisGetReplyFromReader(redisContext *c, void **reply);
/* Write a formatted command to the output buffer. Use these functions in blocking mode
* to get a pipeline of commands. */
int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
/* Write a command to the output buffer. Use these functions in blocking mode /* Write a command to the output buffer. Use these functions in blocking mode
* to get a pipeline of commands. */ * to get a pipeline of commands. */
int redisvAppendCommand(redisContext *c, const char *format, va_list ap); int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
......
/* Extracted from anet.c to work properly with Hiredis error reporting. /* Extracted from anet.c to work properly with Hiredis error reporting.
* *
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -47,6 +49,7 @@ ...@@ -47,6 +49,7 @@
#include <stdio.h> #include <stdio.h>
#include <poll.h> #include <poll.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h>
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
...@@ -54,21 +57,28 @@ ...@@ -54,21 +57,28 @@
/* Defined in hiredis.c */ /* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
static void redisContextCloseFd(redisContext *c) {
if (c && c->fd >= 0) {
close(c->fd);
c->fd = -1;
}
}
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
char buf[128]; char buf[128] = { 0 };
size_t len = 0; size_t len = 0;
if (prefix != NULL) if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix); len = snprintf(buf,sizeof(buf),"%s: ",prefix);
strerror_r(errno,buf+len,sizeof(buf)-len); __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf); __redisSetError(c,type,buf);
} }
static int redisSetReuseAddr(redisContext *c, int fd) { static int redisSetReuseAddr(redisContext *c) {
int on = 1; int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
...@@ -80,23 +90,24 @@ static int redisCreateSocket(redisContext *c, int type) { ...@@ -80,23 +90,24 @@ static int redisCreateSocket(redisContext *c, int type) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR; return REDIS_ERR;
} }
c->fd = s;
if (type == AF_INET) { if (type == AF_INET) {
if (redisSetReuseAddr(c,s) == REDIS_ERR) { if (redisSetReuseAddr(c) == REDIS_ERR) {
return REDIS_ERR; return REDIS_ERR;
} }
} }
return s; return REDIS_OK;
} }
static int redisSetBlocking(redisContext *c, int fd, int blocking) { static int redisSetBlocking(redisContext *c, int blocking) {
int flags; int flags;
/* Set the socket nonblocking. /* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be * Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */ * interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) { if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -105,19 +116,61 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { ...@@ -105,19 +116,61 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) {
else else
flags |= O_NONBLOCK; flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) { if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
close(fd); redisContextCloseFd(c);
return REDIS_ERR;
}
return REDIS_OK;
}
int redisKeepAlive(redisContext *c, int interval) {
int val = 1;
int fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = interval;
#ifdef _OSX
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = interval/3;
if (val == 0) val = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
val = 3;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#endif
#endif
return REDIS_OK; return REDIS_OK;
} }
static int redisSetTcpNoDelay(redisContext *c, int fd) { static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1; int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
...@@ -125,18 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c, int fd) { ...@@ -125,18 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c, int fd) {
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) #define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) { static int redisContextTimeoutMsec(redisContext *c, long *result)
struct pollfd wfd[1]; {
long msec; const struct timeval *timeout = c->timeout;
long msec = -1;
msec = -1;
wfd[0].fd = fd;
wfd[0].events = POLLOUT;
/* Only use timeout when not NULL. */ /* Only use timeout when not NULL. */
if (timeout != NULL) { if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
close(fd); *result = msec;
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -147,52 +197,60 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * ...@@ -147,52 +197,60 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *
} }
} }
*result = msec;
return REDIS_OK;
}
static int redisContextWaitReady(redisContext *c, long msec) {
struct pollfd wfd[1];
wfd[0].fd = c->fd;
wfd[0].events = POLLOUT;
if (errno == EINPROGRESS) { if (errno == EINPROGRESS) {
int res; int res;
if ((res = poll(wfd, 1, msec)) == -1) { if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} else if (res == 0) { } else if (res == 0) {
errno = ETIMEDOUT; errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
if (redisCheckSocketError(c, fd) != REDIS_OK) if (redisCheckSocketError(c) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
return REDIS_OK; return REDIS_OK;
} }
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd); redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
int redisCheckSocketError(redisContext *c, int fd) { int redisCheckSocketError(redisContext *c) {
int err = 0; int err = 0;
socklen_t errlen = sizeof(err); socklen_t errlen = sizeof(err);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
close(fd);
return REDIS_ERR; return REDIS_ERR;
} }
if (err) { if (err) {
errno = err; errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
int redisContextSetTimeout(redisContext *c, struct timeval tv) { int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR; return REDIS_ERR;
...@@ -204,44 +262,141 @@ int redisContextSetTimeout(redisContext *c, struct timeval tv) { ...@@ -204,44 +262,141 @@ int redisContextSetTimeout(redisContext *c, struct timeval tv) {
return REDIS_OK; return REDIS_OK;
} }
int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) { static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
int s, rv; const struct timeval *timeout,
const char *source_addr) {
int s, rv, n;
char _port[6]; /* strlen("65535"); */ char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *p; struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
int reuseaddr = (c->flags & REDIS_REUSEADDR);
int reuses = 0;
long timeout_msec = -1;
servinfo = NULL;
c->connection_type = REDIS_CONN_TCP;
c->tcp.port = port;
/* We need to take possession of the passed parameters
* to make them reusable for a reconnect.
* We also carefully check we don't free data we already own,
* as in the case of the reconnect method.
*
* This is a bit ugly, but atleast it works and doesn't leak memory.
**/
if (c->tcp.host != addr) {
if (c->tcp.host)
free(c->tcp.host);
c->tcp.host = strdup(addr);
}
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
if (c->timeout)
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
goto error;
}
if (source_addr == NULL) {
free(c->tcp.source_addr);
c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) {
free(c->tcp.source_addr);
c->tcp.source_addr = strdup(source_addr);
}
snprintf(_port, 6, "%d", port); snprintf(_port, 6, "%d", port);
memset(&hints,0,sizeof(hints)); memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
* in a Redis client you can't afford to test if you have IPv6 connectivity
* as this would add latency to every connect. Otherwise a more sensible
* route could be: Use IPv6 if both addresses are available and there is IPv6
* connectivity. */
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
return REDIS_ERR; return REDIS_ERR;
} }
}
for (p = servinfo; p != NULL; p = p->ai_next) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue; continue;
if (redisSetBlocking(c,s,0) != REDIS_OK) c->fd = s;
if (redisSetBlocking(c,0) != REDIS_OK)
goto error;
if (c->tcp.source_addr) {
int bound = 0;
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error;
}
if (reuseaddr) {
n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) {
goto error;
}
}
for (b = bservinfo; b != NULL; b = b->ai_next) {
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
bound = 1;
break;
}
}
freeaddrinfo(bservinfo);
if (!bound) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error; goto error;
}
}
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { if (errno == EHOSTUNREACH) {
close(s); redisContextCloseFd(c);
continue; continue;
} else if (errno == EINPROGRESS && !blocking) { } else if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error;
} else {
redisContextCloseFd(c);
goto addrretry;
}
} else { } else {
if (redisContextWaitReady(c,s,timeout) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
} }
} }
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) if (blocking && redisSetBlocking(c,1) != REDIS_OK)
goto error; goto error;
if (redisSetTcpNoDelay(c,s) != REDIS_OK) if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error; goto error;
c->fd = s;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
rv = REDIS_OK; rv = REDIS_OK;
goto end; goto end;
...@@ -260,32 +415,62 @@ end: ...@@ -260,32 +415,62 @@ end:
return rv; // Need to return REDIS_OK if alright return rv; // Need to return REDIS_OK if alright
} }
int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) { int redisContextConnectTcp(redisContext *c, const char *addr, int port,
int s; const struct timeval *timeout) {
return _redisContextConnectTcp(c, addr, port, timeout, NULL);
}
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr) {
return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa; struct sockaddr_un sa;
long timeout_msec = -1;
if ((s = redisCreateSocket(c,AF_LOCAL)) < 0) if (redisCreateSocket(c,AF_LOCAL) < 0)
return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
if (redisSetBlocking(c,s,0) != REDIS_OK)
c->connection_type = REDIS_CONN_UNIX;
if (c->unix_sock.path != path)
c->unix_sock.path = strdup(path);
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
if (c->timeout)
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa.sun_family = AF_LOCAL; sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else { } else {
if (redisContextWaitReady(c,s,timeout) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
} }
} }
/* Reset socket to be blocking after connect(2). */ /* Reset socket to be blocking after connect(2). */
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) if (blocking && redisSetBlocking(c,1) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
c->fd = s;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
return REDIS_OK; return REDIS_OK;
} }
/* Extracted from anet.c to work properly with Hiredis error reporting. /* Extracted from anet.c to work properly with Hiredis error reporting.
* *
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -39,9 +41,13 @@ ...@@ -39,9 +41,13 @@
#define AF_LOCAL AF_UNIX #define AF_LOCAL AF_UNIX
#endif #endif
int redisCheckSocketError(redisContext *c, int fd); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout); int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval);
#endif #endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment