Deprecate the error object for I/O and protocol errors
Rather, use the return value REDIS_OK and REDIS_ERR to designate if calling a function has a successful outcome. When there was an error in performing the call the error field will be set inside the reader or the context.
This commit is contained in:
parent
e6fb160b44
commit
a7d7618141
211
hiredis.c
211
hiredis.c
@ -41,6 +41,7 @@
|
||||
|
||||
typedef struct redisReader {
|
||||
struct redisReplyObjectFunctions *fn;
|
||||
sds error; /* holds optional error */
|
||||
void *reply; /* holds temporary reply */
|
||||
|
||||
sds buf; /* read buffer */
|
||||
@ -52,16 +53,14 @@ typedef struct redisReader {
|
||||
} redisReader;
|
||||
|
||||
static redisReply *createReplyObject(int type, sds reply);
|
||||
static void *createErrorObject(const char *str, size_t len);
|
||||
static void *createStringObject(redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(redisReadTask *task, int elements);
|
||||
static void *createIntegerObject(redisReadTask *task, long long value);
|
||||
static void *createNilObject(redisReadTask *task);
|
||||
static void redisSetReplyReaderError(redisReader *r, void *obj);
|
||||
static void redisSetReplyReaderError(redisReader *r, sds err);
|
||||
|
||||
/* Default set of functions to build the reply. */
|
||||
static redisReplyFunctions defaultFunctions = {
|
||||
createErrorObject,
|
||||
createStringObject,
|
||||
createArrayObject,
|
||||
createIntegerObject,
|
||||
@ -106,24 +105,6 @@ void freeReplyObject(void *reply) {
|
||||
free(r);
|
||||
}
|
||||
|
||||
/* Helper function that allows printf-like creation of error objects. */
|
||||
static void *formatError(redisReplyFunctions *fn, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
sds err;
|
||||
void *obj;
|
||||
va_start(ap,fmt);
|
||||
err = sdscatvprintf(sdsempty(),fmt,ap);
|
||||
va_end(ap);
|
||||
obj = fn->createError(err,sdslen(err));
|
||||
sdsfree(err);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void *createErrorObject(const char *str, size_t len) {
|
||||
redisReply *r = createReplyObject(REDIS_ERROR,sdsnewlen(str,len));
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createStringObject(redisReadTask *task, char *str, size_t len) {
|
||||
redisReply *r = createReplyObject(task->type,sdsnewlen(str,len));
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
@ -308,7 +289,6 @@ static int processItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rlist[r->rpos]);
|
||||
char *p;
|
||||
sds byte;
|
||||
void *err;
|
||||
|
||||
/* check if we need to read type */
|
||||
if (cur->type < 0) {
|
||||
@ -331,9 +311,8 @@ static int processItem(redisReader *r) {
|
||||
break;
|
||||
default:
|
||||
byte = sdscatrepr(sdsempty(),p,1);
|
||||
err = formatError(r->fn,
|
||||
"protocol error, got %s as reply type byte", byte);
|
||||
redisSetReplyReaderError(r,err);
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"protocol error, got %s as reply type byte", byte));
|
||||
sdsfree(byte);
|
||||
return -1;
|
||||
}
|
||||
@ -354,14 +333,15 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
err = formatError(r->fn,"unknown item type '%d'", cur->type);
|
||||
redisSetReplyReaderError(r,err);
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"unknown item type '%d'", cur->type));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void *redisReplyReaderCreate(redisReplyFunctions *fn) {
|
||||
redisReader *r = calloc(sizeof(redisReader),1);
|
||||
r->error = NULL;
|
||||
r->fn = fn == NULL ? &defaultFunctions : fn;
|
||||
r->buf = sdsempty();
|
||||
r->rlist = malloc(sizeof(redisReadTask)*1);
|
||||
@ -379,6 +359,8 @@ void *redisReplyReaderGetObject(void *reader) {
|
||||
|
||||
void redisReplyReaderFree(void *reader) {
|
||||
redisReader *r = reader;
|
||||
if (r->error != NULL)
|
||||
sdsfree(r->error);
|
||||
if (r->reply != NULL)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
@ -388,7 +370,7 @@ void redisReplyReaderFree(void *reader) {
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void redisSetReplyReaderError(redisReader *r, void *obj) {
|
||||
static void redisSetReplyReaderError(redisReader *r, sds err) {
|
||||
if (r->reply != NULL)
|
||||
r->fn->freeObject(r->reply);
|
||||
|
||||
@ -399,7 +381,12 @@ static void redisSetReplyReaderError(redisReader *r, void *obj) {
|
||||
r->pos = 0;
|
||||
}
|
||||
r->rlen = r->rpos = 0;
|
||||
r->reply = obj;
|
||||
r->error = err;
|
||||
}
|
||||
|
||||
char *redisReplyReaderGetError(void *reader) {
|
||||
redisReader *r = reader;
|
||||
return r->error;
|
||||
}
|
||||
|
||||
void redisReplyReaderFeed(void *reader, char *buf, int len) {
|
||||
@ -410,12 +397,13 @@ void redisReplyReaderFeed(void *reader, char *buf, int len) {
|
||||
r->buf = sdscatlen(r->buf,buf,len);
|
||||
}
|
||||
|
||||
void *redisReplyReaderGetReply(void *reader) {
|
||||
int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
redisReader *r = reader;
|
||||
if (reply != NULL) *reply = NULL;
|
||||
|
||||
/* When the buffer is empty, there will never be a reply. */
|
||||
if (sdslen(r->buf) == 0)
|
||||
return NULL;
|
||||
return REDIS_OK;
|
||||
|
||||
/* Create first item to process when the item list is empty. */
|
||||
if (r->rlen == 0) {
|
||||
@ -446,7 +434,7 @@ void *redisReplyReaderGetReply(void *reader) {
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->rpos == r->rlen) {
|
||||
void *reply = r->reply;
|
||||
void *aux = r->reply;
|
||||
r->reply = NULL;
|
||||
|
||||
/* Destroy the buffer when it is empty and is quite large. */
|
||||
@ -458,10 +446,15 @@ void *redisReplyReaderGetReply(void *reader) {
|
||||
|
||||
/* Set list of items to read to be empty. */
|
||||
r->rlen = r->rpos = 0;
|
||||
return reply;
|
||||
} else {
|
||||
return NULL;
|
||||
|
||||
/* Check if there actually *is* a reply. */
|
||||
if (r->error != NULL) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
if (reply != NULL) *reply = aux;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Helper function for redisCommand(). It's used to append the next argument
|
||||
@ -571,11 +564,11 @@ static int redisContextConnect(redisContext *c, const char *ip, int port) {
|
||||
}
|
||||
|
||||
if (c->fd == ANET_ERR) {
|
||||
c->error = c->fn->createError(err,strlen(err));
|
||||
c->error = sdsnew(err);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (anetTcpNoDelay(err,c->fd) == ANET_ERR) {
|
||||
c->error = c->fn->createError(err,strlen(err));
|
||||
c->error = sdsnew(err);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
@ -623,12 +616,12 @@ int redisBufferRead(redisContext *c) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
/* Set error in context */
|
||||
c->error = formatError(c->fn,
|
||||
c->error = sdscatprintf(sdsempty(),
|
||||
"Error reading from socket: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
c->error = formatError(c->fn,
|
||||
c->error = sdscatprintf(sdsempty(),
|
||||
"Server closed the connection");
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
@ -644,21 +637,34 @@ static void redisPopCallback(redisContext *c) {
|
||||
c->cpos--;
|
||||
}
|
||||
|
||||
void *redisGetReply(redisContext *c) {
|
||||
int redisGetReply(redisContext *c, void **reply) {
|
||||
redisPopCallback(c);
|
||||
return redisReplyReaderGetReply(c->reader);
|
||||
if (redisReplyReaderGetReply(c->reader,reply) == REDIS_ERR) {
|
||||
/* Copy the (protocol) error from the reader to the context. */
|
||||
c->error = sdsnew(((redisReader*)c->reader)->error);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisProcessCallbacks(redisContext *c) {
|
||||
void *reply;
|
||||
void *reply = NULL;
|
||||
redisCallback cb;
|
||||
|
||||
while ((reply = redisReplyReaderGetReply(c->reader)) != NULL) {
|
||||
do {
|
||||
cb = c->callbacks[0];
|
||||
if (cb.fn != NULL)
|
||||
cb.fn(c,reply,cb.privdata);
|
||||
redisPopCallback(c);
|
||||
}
|
||||
if (redisGetReply(c,&reply) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Fire callback when there is a reply. */
|
||||
if (reply != NULL) {
|
||||
if (cb.fn != NULL) {
|
||||
cb.fn(c,reply,cb.privdata);
|
||||
} else {
|
||||
c->fn->freeObject(reply);
|
||||
}
|
||||
}
|
||||
} while (reply != NULL);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@ -671,7 +677,7 @@ int redisBufferWrite(redisContext *c, int *done) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
/* Set error in context */
|
||||
c->error = formatError(c->fn,
|
||||
c->error = sdscatprintf(sdsempty(),
|
||||
"Error writing to socket: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@ -687,73 +693,102 @@ int redisBufferWrite(redisContext *c, int *done) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static void* redisCommandWrite(redisContext *c, redisCallback *cb, char *str, size_t len) {
|
||||
void *reply = NULL;
|
||||
static int redisCommandWriteBlock(redisContext *c, void **reply, char *str, size_t len) {
|
||||
int wdone = 0;
|
||||
void *aux = NULL;
|
||||
assert(c->flags & HIREDIS_BLOCK);
|
||||
c->obuf = sdscatlen(c->obuf,str,len);
|
||||
|
||||
/* Read reply immediately when the context is blocking. */
|
||||
if (c->flags & HIREDIS_BLOCK) {
|
||||
do { /* Write until done. */
|
||||
if (redisBufferWrite(c,&wdone) == REDIS_ERR)
|
||||
return c->error;
|
||||
} while (!wdone);
|
||||
/* Write until done. */
|
||||
do {
|
||||
if (redisBufferWrite(c,&wdone) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
} while (!wdone);
|
||||
|
||||
do { /* Read until there is a reply. */
|
||||
if (redisBufferRead(c) == REDIS_ERR)
|
||||
return c->error;
|
||||
reply = redisGetReply(c);
|
||||
} while (reply == NULL);
|
||||
} else {
|
||||
/* Make room for the callback. */
|
||||
assert(c->cpos <= c->clen);
|
||||
if (c->cpos == c->clen) {
|
||||
c->clen++;
|
||||
c->callbacks = realloc(c->callbacks,c->clen*sizeof(redisCallback));
|
||||
}
|
||||
/* Read until there is a reply. */
|
||||
do {
|
||||
if (redisBufferRead(c) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (redisGetReply(c,&aux) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
} while (aux == NULL);
|
||||
|
||||
if (cb != NULL) {
|
||||
c->callbacks[c->cpos] = *cb;
|
||||
} else {
|
||||
memset(&c->callbacks[c->cpos],0,sizeof(redisCallback));
|
||||
}
|
||||
c->cpos++;
|
||||
}
|
||||
return reply;
|
||||
/* Set reply object. */
|
||||
if (reply != NULL)
|
||||
*reply = aux;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Write a formatted command to the output buffer, and, if the context is a
|
||||
* blocking connection, read the reply and return it. When this function
|
||||
* is called from a non-blocking context, it will always return NULL. */
|
||||
static int redisCommandWriteNonBlock(redisContext *c, redisCallback *cb, char *str, size_t len) {
|
||||
assert(!(c->flags & HIREDIS_BLOCK));
|
||||
c->obuf = sdscatlen(c->obuf,str,len);
|
||||
|
||||
/* Make sure there is space for the callback. */
|
||||
assert(c->cpos <= c->clen);
|
||||
if (c->cpos == c->clen) {
|
||||
c->clen++;
|
||||
c->callbacks = realloc(c->callbacks,c->clen*sizeof(redisCallback));
|
||||
}
|
||||
|
||||
if (cb != NULL) {
|
||||
c->callbacks[c->cpos] = *cb;
|
||||
} else {
|
||||
memset(&c->callbacks[c->cpos],0,sizeof(redisCallback));
|
||||
}
|
||||
c->cpos++;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Write a formatted command to the output buffer. If the given context is
|
||||
* blocking, immediately read the reply into the "reply" pointer. When the
|
||||
* context is non-blocking, the "reply" pointer will not be used and a
|
||||
* NULL callback will be appended to the list of callbacks.
|
||||
*
|
||||
* Returns the reply when a reply was succesfully retrieved. Returns NULL
|
||||
* otherwise. When NULL is returned in a blocking context, provided that
|
||||
* the reply build functions did not return NULL when building the reply,
|
||||
* the error field in the context will be set. */
|
||||
void *redisCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
sds cmd;
|
||||
void *reply;
|
||||
void *reply = NULL;
|
||||
va_start(ap,format);
|
||||
cmd = redisFormatCommand(format,ap);
|
||||
va_end(ap);
|
||||
|
||||
reply = redisCommandWrite(c,NULL,cmd,sdslen(cmd));
|
||||
if (c->flags & HIREDIS_BLOCK) {
|
||||
if (redisCommandWriteBlock(c,&reply,cmd,sdslen(cmd)) == REDIS_OK) {
|
||||
return reply;
|
||||
}
|
||||
} else {
|
||||
redisCommandWriteNonBlock(c,NULL,cmd,sdslen(cmd));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
return reply;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Write a formatted command to the output buffer and register the provided
|
||||
* callback function and argument. When this function is called in a
|
||||
* non-blocking context, it is a no-op. Always returns NULL. */
|
||||
/* Write a formatted command to the output buffer. Registers the provided
|
||||
* callback function and argument in the callback list.
|
||||
*
|
||||
* Always returns NULL. In a non-blocking context this will never fail because
|
||||
* this function does not do any I/O. In a blocking context this function will
|
||||
* have no effect (a callback in a blocking context makes no sense). */
|
||||
void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privdata, const char *format, ...) {
|
||||
va_list ap;
|
||||
redisCallback cb = { fn, privdata };
|
||||
sds cmd;
|
||||
int status;
|
||||
redisCallback cb = { fn, privdata };
|
||||
|
||||
/* Skip if the context is blocking. */
|
||||
/* This function may only be used in a non-blocking context. */
|
||||
if (c->flags & HIREDIS_BLOCK) return NULL;
|
||||
|
||||
va_start(ap,format);
|
||||
cmd = redisFormatCommand(format,ap);
|
||||
va_end(ap);
|
||||
|
||||
redisCommandWrite(c,&cb,cmd,sdslen(cmd));
|
||||
status = redisCommandWriteNonBlock(c,&cb,cmd,sdslen(cmd));
|
||||
sdsfree(cmd);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ typedef struct redisReadTask {
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createError)(const char*, size_t);
|
||||
void *(*createString)(redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(redisReadTask*, int);
|
||||
void *(*createInteger)(redisReadTask*, long long);
|
||||
@ -85,7 +84,7 @@ typedef struct redisCallback {
|
||||
typedef struct redisContext {
|
||||
int fd;
|
||||
int flags;
|
||||
void *error; /* Error object is set when in erronous state */
|
||||
sds error; /* Error object is set when in erronous state */
|
||||
sds obuf; /* Write buffer */
|
||||
redisReplyFunctions *fn;
|
||||
void *reader;
|
||||
@ -97,15 +96,16 @@ typedef struct redisContext {
|
||||
void freeReplyObject(void *reply);
|
||||
void *redisReplyReaderCreate(redisReplyFunctions *fn);
|
||||
void *redisReplyReaderGetObject(void *reader);
|
||||
char *redisReplyReaderGetError(void *reader);
|
||||
void redisReplyReaderFree(void *ptr);
|
||||
void redisReplyReaderFeed(void *reader, char *buf, int len);
|
||||
void *redisReplyReaderGetReply(void *reader);
|
||||
int redisReplyReaderGetReply(void *reader, void **reply);
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port, redisReplyFunctions *fn);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port, redisReplyFunctions *fn);
|
||||
int redisBufferRead(redisContext *c);
|
||||
int redisBufferWrite(redisContext *c, int *done);
|
||||
void *redisGetReply(redisContext *c);
|
||||
int redisGetReply(redisContext *c, void **reply);
|
||||
int redisProcessCallbacks(redisContext *c);
|
||||
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
|
25
test.c
25
test.c
@ -25,18 +25,17 @@ static void __connect(redisContext **c) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int i, tests = 0, fails = 0;
|
||||
int i, ret, tests = 0, fails = 0;
|
||||
long long t1, t2;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
void *reader;
|
||||
char *err;
|
||||
__connect(&c);
|
||||
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
test_cond(reply->type == REDIS_ERROR &&
|
||||
strcmp(reply->reply,"Server closed the connection") == 0);
|
||||
freeReplyObject(reply);
|
||||
test_cond(redisCommand(c,"QUIT") == NULL &&
|
||||
strcmp(c->error,"Server closed the connection") == 0);
|
||||
__connect(&c); /* reconnect */
|
||||
|
||||
test("Is able to deliver commands: ");
|
||||
@ -126,10 +125,10 @@ int main(void) {
|
||||
test("Error handling in reply parser: ");
|
||||
reader = redisReplyReaderCreate(NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
reply = redisReplyReaderGetReply(reader);
|
||||
test_cond(reply->type == REDIS_ERROR &&
|
||||
strcasecmp(reply->reply,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
freeReplyObject(reply);
|
||||
ret = redisReplyReaderGetReply(reader,(void*)&reply);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
/* when the reply already contains multiple items, they must be free'd
|
||||
@ -139,10 +138,10 @@ int main(void) {
|
||||
redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
reply = redisReplyReaderGetReply(reader);
|
||||
test_cond(reply->type == REDIS_ERROR &&
|
||||
strcasecmp(reply->reply,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
freeReplyObject(reply);
|
||||
ret = redisReplyReaderGetReply(reader,(void*)&reply);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Throughput:\n");
|
||||
|
Loading…
Reference in New Issue
Block a user