X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/cdbea20afb232a3603f988a1dc235f75d8eff981..4f948300bc26abb0aaa99343bc9f089ea98a9abe:/src/object.c diff --git a/src/object.c b/src/object.c index 58f8f44c..20e7f57a 100644 --- a/src/object.c +++ b/src/object.c @@ -1,31 +1,26 @@ #include "redis.h" #include +#include robj *createObject(int type, void *ptr) { - robj *o; - - if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex); - if (listLength(server.objfreelist)) { - listNode *head = listFirst(server.objfreelist); - o = listNodeValue(head); - listDelNode(server.objfreelist,head); - if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex); - } else { - if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex); - o = zmalloc(sizeof(*o)); - } + robj *o = zmalloc(sizeof(*o)); o->type = type; o->encoding = REDIS_ENCODING_RAW; o->ptr = ptr; o->refcount = 1; - if (server.vm_enabled) { - /* Note that this code may run in the context of an I/O thread - * and accessing server.lruclock in theory is an error - * (no locks). But in practice this is safe, and even if we read - * garbage Redis will not fail. */ - o->lru = server.lruclock; - o->storage = REDIS_VM_MEMORY; - } + + /* Set the LRU to the current lruclock (minutes resolution). + * We do this regardless of the fact VM is active as LRU is also + * used for the maxmemory directive when Redis is used as cache. + * + * Note that this code may run in the context of an I/O thread + * and accessing server.lruclock in theory is an error + * (no locks). But in practice this is safe, and even if we read + * garbage Redis will not fail. */ + o->lru = server.lruclock; + /* The following is only needed if VM is active, but since the conditional + * is probably more costly than initializing the field it's better to + * have every field properly initialized anyway. */ return o; } @@ -35,7 +30,9 @@ robj *createStringObject(char *ptr, size_t len) { robj *createStringObjectFromLongLong(long long value) { robj *o; - if (value >= 0 && value < REDIS_SHARED_INTEGERS) { + if (value >= 0 && value < REDIS_SHARED_INTEGERS && + !server.ds_enabled && + pthread_equal(pthread_self(),server.mainthread)) { incrRefCount(shared.integers[value]); o = shared.integers[value]; } else { @@ -72,7 +69,16 @@ robj *createZiplistObject(void) { robj *createSetObject(void) { dict *d = dictCreate(&setDictType,NULL); - return createObject(REDIS_SET,d); + robj *o = createObject(REDIS_SET,d); + o->encoding = REDIS_ENCODING_HT; + return o; +} + +robj *createIntsetObject(void) { + intset *is = intsetNew(); + robj *o = createObject(REDIS_SET,is); + o->encoding = REDIS_ENCODING_INTSET; + return o; } robj *createHashObject(void) { @@ -87,10 +93,20 @@ 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) { + unsigned char *zl = ziplistNew(); + robj *o = createObject(REDIS_ZSET,zl); + o->encoding = REDIS_ENCODING_ZIPLIST; + return o; } void freeStringObject(robj *o) { @@ -113,15 +129,33 @@ void freeListObject(robj *o) { } void freeSetObject(robj *o) { - dictRelease((dict*) o->ptr); + switch (o->encoding) { + case REDIS_ENCODING_HT: + dictRelease((dict*) o->ptr); + break; + case REDIS_ENCODING_INTSET: + zfree(o->ptr); + break; + default: + redisPanic("Unknown set encoding type"); + } } void freeZsetObject(robj *o) { - zset *zs = o->ptr; - - dictRelease(zs->dict); - zslFree(zs->zsl); - zfree(zs); + zset *zs; + switch (o->encoding) { + case REDIS_ENCODING_SKIPLIST: + zs = o->ptr; + dictRelease(zs->dict); + zslFree(zs->zsl); + zfree(zs); + break; + case REDIS_ENCODING_ZIPLIST: + zfree(o->ptr); + break; + default: + redisPanic("Unknown sorted set encoding"); + } } void freeHashObject(robj *o) { @@ -145,31 +179,8 @@ void incrRefCount(robj *o) { void decrRefCount(void *obj) { robj *o = obj; - /* Object is a swapped out value, or in the process of being loaded. */ - if (server.vm_enabled && - (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING)) - { - vmpointer *vp = obj; - if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(o); - vmMarkPagesFree(vp->page,vp->usedpages); - server.vm_stats_swapped_objects--; - zfree(vp); - return; - } - if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0"); - /* Object is in memory, or in the process of being swapped out. - * - * If the object is being swapped out, abort the operation on - * decrRefCount even if the refcount does not drop to 0: the object - * is referenced at least two times, as value of the key AND as - * job->val in the iojob. So if we don't invalidate the iojob, when it is - * done but the relevant key was removed in the meantime, the - * complete jobs handler will not find the key about the job and the - * assert will fail. */ - if (server.vm_enabled && o->storage == REDIS_VM_SWAPPING) - vmCancelThreadedIOJob(o); - if (--(o->refcount) == 0) { + if (o->refcount == 1) { switch(o->type) { case REDIS_STRING: freeStringObject(o); break; case REDIS_LIST: freeListObject(o); break; @@ -178,11 +189,9 @@ void decrRefCount(void *obj) { case REDIS_HASH: freeHashObject(o); break; default: redisPanic("Unknown object type"); break; } - if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex); - if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX || - !listAddNodeHead(server.objfreelist,o)) - zfree(o); - if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex); + zfree(o); + } else { + o->refcount--; } } @@ -194,6 +203,16 @@ int checkType(redisClient *c, robj *o, int type) { return 0; } +int isObjectRepresentableAsLongLong(robj *o, long long *llval) { + redisAssert(o->type == REDIS_STRING); + if (o->encoding == REDIS_ENCODING_INT) { + if (llval) *llval = (long) o->ptr; + return REDIS_OK; + } else { + return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR; + } +} + /* Try to encode a string object in order to save space */ robj *tryObjectEncoding(robj *o) { long value; @@ -211,17 +230,21 @@ robj *tryObjectEncoding(robj *o) { redisAssert(o->type == REDIS_STRING); /* Check if we can represent this string as a long integer */ - if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o; + if (!string2l(s,sdslen(s),&value)) return o; /* Ok, this object can be encoded... * * Can I use a shared object? Only if the object is inside a given - * range and if this is the main thread, since when VM is enabled we - * have the constraint that I/O thread should only handle non-shared - * objects, in order to avoid race conditions (we don't have per-object - * locking). */ - if (value >= 0 && value < REDIS_SHARED_INTEGERS && - pthread_equal(pthread_self(),server.mainthread)) { + * range and if the back end in use is in-memory. For disk store every + * object in memory used as value should be independent. + * + * Note that we also avoid using shared integers when maxmemory is used + * because every object needs to have a private LRU field for the LRU + * algorithm to work well. */ + if (!server.ds_enabled && + server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS && + pthread_equal(pthread_self(),server.mainthread)) + { decrRefCount(o); incrRefCount(shared.integers[value]); return shared.integers[value]; @@ -317,7 +340,7 @@ int getDoubleFromObject(robj *o, double *target) { redisAssert(o->type == REDIS_STRING); if (o->encoding == REDIS_ENCODING_RAW) { value = strtod(o->ptr, &eptr); - if (eptr[0] != '\0') return REDIS_ERR; + if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR; } else if (o->encoding == REDIS_ENCODING_INT) { value = (long)o->ptr; } else { @@ -333,9 +356,9 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch double value; if (getDoubleFromObject(o, &value) != REDIS_OK) { if (msg != NULL) { - addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); + addReplyError(c,(char*)msg); } else { - addReplySds(c, sdsnew("-ERR value is not a double\r\n")); + addReplyError(c,"value is not a double"); } return REDIS_ERR; } @@ -355,6 +378,8 @@ int getLongLongFromObject(robj *o, long long *target) { if (o->encoding == REDIS_ENCODING_RAW) { value = strtoll(o->ptr, &eptr, 10); if (eptr[0] != '\0') return REDIS_ERR; + if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX)) + return REDIS_ERR; } else if (o->encoding == REDIS_ENCODING_INT) { value = (long)o->ptr; } else { @@ -362,7 +387,7 @@ int getLongLongFromObject(robj *o, long long *target) { } } - *target = value; + if (target) *target = value; return REDIS_OK; } @@ -370,9 +395,9 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con long long value; if (getLongLongFromObject(o, &value) != REDIS_OK) { if (msg != NULL) { - addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); + addReplyError(c,(char*)msg); } else { - addReplySds(c, sdsnew("-ERR value is not an integer\r\n")); + addReplyError(c,"value is not an integer or out of range"); } return REDIS_ERR; } @@ -387,9 +412,9 @@ int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char * if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR; if (value < LONG_MIN || value > LONG_MAX) { if (msg != NULL) { - addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); + addReplyError(c,(char*)msg); } else { - addReplySds(c, sdsnew("-ERR value is out of range\r\n")); + addReplyError(c,"value is out of range"); } return REDIS_ERR; } @@ -406,6 +431,58 @@ char *strEncoding(int encoding) { case REDIS_ENCODING_ZIPMAP: return "zipmap"; 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"; } } + +/* Given an object returns the min number of seconds the object was never + * requested, using an approximated LRU algorithm. */ +unsigned long estimateObjectIdleTime(robj *o) { + if (server.lruclock >= o->lru) { + return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION; + } else { + return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) * + 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)"); + } +} +