int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && c->flags & REDIS_BLOCK) { /* Write until done */ do { if (redisBufferWrite(c,&wdone) == REDIS_ERR) return REDIS_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; } while (aux == NULL); } /* Set reply object */ if (reply != NULL) *reply = aux; return REDIS_OK; }
void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); int done = 0; // 连接创建失败??? if (!(c->flags & REDIS_CONNECTED)) { /* Abort connect was not successful. */ if (__redisAsyncHandleConnect(ac) != REDIS_OK) return; // 再尝试检测 flag /* Try again later when the context is still not connected. */ if (!(c->flags & REDIS_CONNECTED)) return; } if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ // 当缓冲区中还有数据待发送时,会再次注册写事件 if (!done) _EL_ADD_WRITE(ac); // 当缓冲区中没有数据待发送时,会再次注册写事件 else _EL_DEL_WRITE(ac); /* Always schedule reads after writes */ _EL_ADD_READ(ac); } }
static VALUE connection_flush(VALUE self) { redisParentContext *pc; redisContext *c; int wdone = 0; Data_Get_Struct(self,redisParentContext,pc); if (!pc->context) rb_raise(rb_eRuntimeError, "not connected"); c = pc->context; while (!wdone) { errno = 0; if (redisBufferWrite(c, &wdone) == REDIS_ERR) { /* Socket error */ parent_context_raise(pc); } if (errno == EAGAIN) { int writable = 0; if (__wait_writable(c->fd, pc->timeout, &writable) < 0) { rb_sys_fail(0); } if (!writable) { errno = EAGAIN; rb_sys_fail(0); } } } return Qnil; }
void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); int done = 0; int status; if (!(c->flags & REDIS_CONNECTED)) { /* Abort connect was not successful. */ if (__redisAsyncHandleConnect(ac) != REDIS_OK) return; /* Try again later when the context is still not connected. */ if (!(c->flags & REDIS_CONNECTED)) return; } pthread_mutex_lock(&ac->lock); status = redisBufferWrite(c,&done); pthread_mutex_unlock(&ac->lock); if (status == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ if (!done) _EL_ADD_WRITE(ac); else _EL_DEL_WRITE(ac); /* Always schedule reads after writes */ _EL_ADD_READ(ac); } }
void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); int done = 0; if (redisBufferWrite(c, &done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ if (!done) { if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); } else { if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data); } /* Always schedule reads after writes */ if (ac->ev.addRead) ac->ev.addRead(ac->ev.data); /* Fire onConnect when this is the first write event. */ if (!(c->flags & REDIS_CONNECTED)) { c->flags |= REDIS_CONNECTED; if (ac->onConnect) ac->onConnect(ac); } } }
static int __get_reply(redisParentContext *pc, VALUE *reply) { redisContext *c = pc->context; int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) { /* Protocol error */ return -1; } if (aux == NULL) { /* Write until the write buffer is drained */ while (!wdone) { errno = 0; if (redisBufferWrite(c, &wdone) == REDIS_ERR) { /* Socket error */ return -1; } if (errno == EAGAIN) { int writable = 0; if (__wait_writable(c->fd, pc->timeout, &writable) < 0) { rb_sys_fail(0); } if (!writable) { errno = EAGAIN; rb_sys_fail(0); } } } /* Read until there is a full reply */ while (aux == NULL) { errno = 0; if (redisBufferRead(c) == REDIS_ERR) { /* Socket error */ return -1; } if (errno == EAGAIN) { int readable = 0; if (__wait_readable(c->fd, pc->timeout, &readable) < 0) { rb_sys_fail(0); } if (!readable) { errno = EAGAIN; rb_sys_fail(0); } /* Retry */ continue; } if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) { /* Protocol error */ return -1; } } } /* Set reply object */ if (reply != NULL) { *reply = (VALUE)aux; } return 0; }
/* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * * After this function is called, you may use redisContextReadReply to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; int nread; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; #ifndef HIREDIS_WIN nread = read(c->fd,buf,sizeof(buf)); if (nread == -1) { if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) { /* Try again later */ #else nread = recv(c->fd,buf,sizeof(buf),0); if (nread == -1) { errno = WSAGetLastError(); if ((errno == WSAEINPROGRESS || errno == WSAEWOULDBLOCK) && !(c->flags & REDIS_BLOCK)) { /* Try again later */ #endif } else { __redisSetError(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } } else if (nread == 0) { __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); return REDIS_ERR; } else { if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } } return REDIS_OK; } /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * succesfully written to the socket. When the buffer is empty after the * write operation, "done" is set to 1 (if given). * * Returns REDIS_ERR if an error occured trying to write and sets * c->errstr to hold the appropriate error string. */ int redisBufferWrite(redisContext *c, int *done) { int nwritten; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (sdslen(c->obuf) > 0) { #ifndef HIREDIS_WIN nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); if (nwritten == -1) { if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) { /* Try again later */ #else nwritten = send(c->fd,c->obuf,sdslen(c->obuf),0); if (nwritten == -1) { errno = WSAGetLastError(); if ((errno == WSAEINPROGRESS || errno == WSAEWOULDBLOCK) && !(c->flags & REDIS_BLOCK)) { /* Try again later */ #endif } else { __redisSetError(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } } else if (nwritten > 0) { if (nwritten == (signed)sdslen(c->obuf)) { sdsfree(c->obuf); c->obuf = sdsempty(); } else { c->obuf = sdsrange(c->obuf,nwritten,-1); } } } if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDIS_OK; } /* Internal helper function to try and get a reply from the reader, * or set an error in the context otherwise. */ int redisGetReplyFromReader(redisContext *c, void **reply) { if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && c->flags & REDIS_BLOCK) { /* Write until done */ do { if (redisBufferWrite(c,&wdone) == REDIS_ERR) return REDIS_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; } while (aux == NULL); } /* Set reply object */ if (reply != NULL) *reply = aux; return REDIS_OK; } /* Helper function for the redisAppendCommand* family of functions. * * Write a formatted command to the output buffer. When this family * is used, you need to call redisGetReply yourself to retrieve * the reply (or replies in pub/sub). */ int __redisAppendCommand(redisContext *c, char *cmd, size_t len) { sds newbuf; newbuf = sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } c->obuf = newbuf; return REDIS_OK; }
static void test_nonblocking_connection() { redisContext *c; int wdone = 0; test("Calls command callback when command is issued: "); c = __connect_nonblock(); redisSetCommandCallback(c,__test_callback,(void*)1); redisCommand(c,"PING"); test_cond(__test_callback_flags == 1); redisFree(c); test("Calls disconnect callback on redisDisconnect: "); c = __connect_nonblock(); redisSetDisconnectCallback(c,__test_callback,(void*)2); redisDisconnect(c); test_cond(__test_callback_flags == 2); redisFree(c); test("Calls disconnect callback and free callback on redisFree: "); c = __connect_nonblock(); redisSetDisconnectCallback(c,__test_callback,(void*)2); redisSetFreeCallback(c,__test_callback,(void*)4); redisFree(c); test_cond(__test_callback_flags == ((2 << 8) | 4)); test("redisBufferWrite against empty write buffer: "); c = __connect_nonblock(); test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); redisFree(c); test("redisBufferWrite against not yet connected fd: "); c = __connect_nonblock(); redisCommand(c,"PING"); test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && strncmp(c->error,"write:",6) == 0); redisFree(c); test("redisBufferWrite against closed fd: "); c = __connect_nonblock(); redisCommand(c,"PING"); redisDisconnect(c); test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && strncmp(c->error,"write:",6) == 0); redisFree(c); test("Process callbacks in the right sequence: "); c = __connect_nonblock(); redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); /* Write output buffer */ wdone = 0; while(!wdone) { usleep(500); redisBufferWrite(c,&wdone); } /* Read until at least one callback is executed (the 3 replies will * arrive in a single packet, causing all callbacks to be executed in * a single pass). */ while(__test_callback_flags == 0) { assert(redisBufferRead(c) == REDIS_OK); redisProcessCallbacks(c); } test_cond(__test_callback_flags == 0x010203); redisFree(c); test("redisDisconnect executes pending callbacks with NULL reply: "); c = __connect_nonblock(); redisSetDisconnectCallback(c,__test_callback,(void*)1); redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); redisDisconnect(c); test_cond(__test_callback_flags == 0x0201); redisFree(c); }
/* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * * After this function is called, you may use redisContextReadReply to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; int nread; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; #ifdef WIN32 nread = recv(c->fd,buf,sizeof(buf),0); #else nread = read(c->fd,buf,sizeof(buf)); #endif if (nread == -1) { #ifdef WIN32 errno = WSAGetLastError(); if ((errno == WSAEWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == WSAEINTR)) { #else if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { #endif /* Try again later */ } else { __redisSetError(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } } else if (nread == 0) { __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); return REDIS_ERR; } else { if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } } return REDIS_OK; } /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * succesfully written to the socket. When the buffer is empty after the * write operation, "done" is set to 1 (if given). * * Returns REDIS_ERR if an error occured trying to write and sets * c->errstr to hold the appropriate error string. */ int redisBufferWrite(redisContext *c, int *done) { int nwritten; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (sdslen(c->obuf) > 0) { #ifdef WIN32 nwritten = send(c->fd,c->obuf,sdslen(c->obuf),0); #else nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); #endif if (nwritten == -1) { #ifdef WIN32 errno = WSAGetLastError(); if ((errno == WSAEWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == WSAEINTR)) { #else if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { #endif /* Try again later */ } else { __redisSetError(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } } else if (nwritten > 0) { if (nwritten == (signed)sdslen(c->obuf)) { sdsfree(c->obuf); c->obuf = sdsempty(); } else { c->obuf = sdsrange(c->obuf,nwritten,-1); } } } if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDIS_OK; } /* Internal helper function to try and get a reply from the reader, * or set an error in the context otherwise. */ int redisGetReplyFromReader(redisContext *c, void **reply) { if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && c->flags & REDIS_BLOCK) { /* Write until done */ do { if (redisBufferWrite(c,&wdone) == REDIS_ERR) return REDIS_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; } while (aux == NULL); } /* Set reply object */ if (reply != NULL) *reply = aux; return REDIS_OK; }