]> git.saurik.com Git - redis.git/blame - src/object.c
Object approximated LRU algorithm enhanced / fixed / refactored. This is used for...
[redis.git] / src / object.c
CommitLineData
e2641e09 1#include "redis.h"
2#include <pthread.h>
673e1fb7 3#include <math.h>
e2641e09 4
5robj *createObject(int type, void *ptr) {
6 robj *o;
7
8 if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex);
9 if (listLength(server.objfreelist)) {
10 listNode *head = listFirst(server.objfreelist);
11 o = listNodeValue(head);
12 listDelNode(server.objfreelist,head);
13 if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
14 } else {
2cffe299 15 if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
e2641e09 16 o = zmalloc(sizeof(*o));
17 }
18 o->type = type;
19 o->encoding = REDIS_ENCODING_RAW;
20 o->ptr = ptr;
21 o->refcount = 1;
ef59a8bc 22 /* Set the LRU to the current lruclock (minutes resolution).
23 * We do this regardless of the fact VM is active as LRU is also
24 * used for the maxmemory directive when Redis is used as cache.
25 *
26 * Note that this code may run in the context of an I/O thread
27 * and accessing server.lruclock in theory is an error
28 * (no locks). But in practice this is safe, and even if we read
29 * garbage Redis will not fail. */
30 o->lru = server.lruclock;
31 /* The following is only needed if VM is active, but since the conditional
32 * is probably more costly than initializing the field it's better to
33 * have every field properly initialized anyway. */
34 o->storage = REDIS_VM_MEMORY;
e2641e09 35 return o;
36}
37
38robj *createStringObject(char *ptr, size_t len) {
39 return createObject(REDIS_STRING,sdsnewlen(ptr,len));
40}
41
42robj *createStringObjectFromLongLong(long long value) {
43 robj *o;
e002ec68 44 if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
45 pthread_equal(pthread_self(),server.mainthread)) {
e2641e09 46 incrRefCount(shared.integers[value]);
47 o = shared.integers[value];
48 } else {
49 if (value >= LONG_MIN && value <= LONG_MAX) {
50 o = createObject(REDIS_STRING, NULL);
51 o->encoding = REDIS_ENCODING_INT;
52 o->ptr = (void*)((long)value);
53 } else {
54 o = createObject(REDIS_STRING,sdsfromlonglong(value));
55 }
56 }
57 return o;
58}
59
60robj *dupStringObject(robj *o) {
61 redisAssert(o->encoding == REDIS_ENCODING_RAW);
62 return createStringObject(o->ptr,sdslen(o->ptr));
63}
64
65robj *createListObject(void) {
66 list *l = listCreate();
67 robj *o = createObject(REDIS_LIST,l);
68 listSetFreeMethod(l,decrRefCount);
69 o->encoding = REDIS_ENCODING_LINKEDLIST;
70 return o;
71}
72
73robj *createZiplistObject(void) {
74 unsigned char *zl = ziplistNew();
75 robj *o = createObject(REDIS_LIST,zl);
76 o->encoding = REDIS_ENCODING_ZIPLIST;
77 return o;
78}
79
80robj *createSetObject(void) {
81 dict *d = dictCreate(&setDictType,NULL);
96ffb2fe
PN
82 robj *o = createObject(REDIS_SET,d);
83 o->encoding = REDIS_ENCODING_HT;
84 return o;
85}
86
87robj *createIntsetObject(void) {
88 intset *is = intsetNew();
89 robj *o = createObject(REDIS_SET,is);
90 o->encoding = REDIS_ENCODING_INTSET;
91 return o;
e2641e09 92}
93
94robj *createHashObject(void) {
95 /* All the Hashes start as zipmaps. Will be automatically converted
96 * into hash tables if there are enough elements or big elements
97 * inside. */
98 unsigned char *zm = zipmapNew();
99 robj *o = createObject(REDIS_HASH,zm);
100 o->encoding = REDIS_ENCODING_ZIPMAP;
101 return o;
102}
103
104robj *createZsetObject(void) {
105 zset *zs = zmalloc(sizeof(*zs));
106
107 zs->dict = dictCreate(&zsetDictType,NULL);
108 zs->zsl = zslCreate();
109 return createObject(REDIS_ZSET,zs);
110}
111
112void freeStringObject(robj *o) {
113 if (o->encoding == REDIS_ENCODING_RAW) {
114 sdsfree(o->ptr);
115 }
116}
117
118void freeListObject(robj *o) {
119 switch (o->encoding) {
120 case REDIS_ENCODING_LINKEDLIST:
121 listRelease((list*) o->ptr);
122 break;
123 case REDIS_ENCODING_ZIPLIST:
124 zfree(o->ptr);
125 break;
126 default:
127 redisPanic("Unknown list encoding type");
128 }
129}
130
131void freeSetObject(robj *o) {
96ffb2fe
PN
132 switch (o->encoding) {
133 case REDIS_ENCODING_HT:
134 dictRelease((dict*) o->ptr);
135 break;
136 case REDIS_ENCODING_INTSET:
137 zfree(o->ptr);
138 break;
139 default:
140 redisPanic("Unknown set encoding type");
141 }
e2641e09 142}
143
144void freeZsetObject(robj *o) {
145 zset *zs = o->ptr;
146
147 dictRelease(zs->dict);
148 zslFree(zs->zsl);
149 zfree(zs);
150}
151
152void freeHashObject(robj *o) {
153 switch (o->encoding) {
154 case REDIS_ENCODING_HT:
155 dictRelease((dict*) o->ptr);
156 break;
157 case REDIS_ENCODING_ZIPMAP:
158 zfree(o->ptr);
159 break;
160 default:
161 redisPanic("Unknown hash encoding type");
162 break;
163 }
164}
165
166void incrRefCount(robj *o) {
167 o->refcount++;
168}
169
170void decrRefCount(void *obj) {
171 robj *o = obj;
172
173 /* Object is a swapped out value, or in the process of being loaded. */
174 if (server.vm_enabled &&
175 (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING))
176 {
177 vmpointer *vp = obj;
178 if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(o);
179 vmMarkPagesFree(vp->page,vp->usedpages);
180 server.vm_stats_swapped_objects--;
181 zfree(vp);
182 return;
183 }
184
185 if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
186 /* Object is in memory, or in the process of being swapped out.
187 *
188 * If the object is being swapped out, abort the operation on
189 * decrRefCount even if the refcount does not drop to 0: the object
190 * is referenced at least two times, as value of the key AND as
191 * job->val in the iojob. So if we don't invalidate the iojob, when it is
192 * done but the relevant key was removed in the meantime, the
193 * complete jobs handler will not find the key about the job and the
194 * assert will fail. */
195 if (server.vm_enabled && o->storage == REDIS_VM_SWAPPING)
196 vmCancelThreadedIOJob(o);
197 if (--(o->refcount) == 0) {
198 switch(o->type) {
199 case REDIS_STRING: freeStringObject(o); break;
200 case REDIS_LIST: freeListObject(o); break;
201 case REDIS_SET: freeSetObject(o); break;
202 case REDIS_ZSET: freeZsetObject(o); break;
203 case REDIS_HASH: freeHashObject(o); break;
204 default: redisPanic("Unknown object type"); break;
205 }
2f996f02 206 o->ptr = NULL; /* defensive programming. We'll see NULL in traces. */
e2641e09 207 if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex);
208 if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX ||
209 !listAddNodeHead(server.objfreelist,o))
210 zfree(o);
211 if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
212 }
213}
214
215int checkType(redisClient *c, robj *o, int type) {
216 if (o->type != type) {
217 addReply(c,shared.wrongtypeerr);
218 return 1;
219 }
220 return 0;
221}
222
223/* Try to encode a string object in order to save space */
224robj *tryObjectEncoding(robj *o) {
225 long value;
226 sds s = o->ptr;
227
228 if (o->encoding != REDIS_ENCODING_RAW)
229 return o; /* Already encoded */
230
231 /* It's not safe to encode shared objects: shared objects can be shared
232 * everywhere in the "object space" of Redis. Encoded objects can only
233 * appear as "values" (and not, for instance, as keys) */
234 if (o->refcount > 1) return o;
235
236 /* Currently we try to encode only strings */
237 redisAssert(o->type == REDIS_STRING);
238
239 /* Check if we can represent this string as a long integer */
240 if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o;
241
0e5441d8 242 /* Ok, this object can be encoded...
243 *
244 * Can I use a shared object? Only if the object is inside a given
cdbea20a 245 * range and if this is the main thread, since when VM is enabled we
0e5441d8 246 * have the constraint that I/O thread should only handle non-shared
247 * objects, in order to avoid race conditions (we don't have per-object
248 * locking). */
249 if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
250 pthread_equal(pthread_self(),server.mainthread)) {
e2641e09 251 decrRefCount(o);
252 incrRefCount(shared.integers[value]);
253 return shared.integers[value];
254 } else {
255 o->encoding = REDIS_ENCODING_INT;
256 sdsfree(o->ptr);
257 o->ptr = (void*) value;
258 return o;
259 }
260}
261
262/* Get a decoded version of an encoded object (returned as a new object).
263 * If the object is already raw-encoded just increment the ref count. */
264robj *getDecodedObject(robj *o) {
265 robj *dec;
266
267 if (o->encoding == REDIS_ENCODING_RAW) {
268 incrRefCount(o);
269 return o;
270 }
271 if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
272 char buf[32];
273
274 ll2string(buf,32,(long)o->ptr);
275 dec = createStringObject(buf,strlen(buf));
276 return dec;
277 } else {
278 redisPanic("Unknown encoding type");
279 }
280}
281
282/* Compare two string objects via strcmp() or alike.
283 * Note that the objects may be integer-encoded. In such a case we
284 * use ll2string() to get a string representation of the numbers on the stack
285 * and compare the strings, it's much faster than calling getDecodedObject().
286 *
287 * Important note: if objects are not integer encoded, but binary-safe strings,
288 * sdscmp() from sds.c will apply memcmp() so this function ca be considered
289 * binary safe. */
290int compareStringObjects(robj *a, robj *b) {
291 redisAssert(a->type == REDIS_STRING && b->type == REDIS_STRING);
292 char bufa[128], bufb[128], *astr, *bstr;
293 int bothsds = 1;
294
295 if (a == b) return 0;
296 if (a->encoding != REDIS_ENCODING_RAW) {
297 ll2string(bufa,sizeof(bufa),(long) a->ptr);
298 astr = bufa;
299 bothsds = 0;
300 } else {
301 astr = a->ptr;
302 }
303 if (b->encoding != REDIS_ENCODING_RAW) {
304 ll2string(bufb,sizeof(bufb),(long) b->ptr);
305 bstr = bufb;
306 bothsds = 0;
307 } else {
308 bstr = b->ptr;
309 }
310 return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
311}
312
313/* Equal string objects return 1 if the two objects are the same from the
314 * point of view of a string comparison, otherwise 0 is returned. Note that
315 * this function is faster then checking for (compareStringObject(a,b) == 0)
316 * because it can perform some more optimization. */
317int equalStringObjects(robj *a, robj *b) {
318 if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){
319 return a->ptr == b->ptr;
320 } else {
321 return compareStringObjects(a,b) == 0;
322 }
323}
324
325size_t stringObjectLen(robj *o) {
326 redisAssert(o->type == REDIS_STRING);
327 if (o->encoding == REDIS_ENCODING_RAW) {
328 return sdslen(o->ptr);
329 } else {
330 char buf[32];
331
332 return ll2string(buf,32,(long)o->ptr);
333 }
334}
335
336int getDoubleFromObject(robj *o, double *target) {
337 double value;
338 char *eptr;
339
340 if (o == NULL) {
341 value = 0;
342 } else {
343 redisAssert(o->type == REDIS_STRING);
344 if (o->encoding == REDIS_ENCODING_RAW) {
345 value = strtod(o->ptr, &eptr);
673e1fb7 346 if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
e2641e09 347 } else if (o->encoding == REDIS_ENCODING_INT) {
348 value = (long)o->ptr;
349 } else {
350 redisPanic("Unknown string encoding");
351 }
352 }
353
354 *target = value;
355 return REDIS_OK;
356}
357
358int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
359 double value;
360 if (getDoubleFromObject(o, &value) != REDIS_OK) {
361 if (msg != NULL) {
3ab20376 362 addReplyError(c,(char*)msg);
e2641e09 363 } else {
3ab20376 364 addReplyError(c,"value is not a double");
e2641e09 365 }
366 return REDIS_ERR;
367 }
368
369 *target = value;
370 return REDIS_OK;
371}
372
373int getLongLongFromObject(robj *o, long long *target) {
374 long long value;
375 char *eptr;
376
377 if (o == NULL) {
378 value = 0;
379 } else {
380 redisAssert(o->type == REDIS_STRING);
381 if (o->encoding == REDIS_ENCODING_RAW) {
382 value = strtoll(o->ptr, &eptr, 10);
383 if (eptr[0] != '\0') return REDIS_ERR;
c91abdcd 384 if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX))
385 return REDIS_ERR;
e2641e09 386 } else if (o->encoding == REDIS_ENCODING_INT) {
387 value = (long)o->ptr;
388 } else {
389 redisPanic("Unknown string encoding");
390 }
391 }
392
96ffb2fe 393 if (target) *target = value;
e2641e09 394 return REDIS_OK;
395}
396
397int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) {
398 long long value;
399 if (getLongLongFromObject(o, &value) != REDIS_OK) {
400 if (msg != NULL) {
3ab20376 401 addReplyError(c,(char*)msg);
e2641e09 402 } else {
3ab20376 403 addReplyError(c,"value is not an integer or out of range");
e2641e09 404 }
405 return REDIS_ERR;
406 }
407
408 *target = value;
409 return REDIS_OK;
410}
411
412int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) {
413 long long value;
414
415 if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
416 if (value < LONG_MIN || value > LONG_MAX) {
417 if (msg != NULL) {
3ab20376 418 addReplyError(c,(char*)msg);
e2641e09 419 } else {
3ab20376 420 addReplyError(c,"value is out of range");
e2641e09 421 }
422 return REDIS_ERR;
423 }
424
425 *target = value;
426 return REDIS_OK;
427}
428
429char *strEncoding(int encoding) {
430 switch(encoding) {
431 case REDIS_ENCODING_RAW: return "raw";
432 case REDIS_ENCODING_INT: return "int";
433 case REDIS_ENCODING_HT: return "hashtable";
434 case REDIS_ENCODING_ZIPMAP: return "zipmap";
435 case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
436 case REDIS_ENCODING_ZIPLIST: return "ziplist";
96ffb2fe 437 case REDIS_ENCODING_INTSET: return "intset";
e2641e09 438 default: return "unknown";
439 }
440}
ef59a8bc 441
442/* Given an object returns the min number of seconds the object was never
443 * requested, using an approximated LRU algorithm. */
444unsigned long estimateObjectIdleTime(robj *o) {
445 if (server.lruclock >= o->lru) {
446 return (server.lruclock - o->lru) * 60;
447 } else {
448 return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) * 60;
449 }
450}