diff --git a/00-RELEASENOTES b/00-RELEASENOTES index 8e04f760..1bc6c915 100644 --- a/00-RELEASENOTES +++ b/00-RELEASENOTES @@ -12,6 +12,21 @@ for 2.0. CHANGELOG --------- +What's new in Redis 2.2.4 +========================= + +* Return value of OBJECT DEBUG against sorted sets fixed, now is "skiplist". + +What's new in Redis 2.2.3 +========================= + +* Fixed issue #503. MONITOR + QUIT (and other combinations) could crash + the server. +* OBJECT command implemented. See http://redis.io/commands/object +* Fixed a problem in redis-cli related to escapes in the form "\x..". +* Fixed a minor memory leak in redis-cli +* Saved RDB on SIGTERM on archs where it was not working properly. + What's new in Redis 2.2.2 ========================= diff --git a/src/networking.c b/src/networking.c index 758344a5..510ed82a 100644 --- a/src/networking.c +++ b/src/networking.c @@ -60,9 +60,6 @@ redisClient *createClient(int fd) { /* Set the event loop to listen for write events on the client's socket. * Typically gets called every time a reply is built. */ int _installWriteEvent(redisClient *c) { - /* When CLOSE_AFTER_REPLY is set, no more replies may be added! */ - redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY)); - if (c->fd <= 0) return REDIS_ERR; if (c->bufpos == 0 && listLength(c->reply) == 0 && (c->replstate == REDIS_REPL_NONE || @@ -88,9 +85,15 @@ robj *dupLastObjectIfNeeded(list *reply) { return listNodeValue(ln); } +/* ----------------------------------------------------------------------------- + * Low level functions to add more data to output buffers. + * -------------------------------------------------------------------------- */ + int _addReplyToBuffer(redisClient *c, char *s, size_t len) { size_t available = sizeof(c->buf)-c->bufpos; + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK; + /* If there already are entries in the reply list, we cannot * add anything more to the static buffer. */ if (listLength(c->reply) > 0) return REDIS_ERR; @@ -105,6 +108,9 @@ int _addReplyToBuffer(redisClient *c, char *s, size_t len) { void _addReplyObjectToList(redisClient *c, robj *o) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + if (listLength(c->reply) == 0) { incrRefCount(o); listAddNodeTail(c->reply,o); @@ -128,6 +134,12 @@ void _addReplyObjectToList(redisClient *c, robj *o) { * needed it will be free'd, otherwise it ends up in a robj. */ void _addReplySdsToList(redisClient *c, sds s) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) { + sdsfree(s); + return; + } + if (listLength(c->reply) == 0) { listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); } else { @@ -148,6 +160,9 @@ void _addReplySdsToList(redisClient *c, sds s) { void _addReplyStringToList(redisClient *c, char *s, size_t len) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + if (listLength(c->reply) == 0) { listAddNodeTail(c->reply,createStringObject(s,len)); } else { @@ -165,6 +180,11 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) { } } +/* ----------------------------------------------------------------------------- + * Higher level functions to queue data on the client output buffer. + * The following functions are the ones that commands implementations will call. + * -------------------------------------------------------------------------- */ + void addReply(redisClient *c, robj *obj) { if (_installWriteEvent(c) != REDIS_OK) return; redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY); diff --git a/src/object.c b/src/object.c index a47b8852..923e603b 100644 --- a/src/object.c +++ b/src/object.c @@ -93,9 +93,13 @@ robj *createHashObject(void) { robj *createZsetObject(void) { zset *zs = zmalloc(sizeof(*zs)); + robj *o; + zs->dict = dictCreate(&zsetDictType,NULL); zs->zsl = zslCreate(); - return createObject(REDIS_ZSET,zs); + o = createObject(REDIS_ZSET,zs); + o->encoding = REDIS_ENCODING_SKIPLIST; + return o; } robj *createZsetZiplistObject(void) { @@ -440,6 +444,7 @@ char *strEncoding(int encoding) { case REDIS_ENCODING_LINKEDLIST: return "linkedlist"; case REDIS_ENCODING_ZIPLIST: return "ziplist"; case REDIS_ENCODING_INTSET: return "intset"; + case REDIS_ENCODING_SKIPLIST: return "skiplist"; default: return "unknown"; } } @@ -454,3 +459,42 @@ unsigned long estimateObjectIdleTime(robj *o) { REDIS_LRU_CLOCK_RESOLUTION; } } + +/* This is an helper function for the DEBUG command. We need to lookup keys + * without any modification of LRU or other parameters. */ +robj *objectCommandLookup(redisClient *c, robj *key) { + dictEntry *de; + + if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL; + return (robj*) dictGetEntryVal(de); +} + +robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) { + robj *o = objectCommandLookup(c,key); + + if (!o) addReply(c, reply); + return o; +} + +/* Object command allows to inspect the internals of an Redis Object. + * Usage: OBJECT ... arguments ... */ +void objectCommand(redisClient *c) { + robj *o; + + if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) { + if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) + == NULL) return; + addReplyLongLong(c,o->refcount); + } else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) { + if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) + == NULL) return; + addReplyBulkCString(c,strEncoding(o->encoding)); + } else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) { + if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) + == NULL) return; + addReplyLongLong(c,estimateObjectIdleTime(o)); + } else { + addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)"); + } +} + diff --git a/src/redis.c b/src/redis.c index 1464ab0f..5223bae5 100644 --- a/src/redis.c +++ b/src/redis.c @@ -187,7 +187,8 @@ struct redisCommand readonlyCommandTable[] = { {"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0}, {"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, {"watch",watchCommand,-2,0,NULL,0,0,0}, - {"unwatch",unwatchCommand,1,0,NULL,0,0,0} + {"unwatch",unwatchCommand,1,0,NULL,0,0,0}, + {"object",objectCommand,-2,0,NULL,0,0,0} }; /*============================ Utility functions ============================ */ diff --git a/src/redis.h b/src/redis.h index fb5d7573..5940bb90 100644 --- a/src/redis.h +++ b/src/redis.h @@ -81,6 +81,7 @@ #define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ #define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define REDIS_ENCODING_INTSET 6 /* Encoded as intset */ +#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ /* Object types only used for dumping to disk */ #define REDIS_EXPIRETIME 253 @@ -1021,6 +1022,7 @@ void punsubscribeCommand(redisClient *c); void publishCommand(redisClient *c); void watchCommand(redisClient *c); void unwatchCommand(redisClient *c); +void objectCommand(redisClient *c); #if defined(__GNUC__) void *calloc(size_t count, size_t size) __attribute__ ((deprecated)); diff --git a/src/sds.c b/src/sds.c index da049f6c..6aa8ff57 100644 --- a/src/sds.c +++ b/src/sds.c @@ -26,6 +26,12 @@ * 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. + * + * History: + * + * - 22 March 2011: History section created on top of sds.c + * - 22 March 2011: Fixed a problem with "\xab" escapes convertion in + * function sdssplitargs(). */ #define SDS_ABORT_ON_OOM @@ -412,6 +418,37 @@ sds sdscatrepr(sds s, char *p, size_t len) { return sdscatlen(s,"\"",1); } +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts an hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + /* Split a line into arguments, where every argument can be in the * following programming-language REPL-alike form: * @@ -441,7 +478,17 @@ sds *sdssplitargs(char *line, int *argc) { if (current == NULL) current = sdsempty(); while(!done) { if (inq) { - if (*p == '\\' && *(p+1)) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { char c; p++; diff --git a/src/version.h b/src/version.h index 0834bc82..6ae276d8 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define REDIS_VERSION "2.2.2" +#define REDIS_VERSION "2.2.4"