+/* Redis Object implementation.
+ *
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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.
+ */
+
#include "redis.h"
-#include <pthread.h>
#include <math.h>
+#include <ctype.h>
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). */
+ o->lru = server.lruclock;
return o;
}
robj *createStringObjectFromLongLong(long long value) {
robj *o;
- if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
- pthread_equal(pthread_self(),server.mainthread)) {
+ if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
incrRefCount(shared.integers[value]);
o = shared.integers[value];
} else {
return o;
}
+/* Note: this function is defined into object.c since here it is where it
+ * belongs but it is actually designed to be used just for INCRBYFLOAT */
+robj *createStringObjectFromLongDouble(long double value) {
+ char buf[256];
+ int len;
+
+ /* We use 17 digits precision since with 128 bit floats that precision
+ * after rouding is able to represent most small decimal numbers in a way
+ * that is "non surprising" for the user (that is, most small decimal
+ * numbers will be represented in a way that when converted back into
+ * a string are exactly the same as what the user typed.) */
+ len = snprintf(buf,sizeof(buf),"%.17Lf", value);
+ /* Now remove trailing zeroes after the '.' */
+ if (strchr(buf,'.') != NULL) {
+ char *p = buf+len-1;
+ while(*p == '0') {
+ p--;
+ len--;
+ }
+ if (*p == '.') len--;
+ }
+ return createStringObject(buf,len);
+}
+
robj *dupStringObject(robj *o) {
- redisAssert(o->encoding == REDIS_ENCODING_RAW);
+ redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
return createStringObject(o->ptr,sdslen(o->ptr));
}
}
robj *createHashObject(void) {
- /* All the Hashes start as zipmaps. Will be automatically converted
- * into hash tables if there are enough elements or big elements
- * inside. */
- unsigned char *zm = zipmapNew();
- robj *o = createObject(REDIS_HASH,zm);
- o->encoding = REDIS_ENCODING_ZIPMAP;
+ unsigned char *zl = ziplistNew();
+ robj *o = createObject(REDIS_HASH, zl);
+ o->encoding = REDIS_ENCODING_ZIPLIST;
return o;
}
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) {
}
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) {
case REDIS_ENCODING_HT:
dictRelease((dict*) o->ptr);
break;
- case REDIS_ENCODING_ZIPMAP:
+ case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
break;
default:
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;
case REDIS_HASH: freeHashObject(o); break;
default: redisPanic("Unknown object type"); break;
}
- o->ptr = NULL; /* defensive programming. We'll see NULL in traces. */
- 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--;
}
}
+/* This function set the ref count to zero without freeing the object.
+ * It is useful in order to pass a new object to functions incrementing
+ * the ref count of the received object. Example:
+ *
+ * functionThatWillIncrementRefCount(resetRefCount(CreateObject(...)));
+ *
+ * Otherwise you need to resort to the less elegant pattern:
+ *
+ * *obj = createObject(...);
+ * functionThatWillIncrementRefCount(obj);
+ * decrRefCount(obj);
+ */
+robj *resetRefCount(robj *obj) {
+ obj->refcount = 0;
+ return obj;
+}
+
int checkType(redisClient *c, robj *o, int type) {
if (o->type != type) {
addReply(c,shared.wrongtypeerr);
return 0;
}
+int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
+ redisAssertWithInfo(NULL,o,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;
if (o->refcount > 1) return o;
/* Currently we try to encode only strings */
- redisAssert(o->type == REDIS_STRING);
+ redisAssertWithInfo(NULL,o,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)) {
+ * Can I use a shared object? Only if the object is inside a given range
+ *
+ * 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.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) {
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
* sdscmp() from sds.c will apply memcmp() so this function ca be considered
* binary safe. */
int compareStringObjects(robj *a, robj *b) {
- redisAssert(a->type == REDIS_STRING && b->type == REDIS_STRING);
+ redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
char bufa[128], bufb[128], *astr, *bstr;
int bothsds = 1;
}
size_t stringObjectLen(robj *o) {
- redisAssert(o->type == REDIS_STRING);
+ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) {
return sdslen(o->ptr);
} else {
if (o == NULL) {
value = 0;
} else {
- redisAssert(o->type == REDIS_STRING);
+ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) {
+ errno = 0;
value = strtod(o->ptr, &eptr);
- if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
+ if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+ errno == ERANGE || isnan(value))
+ return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;
} else {
redisPanic("Unknown string encoding");
}
}
-
*target = value;
return REDIS_OK;
}
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 valid float");
}
return REDIS_ERR;
}
+ *target = value;
+ return REDIS_OK;
+}
+
+int getLongDoubleFromObject(robj *o, long double *target) {
+ long double value;
+ char *eptr;
+ if (o == NULL) {
+ value = 0;
+ } else {
+ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
+ if (o->encoding == REDIS_ENCODING_RAW) {
+ errno = 0;
+ value = strtold(o->ptr, &eptr);
+ if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+ errno == ERANGE || isnan(value))
+ return REDIS_ERR;
+ } else if (o->encoding == REDIS_ENCODING_INT) {
+ value = (long)o->ptr;
+ } else {
+ redisPanic("Unknown string encoding");
+ }
+ }
+ *target = value;
+ return REDIS_OK;
+}
+
+int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) {
+ long double value;
+ if (getLongDoubleFromObject(o, &value) != REDIS_OK) {
+ if (msg != NULL) {
+ addReplyError(c,(char*)msg);
+ } else {
+ addReplyError(c,"value is not a valid float");
+ }
+ return REDIS_ERR;
+ }
*target = value;
return REDIS_OK;
}
if (o == NULL) {
value = 0;
} else {
- redisAssert(o->type == REDIS_STRING);
+ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) {
+ errno = 0;
value = strtoll(o->ptr, &eptr, 10);
- if (errno == ERANGE) return REDIS_ERR;
- if (eptr[0] != '\0') return REDIS_ERR;
+ if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+ errno == ERANGE)
+ return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;
} else {
redisPanic("Unknown string encoding");
}
}
-
if (target) *target = value;
return REDIS_OK;
}
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;
}
-
*target = value;
return REDIS_OK;
}
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;
}
-
*target = value;
return REDIS_OK;
}
case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable";
- 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*) dictGetVal(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 <verb> ... 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)");
+ }
+}
+