X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/6e9e463f8014cbde1eaabe4c59045471e1871b74..a737ada1e58e6cd75e2fc643e2e55778de17b2ef:/redis.c diff --git a/redis.c b/redis.c index 773b3657..78f5fac0 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "1.3.8" +#define REDIS_VERSION "1.3.10" #include "fmacros.h" #include "config.h" @@ -237,7 +237,9 @@ static char* strencoding[] = { /* We can print the stacktrace, so our assert is defined this way: */ #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) +#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1) static void _redisAssert(char *estr, char *file, int line); +static void _redisPanic(char *msg, char *file, int line); /*================================= Data types ============================== */ @@ -377,7 +379,6 @@ struct redisServer { char *dbfilename; char *appendfilename; char *requirepass; - int shareobjects; int rdbcompression; int activerehashing; /* Replication related */ @@ -514,8 +515,9 @@ struct sharedObjectsStruct { *outofrangeerr, *plus, *select0, *select1, *select2, *select3, *select4, *select5, *select6, *select7, *select8, *select9, - *messagebulk, *subscribebulk, *unsubscribebulk, *mbulk3, - *psubscribebulk, *punsubscribebulk, *integers[REDIS_SHARED_INTEGERS]; + *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, *mbulk3, + *mbulk4, *psubscribebulk, *punsubscribebulk, + *integers[REDIS_SHARED_INTEGERS]; } shared; /* Global vars that are actally used as constants. The following double @@ -556,6 +558,7 @@ static int rdbSaveBackground(char *filename); static robj *createStringObject(char *ptr, size_t len); static robj *dupStringObject(robj *o); static void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); +static void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int argc); static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); static int syncWithMaster(void); static robj *tryObjectEncoding(robj *o); @@ -620,12 +623,14 @@ static void freePubsubPattern(void *p); static int listMatchPubsubPattern(void *a, void *b); static int compareStringObjects(robj *a, robj *b); static void usage(); +static int rewriteAppendOnlyFileBackground(void); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); static void echoCommand(redisClient *c); static void setCommand(redisClient *c); static void setnxCommand(redisClient *c); +static void setexCommand(redisClient *c); static void getCommand(redisClient *c); static void delCommand(redisClient *c); static void existsCommand(redisClient *c); @@ -734,6 +739,7 @@ static struct redisCommand cmdTable[] = { {"get",getCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, {"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, @@ -968,6 +974,53 @@ static int stringmatch(const char *pattern, const char *string, int nocase) { return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); } +/* Convert a string representing an amount of memory into the number of + * bytes, so for instance memtoll("1Gi") will return 1073741824 that is + * (1024*1024*1024). + * + * On parsing error, if *err is not NULL, it's set to 1, otherwise it's + * set to 0 */ +static long long memtoll(const char *p, int *err) { + const char *u; + char buf[128]; + long mul; /* unit multiplier */ + long long val; + unsigned int digits; + + if (err) *err = 0; + /* Search the first non digit character. */ + u = p; + if (*u == '-') u++; + while(*u && isdigit(*u)) u++; + if (*u == '\0' || !strcasecmp(u,"b")) { + mul = 1; + } else if (!strcasecmp(u,"k")) { + mul = 1000; + } else if (!strcasecmp(u,"kb")) { + mul = 1024; + } else if (!strcasecmp(u,"m")) { + mul = 1000*1000; + } else if (!strcasecmp(u,"mb")) { + mul = 1024*1024; + } else if (!strcasecmp(u,"g")) { + mul = 1000L*1000*1000; + } else if (!strcasecmp(u,"gb")) { + mul = 1024L*1024*1024; + } else { + if (err) *err = 1; + mul = 1; + } + digits = u-p; + if (digits >= sizeof(buf)) { + if (err) *err = 1; + return LLONG_MAX; + } + memcpy(buf,p,digits); + buf[digits] = '\0'; + val = strtoll(buf,NULL,10); + return val*mul; +} + static void redisLog(int level, const char *fmt, ...) { va_list ap; FILE *fp; @@ -1465,6 +1518,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD redisLog(REDIS_NOTICE,"Connecting to MASTER..."); if (syncWithMaster() == REDIS_OK) { redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync succeeded"); + if (server.appendonly) rewriteAppendOnlyFileBackground(); } } return 100; @@ -1540,11 +1594,13 @@ static void createSharedObjects(void) { shared.select8 = createStringObject("select 8\r\n",10); shared.select9 = createStringObject("select 9\r\n",10); shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); + shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14); shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18); shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17); shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19); shared.mbulk3 = createStringObject("*3\r\n",4); + shared.mbulk4 = createStringObject("*4\r\n",4); for (j = 0; j < REDIS_SHARED_INTEGERS; j++) { shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j); shared.integers[j]->encoding = REDIS_ENCODING_INT; @@ -1583,7 +1639,6 @@ static void initServerConfig() { server.dbfilename = zstrdup("dump.rdb"); server.appendfilename = zstrdup("appendonly.aof"); server.requirepass = NULL; - server.shareobjects = 0; server.rdbcompression = 1; server.activerehashing = 1; server.maxclients = 0; @@ -1800,7 +1855,7 @@ static void loadServerConfig(char *filename) { } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { server.maxclients = atoi(argv[1]); } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { - server.maxmemory = strtoll(argv[1], NULL, 10); + server.maxmemory = memtoll(argv[1],NULL); } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) { server.masterhost = sdsnew(argv[1]); server.masterport = atoi(argv[2]); @@ -1811,10 +1866,6 @@ static void loadServerConfig(char *filename) { if ((server.glueoutputbuf = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; } - } else if (!strcasecmp(argv[0],"shareobjects") && argc == 2) { - if ((server.shareobjects = yesnotoi(argv[1])) == -1) { - err = "argument must be 'yes' or 'no'"; goto loaderr; - } } else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) { if ((server.rdbcompression = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; @@ -1858,19 +1909,17 @@ static void loadServerConfig(char *filename) { zfree(server.vm_swap_file); server.vm_swap_file = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"vm-max-memory") && argc == 2) { - server.vm_max_memory = strtoll(argv[1], NULL, 10); + server.vm_max_memory = memtoll(argv[1],NULL); } else if (!strcasecmp(argv[0],"vm-page-size") && argc == 2) { - server.vm_page_size = strtoll(argv[1], NULL, 10); + server.vm_page_size = memtoll(argv[1], NULL); } else if (!strcasecmp(argv[0],"vm-pages") && argc == 2) { - server.vm_pages = strtoll(argv[1], NULL, 10); + server.vm_pages = memtoll(argv[1], NULL); } else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) { server.vm_max_threads = strtoll(argv[1], NULL, 10); } else if (!strcasecmp(argv[0],"hash-max-zipmap-entries") && argc == 2){ - server.hash_max_zipmap_entries = strtol(argv[1], NULL, 10); + server.hash_max_zipmap_entries = memtoll(argv[1], NULL); } else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2){ - server.hash_max_zipmap_value = strtol(argv[1], NULL, 10); - } else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) { - server.vm_max_threads = strtoll(argv[1], NULL, 10); + server.hash_max_zipmap_value = memtoll(argv[1], NULL); } else { err = "Bad directive or wrong number of arguments"; goto loaderr; } @@ -2164,7 +2213,7 @@ static void call(redisClient *c, struct redisCommand *cmd) { listLength(server.slaves)) replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc); if (listLength(server.monitors)) - replicationFeedSlaves(server.monitors,c->db->id,c->argv,c->argc); + replicationFeedMonitors(server.monitors,c->db->id,c->argv,c->argc); server.stat_numcommands++; } @@ -2419,6 +2468,64 @@ static void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int arg if (outv != static_outv) zfree(outv); } +static sds sdscatrepr(sds s, char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",1); break; + case '\r': s = sdscatlen(s,"\\r",1); break; + case '\t': s = sdscatlen(s,"\\t",1); break; + case '\a': s = sdscatlen(s,"\\a",1); break; + case '\b': s = sdscatlen(s,"\\b",1); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +static void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int argc) { + listNode *ln; + listIter li; + int j; + sds cmdrepr = sdsnew("+"); + robj *cmdobj; + struct timeval tv; + + gettimeofday(&tv,NULL); + cmdrepr = sdscatprintf(cmdrepr,"%ld.%ld ",(long)tv.tv_sec,(long)tv.tv_usec); + if (dictid != 0) cmdrepr = sdscatprintf(cmdrepr,"(db %d) ", dictid); + + for (j = 0; j < argc; j++) { + if (argv[j]->encoding == REDIS_ENCODING_INT) { + cmdrepr = sdscatprintf(cmdrepr, "%ld", (long)argv[j]->ptr); + } else { + cmdrepr = sdscatrepr(cmdrepr,(char*)argv[j]->ptr, + sdslen(argv[j]->ptr)); + } + if (j != argc-1) + cmdrepr = sdscatlen(cmdrepr," ",1); + } + cmdrepr = sdscatlen(cmdrepr,"\r\n",2); + cmdobj = createObject(REDIS_STRING,cmdrepr); + + listRewind(monitors,&li); + while((ln = listNext(&li))) { + redisClient *monitor = ln->value; + addReply(monitor,cmdobj); + } + decrRefCount(cmdobj); +} + static void processInputBuffer(redisClient *c) { again: /* Before to process the input buffer, make sure the client is not @@ -2773,6 +2880,23 @@ static robj *createStringObject(char *ptr, size_t len) { return createObject(REDIS_STRING,sdsnewlen(ptr,len)); } +static robj *createStringObjectFromLongLong(long long value) { + robj *o; + if (value >= 0 && value < REDIS_SHARED_INTEGERS) { + incrRefCount(shared.integers[value]); + o = shared.integers[value]; + } else { + o = createObject(REDIS_STRING, NULL); + if (value >= LONG_MIN && value <= LONG_MAX) { + o->encoding = REDIS_ENCODING_INT; + o->ptr = (void*)((long)value); + } else { + o->ptr = sdscatprintf(sdsempty(),"%lld",value); + } + } + return o; +} + static robj *dupStringObject(robj *o) { assert(o->encoding == REDIS_ENCODING_RAW); return createStringObject(o->ptr,sdslen(o->ptr)); @@ -2839,7 +2963,7 @@ static void freeHashObject(robj *o) { zfree(o->ptr); break; default: - redisAssert(0); + redisPanic("Unknown hash encoding type"); break; } } @@ -2851,6 +2975,7 @@ static void incrRefCount(robj *o) { static void decrRefCount(void *obj) { robj *o = obj; + if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0"); /* Object is a key of a swapped out value, or in the process of being * loaded. */ if (server.vm_enabled && @@ -2878,7 +3003,7 @@ static void decrRefCount(void *obj) { case REDIS_SET: freeSetObject(o); break; case REDIS_ZSET: freeZsetObject(o); break; case REDIS_HASH: freeHashObject(o); break; - default: redisAssert(0); 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 || @@ -3038,7 +3163,7 @@ static robj *getDecodedObject(robj *o) { dec = createStringObject(buf,strlen(buf)); return dec; } else { - redisAssert(1 != 1); + redisPanic("Unknown encoding type"); } } @@ -3084,74 +3209,94 @@ static size_t stringObjectLen(robj *o) { } } -static int getDoubleFromObject(redisClient *c, robj *o, double *value) { - double parsedValue; - char *eptr = NULL; +static int getDoubleFromObject(robj *o, double *target) { + double value; + char *eptr; - if (o && o->type != REDIS_STRING) { - addReplySds(c,sdsnew("-ERR value is not a double\r\n")); - return REDIS_ERR; + if (o == NULL) { + value = 0; + } else { + redisAssert(o->type == REDIS_STRING); + if (o->encoding == REDIS_ENCODING_RAW) { + value = strtod(o->ptr, &eptr); + if (eptr[0] != '\0') return REDIS_ERR; + } else if (o->encoding == REDIS_ENCODING_INT) { + value = (long)o->ptr; + } else { + redisAssert(1 != 1); + } } - if (o == NULL) - parsedValue = 0; - else if (o->encoding == REDIS_ENCODING_RAW) - parsedValue = strtod(o->ptr, &eptr); - else if (o->encoding == REDIS_ENCODING_INT) - parsedValue = (long)o->ptr; - else - redisAssert(1 != 1); + *target = value; + return REDIS_OK; +} - if (eptr != NULL && *eptr != '\0') { - addReplySds(c,sdsnew("-ERR value is not a double\r\n")); +static int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) { + double value; + if (getDoubleFromObject(o, &value) != REDIS_OK) { + if (msg != NULL) { + addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); + } else { + addReplySds(c, sdsnew("-ERR value is not a double\r\n")); + } return REDIS_ERR; } - *value = parsedValue; - + *target = value; return REDIS_OK; } -static int getLongLongFromObject(redisClient *c, robj *o, long long *value) { - long long parsedValue; - char *eptr = NULL; +static int getLongLongFromObject(robj *o, long long *target) { + long long value; + char *eptr; - if (o && o->type != REDIS_STRING) { - addReplySds(c,sdsnew("-ERR value is not an integer\r\n")); - return REDIS_ERR; + if (o == NULL) { + value = 0; + } else { + redisAssert(o->type == REDIS_STRING); + if (o->encoding == REDIS_ENCODING_RAW) { + value = strtoll(o->ptr, &eptr, 10); + if (eptr[0] != '\0') return REDIS_ERR; + } else if (o->encoding == REDIS_ENCODING_INT) { + value = (long)o->ptr; + } else { + redisAssert(1 != 1); + } } - if (o == NULL) - parsedValue = 0; - else if (o->encoding == REDIS_ENCODING_RAW) - parsedValue = strtoll(o->ptr, &eptr, 10); - else if (o->encoding == REDIS_ENCODING_INT) - parsedValue = (long)o->ptr; - else - redisAssert(1 != 1); + *target = value; + return REDIS_OK; +} - if (eptr != NULL && *eptr != '\0') { - addReplySds(c,sdsnew("-ERR value is not an integer\r\n")); +static int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) { + long long value; + if (getLongLongFromObject(o, &value) != REDIS_OK) { + if (msg != NULL) { + addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); + } else { + addReplySds(c, sdsnew("-ERR value is not an integer\r\n")); + } return REDIS_ERR; } - *value = parsedValue; - + *target = value; return REDIS_OK; } -static int getLongFromObject(redisClient *c, robj *o, long *value) { - long long actualValue; - - if (getLongLongFromObject(c, o, &actualValue) != REDIS_OK) return REDIS_ERR; +static int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) { + long long value; - if (actualValue < LONG_MIN || actualValue > LONG_MAX) { - addReplySds(c,sdsnew("-ERR value is out of range\r\n")); + 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)); + } else { + addReplySds(c, sdsnew("-ERR value is out of range\r\n")); + } return REDIS_ERR; } - *value = actualValue; - + *target = value; return REDIS_OK; } @@ -3408,7 +3553,7 @@ static int rdbSaveObject(FILE *fp, robj *o) { dictReleaseIterator(di); } } else { - redisAssert(0); + redisPanic("Unknown object type"); } return 0; } @@ -3629,7 +3774,7 @@ static robj *rdbLoadIntegerObject(FILE *fp, int enctype) { val = (int32_t)v; } else { val = 0; /* anti-warning */ - redisAssert(0); + redisPanic("Unknown RDB integer encoding type"); } return createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",val)); } @@ -3668,7 +3813,7 @@ static robj *rdbLoadStringObject(FILE*fp) { case REDIS_RDB_ENC_LZF: return rdbLoadLzfStringObject(fp); default: - redisAssert(0); + redisPanic("Unknown RDB encoding type"); } } @@ -3790,7 +3935,7 @@ static robj *rdbLoadObject(int type, FILE *fp) { } } } else { - redisAssert(0); + redisPanic("Unknown object type"); } return o; } @@ -3902,40 +4047,55 @@ static void echoCommand(redisClient *c) { /*=================================== Strings =============================== */ -static void setGenericCommand(redisClient *c, int nx) { +static void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) { int retval; + long seconds; - if (nx) deleteIfVolatile(c->db,c->argv[1]); - retval = dictAdd(c->db->dict,c->argv[1],c->argv[2]); + if (expire) { + if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK) + return; + if (seconds <= 0) { + addReplySds(c,sdsnew("-ERR invalid expire time in SETEX\r\n")); + return; + } + } + + if (nx) deleteIfVolatile(c->db,key); + retval = dictAdd(c->db->dict,key,val); if (retval == DICT_ERR) { if (!nx) { /* If the key is about a swapped value, we want a new key object * to overwrite the old. So we delete the old key in the database. * This will also make sure that swap pages about the old object * will be marked as free. */ - if (server.vm_enabled && deleteIfSwapped(c->db,c->argv[1])) - incrRefCount(c->argv[1]); - dictReplace(c->db->dict,c->argv[1],c->argv[2]); - incrRefCount(c->argv[2]); + if (server.vm_enabled && deleteIfSwapped(c->db,key)) + incrRefCount(key); + dictReplace(c->db->dict,key,val); + incrRefCount(val); } else { addReply(c,shared.czero); return; } } else { - incrRefCount(c->argv[1]); - incrRefCount(c->argv[2]); + incrRefCount(key); + incrRefCount(val); } server.dirty++; - removeExpire(c->db,c->argv[1]); + removeExpire(c->db,key); + if (expire) setExpire(c->db,key,time(NULL)+seconds); addReply(c, nx ? shared.cone : shared.ok); } static void setCommand(redisClient *c) { - setGenericCommand(c,0); + setGenericCommand(c,0,c->argv[1],c->argv[2],NULL); } static void setnxCommand(redisClient *c) { - setGenericCommand(c,1); + setGenericCommand(c,1,c->argv[1],c->argv[2],NULL); +} + +static void setexCommand(redisClient *c) { + setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]); } static int getGenericCommand(redisClient *c) { @@ -4041,7 +4201,7 @@ static void incrDecrCommand(redisClient *c, long long incr) { o = lookupKeyWrite(c->db,c->argv[1]); - if (getLongLongFromObject(c, o, &value) != REDIS_OK) return; + if (getLongLongFromObjectOrReply(c, o, &value, NULL) != REDIS_OK) return; value += incr; o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); @@ -4070,16 +4230,14 @@ static void decrCommand(redisClient *c) { static void incrbyCommand(redisClient *c) { long long incr; - if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return; - + if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; incrDecrCommand(c,incr); } static void decrbyCommand(redisClient *c) { long long incr; - if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return; - + if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; incrDecrCommand(c,-incr); } @@ -4196,18 +4354,25 @@ static void selectCommand(redisClient *c) { static void randomkeyCommand(redisClient *c) { dictEntry *de; + robj *key; while(1) { de = dictGetRandomKey(c->db->dict); if (!de || expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break; } + if (de == NULL) { - addReply(c,shared.plus); - addReply(c,shared.crlf); + addReply(c,shared.nullbulk); + return; + } + + key = dictGetEntryKey(de); + if (server.vm_enabled) { + key = dupStringObject(key); + addReplyBulk(c,key); + decrRefCount(key); } else { - addReply(c,shared.plus); - addReply(c,dictGetEntryKey(de)); - addReply(c,shared.crlf); + addReplyBulk(c,key); } } @@ -5477,16 +5642,14 @@ static void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scor static void zaddCommand(redisClient *c) { double scoreval; - if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return; - + if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return; zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0); } static void zincrbyCommand(redisClient *c) { double scoreval; - if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return; - + if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return; zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1); } @@ -5526,8 +5689,8 @@ static void zremrangebyscoreCommand(redisClient *c) { robj *zsetobj; zset *zs; - if ((getDoubleFromObject(c, c->argv[2], &min) != REDIS_OK) || - (getDoubleFromObject(c, c->argv[3], &max) != REDIS_OK)) return; + if ((getDoubleFromObjectOrReply(c, c->argv[2], &min, NULL) != REDIS_OK) || + (getDoubleFromObjectOrReply(c, c->argv[3], &max, NULL) != REDIS_OK)) return; if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; @@ -5548,8 +5711,8 @@ static void zremrangebyrankCommand(redisClient *c) { robj *zsetobj; zset *zs; - if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) || - (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return; + if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || + (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; @@ -5604,7 +5767,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat *target = val > *target ? val : *target; } else { /* safety net */ - redisAssert(0 != 0); + redisPanic("Unknown ZUNION/INTER aggregate type"); } } @@ -5657,7 +5820,7 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) { j++; remaining--; for (i = 0; i < zsetnum; i++, j++, remaining--) { - if (getDoubleFromObject(c, c->argv[j], &src[i].weight) != REDIS_OK) + if (getDoubleFromObjectOrReply(c, c->argv[j], &src[i].weight, NULL) != REDIS_OK) return; } } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) { @@ -5790,8 +5953,8 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { zskiplistNode *ln; robj *ele; - if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) || - (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return; + if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || + (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) { withscores = 1; @@ -6065,11 +6228,17 @@ static void hashTryConversion(robj *subject, robj **argv, int start, int end) { } } +/* Encode given objects in-place when the hash uses a dict. */ +static void hashTryObjectEncoding(robj *subject, robj **o1, robj **o2) { + if (subject->encoding == REDIS_ENCODING_HT) { + if (o1) *o1 = tryObjectEncoding(*o1); + if (o2) *o2 = tryObjectEncoding(*o2); + } +} + /* Get the value from a hash identified by key. Returns either a string - * object or NULL if the value cannot be found. - * Note: the refcount for objects retrieved from a zipmap is set to 0. - * This is done, so addReply will increment and clean up the object. - * Make sure to clean up the object when it isn't added to a reply. */ + * object or NULL if the value cannot be found. The refcount of the object + * is always increased by 1 when the value was found. */ static robj *hashGet(robj *o, robj *key) { robj *value = NULL; if (o->encoding == REDIS_ENCODING_ZIPMAP) { @@ -6078,13 +6247,13 @@ static robj *hashGet(robj *o, robj *key) { key = getDecodedObject(key); if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) { value = createStringObject((char*)v,vlen); - value->refcount = 0; } decrRefCount(key); } else { dictEntry *de = dictFind(o->ptr,key); if (de != NULL) { value = dictGetEntryVal(de); + incrRefCount(value); } } return value; @@ -6110,7 +6279,7 @@ static int hashExists(robj *o, robj *key) { /* Add an element, discard the old if the key already exists. * Return 0 on insert and 1 on update. */ -static int hashReplace(robj *o, robj *key, robj *value) { +static int hashSet(robj *o, robj *key, robj *value) { int update = 0; if (o->encoding == REDIS_ENCODING_ZIPMAP) { key = getDecodedObject(key); @@ -6125,7 +6294,6 @@ static int hashReplace(robj *o, robj *key, robj *value) { if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries) convertToRealHash(o); } else { - value = tryObjectEncoding(value); if (dictReplace(o->ptr,key,value)) { /* Insert */ incrRefCount(key); @@ -6155,7 +6323,7 @@ static int hashDelete(robj *o, robj *key) { } /* Return the number of elements in a hash. */ -static inline unsigned long hashLength(robj *o) { +static unsigned long hashLength(robj *o) { return (o->encoding == REDIS_ENCODING_ZIPMAP) ? zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); } @@ -6174,7 +6342,8 @@ typedef struct { dictEntry *de; } hashIterator; -static void hashInitIterator(robj *subject, hashIterator *hi) { +static hashIterator *hashInitIterator(robj *subject) { + hashIterator *hi = zmalloc(sizeof(hashIterator)); hi->encoding = subject->encoding; if (hi->encoding == REDIS_ENCODING_ZIPMAP) { hi->zi = zipmapRewind(subject->ptr); @@ -6183,17 +6352,19 @@ static void hashInitIterator(robj *subject, hashIterator *hi) { } else { redisAssert(NULL); } + return hi; } static void hashReleaseIterator(hashIterator *hi) { if (hi->encoding == REDIS_ENCODING_HT) { dictReleaseIterator(hi->di); } + zfree(hi); } /* Move to the next entry in the hash. Return REDIS_OK when the next entry * could be found and REDIS_ERR when the iterator reaches the end. */ -static inline int hashNext(hashIterator *hi) { +static int hashNext(hashIterator *hi) { if (hi->encoding == REDIS_ENCODING_ZIPMAP) { if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen, &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR; @@ -6204,9 +6375,8 @@ static inline int hashNext(hashIterator *hi) { } /* Get key or value object at current iteration position. - * See comments at hashGet for a discussion on the refcount for - * keys and values retrieved from zipmaps. */ -static inline robj *hashCurrent(hashIterator *hi, int what) { + * This increases the refcount of the field object by 1. */ +static robj *hashCurrent(hashIterator *hi, int what) { robj *o; if (hi->encoding == REDIS_ENCODING_ZIPMAP) { if (what & REDIS_HASH_KEY) { @@ -6214,13 +6384,13 @@ static inline robj *hashCurrent(hashIterator *hi, int what) { } else { o = createStringObject((char*)hi->zv,hi->zvlen); } - o->refcount = 0; } else { if (what & REDIS_HASH_KEY) { o = dictGetEntryKey(hi->de); } else { o = dictGetEntryVal(hi->de); } + incrRefCount(o); } return o; } @@ -6247,7 +6417,8 @@ static void hsetCommand(redisClient *c) { if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; hashTryConversion(o,c->argv,2,3); - update = hashReplace(o,c->argv[2],c->argv[3]); + hashTryObjectEncoding(o,&c->argv[2], &c->argv[3]); + update = hashSet(o,c->argv[2],c->argv[3]); addReply(c, update ? shared.czero : shared.cone); server.dirty++; } @@ -6260,7 +6431,8 @@ static void hsetnxCommand(redisClient *c) { if (hashExists(o, c->argv[2])) { addReply(c, shared.czero); } else { - hashReplace(o,c->argv[2],c->argv[3]); + hashTryObjectEncoding(o,&c->argv[2], &c->argv[3]); + hashSet(o,c->argv[2],c->argv[3]); addReply(c, shared.cone); server.dirty++; } @@ -6278,16 +6450,18 @@ static void hmsetCommand(redisClient *c) { if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; hashTryConversion(o,c->argv,2,c->argc-1); for (i = 2; i < c->argc; i += 2) { - hashReplace(o,c->argv[i],c->argv[i+1]); + hashTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); + hashSet(o,c->argv[i],c->argv[i+1]); } addReply(c, shared.ok); + server.dirty++; } static void hincrbyCommand(redisClient *c) { long long value, incr; robj *o, *current, *new; - if (getLongLongFromObject(c,c->argv[3],&incr) != REDIS_OK) return; + if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; if ((current = hashGet(o,c->argv[2])) != NULL) { if (current->encoding == REDIS_ENCODING_RAW) @@ -6296,19 +6470,15 @@ static void hincrbyCommand(redisClient *c) { value = (long)current->ptr; else redisAssert(1 != 1); - - /* clean up object when it was retrieved from a zipmap */ - if (current->refcount == 0) { - current->refcount = 1; - decrRefCount(current); - } + decrRefCount(current); } else { value = 0; } value += incr; - new = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); - hashReplace(o,c->argv[2],new); + new = createStringObjectFromLongLong(value); + hashTryObjectEncoding(o,&c->argv[2],NULL); + hashSet(o,c->argv[2],new); decrRefCount(new); addReplyLongLong(c,value); server.dirty++; @@ -6321,6 +6491,7 @@ static void hgetCommand(redisClient *c) { if ((value = hashGet(o,c->argv[2])) != NULL) { addReplyBulk(c,value); + decrRefCount(value); } else { addReply(c,shared.nullbulk); } @@ -6341,6 +6512,7 @@ static void hmgetCommand(redisClient *c) { for (i = 2; i < c->argc; i++) { if (o != NULL && (value = hashGet(o,c->argv[i])) != NULL) { addReplyBulk(c,value); + decrRefCount(value); } else { addReply(c,shared.nullbulk); } @@ -6372,7 +6544,7 @@ static void hlenCommand(redisClient *c) { static void genericHgetallCommand(redisClient *c, int flags) { robj *o, *lenobj, *obj; unsigned long count = 0; - hashIterator hi; + hashIterator *hi; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,REDIS_HASH)) return; @@ -6381,20 +6553,22 @@ static void genericHgetallCommand(redisClient *c, int flags) { addReply(c,lenobj); decrRefCount(lenobj); - hashInitIterator(o, &hi); - while (hashNext(&hi) != REDIS_ERR) { + hi = hashInitIterator(o); + while (hashNext(hi) != REDIS_ERR) { if (flags & REDIS_HASH_KEY) { - obj = hashCurrent(&hi,REDIS_HASH_KEY); + obj = hashCurrent(hi,REDIS_HASH_KEY); addReplyBulk(c,obj); + decrRefCount(obj); count++; } if (flags & REDIS_HASH_VALUE) { - obj = hashCurrent(&hi,REDIS_HASH_VALUE); + obj = hashCurrent(hi,REDIS_HASH_VALUE); addReplyBulk(c,obj); + decrRefCount(obj); count++; } } - hashReleaseIterator(&hi); + hashReleaseIterator(hi); lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count); } @@ -6468,23 +6642,26 @@ static redisSortOperation *createSortOperation(int type, robj *pattern) { } /* Return the value associated to the key with a name obtained - * substituting the first occurence of '*' in 'pattern' with 'subst' */ + * substituting the first occurence of '*' in 'pattern' with 'subst'. + * The returned object will always have its refcount increased by 1 + * when it is non-NULL. */ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { - char *p; + char *p, *f; sds spat, ssub; - robj keyobj; - int prefixlen, sublen, postfixlen; + robj keyobj, fieldobj, *o; + int prefixlen, sublen, postfixlen, fieldlen; /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */ struct { long len; long free; char buf[REDIS_SORTKEY_MAX+1]; - } keyname; + } keyname, fieldname; /* If the pattern is "#" return the substitution object itself in order * to implement the "SORT ... GET #" feature. */ spat = pattern->ptr; if (spat[0] == '#' && spat[1] == '\0') { + incrRefCount(subst); return subst; } @@ -6501,20 +6678,47 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { return NULL; } + /* Find out if we're dealing with a hash dereference. */ + if ((f = strstr(p+1, "->")) != NULL) { + fieldlen = sdslen(spat)-(f-spat); + /* this also copies \0 character */ + memcpy(fieldname.buf,f+2,fieldlen-1); + fieldname.len = fieldlen-2; + } else { + fieldlen = 0; + } + prefixlen = p-spat; sublen = sdslen(ssub); - postfixlen = sdslen(spat)-(prefixlen+1); + postfixlen = sdslen(spat)-(prefixlen+1)-fieldlen; memcpy(keyname.buf,spat,prefixlen); memcpy(keyname.buf+prefixlen,ssub,sublen); memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen); keyname.buf[prefixlen+sublen+postfixlen] = '\0'; keyname.len = prefixlen+sublen+postfixlen; - - initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2)) decrRefCount(subst); - /* printf("lookup '%s' => %p\n", keyname.buf,de); */ - return lookupKeyRead(db,&keyobj); + /* Lookup substituted key */ + initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2)); + o = lookupKeyRead(db,&keyobj); + if (o == NULL) return NULL; + + if (fieldlen > 0) { + if (o->type != REDIS_HASH || fieldname.len < 1) return NULL; + + /* Retrieve value from hash by the field name. This operation + * already increases the refcount of the returned object. */ + initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(long)*2)); + o = hashGet(o, &fieldobj); + } else { + if (o->type != REDIS_STRING) return NULL; + + /* Every object that this function returns needs to have its refcount + * increased. sortCommand decreases it again. */ + incrRefCount(o); + } + + return o; } /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with @@ -6549,14 +6753,8 @@ static int sortCompare(const void *s1, const void *s2) { cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr); } } else { - /* Compare elements directly */ - robj *dec1, *dec2; - - dec1 = getDecodedObject(so1->obj); - dec2 = getDecodedObject(so2->obj); - cmp = strcoll(dec1->ptr,dec2->ptr); - decrRefCount(dec1); - decrRefCount(dec2); + /* Compare elements directly. */ + cmp = compareStringObjects(so1->obj,so2->obj); } } return server.sort_desc ? -cmp : cmp; @@ -6639,7 +6837,7 @@ static void sortCommand(redisClient *c) { case REDIS_LIST: vectorlen = listLength((list*)sortval->ptr); break; case REDIS_SET: vectorlen = dictSize((dict*)sortval->ptr); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; - default: vectorlen = 0; redisAssert(0); /* Avoid GCC warning */ + default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; @@ -6683,38 +6881,36 @@ static void sortCommand(redisClient *c) { /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { + robj *byval; if (sortby) { - robj *byval; - + /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); - if (!byval || byval->type != REDIS_STRING) continue; - if (alpha) { - vector[j].u.cmpobj = getDecodedObject(byval); - } else { - if (byval->encoding == REDIS_ENCODING_RAW) { - vector[j].u.score = strtod(byval->ptr,NULL); - } else { - /* Don't need to decode the object if it's - * integer-encoded (the only encoding supported) so - * far. We can just cast it */ - if (byval->encoding == REDIS_ENCODING_INT) { - vector[j].u.score = (long)byval->ptr; - } else - redisAssert(1 != 1); - } - } + if (!byval) continue; } else { - if (!alpha) { - if (vector[j].obj->encoding == REDIS_ENCODING_RAW) - vector[j].u.score = strtod(vector[j].obj->ptr,NULL); - else { - if (vector[j].obj->encoding == REDIS_ENCODING_INT) - vector[j].u.score = (long) vector[j].obj->ptr; - else - redisAssert(1 != 1); - } + /* use object itself to sort by */ + byval = vector[j].obj; + } + + if (alpha) { + if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); + } else { + if (byval->encoding == REDIS_ENCODING_RAW) { + vector[j].u.score = strtod(byval->ptr,NULL); + } else if (byval->encoding == REDIS_ENCODING_INT) { + /* Don't need to decode the object if it's + * integer-encoded (the only encoding supported) so + * far. We can just cast it */ + vector[j].u.score = (long)byval->ptr; + } else { + redisAssert(1 != 1); } } + + /* when the object was retrieved using lookupKeyByPattern, + * its refcount needs to be decreased. */ + if (sortby) { + decrRefCount(byval); + } } } @@ -6756,10 +6952,11 @@ static void sortCommand(redisClient *c) { vector[j].obj); if (sop->type == REDIS_SORT_GET) { - if (!val || val->type != REDIS_STRING) { + if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); + decrRefCount(val); } } else { redisAssert(sop->type == REDIS_SORT_GET); /* always fails */ @@ -6786,11 +6983,14 @@ static void sortCommand(redisClient *c) { vector[j].obj); if (sop->type == REDIS_SORT_GET) { - if (!val || val->type != REDIS_STRING) { + if (!val) { listAddNodeTail(listPtr,createStringObject("",0)); } else { + /* We should do a incrRefCount on val because it is + * added to the list, but also a decrRefCount because + * it is returned by lookupKeyByPattern. This results + * in doing nothing at all. */ listAddNodeTail(listPtr,val); - incrRefCount(val); } } else { redisAssert(sop->type == REDIS_SORT_GET); /* always fails */ @@ -6811,7 +7011,7 @@ static void sortCommand(redisClient *c) { decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { - if (sortby && alpha && vector[j].u.cmpobj) + if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); @@ -7038,7 +7238,7 @@ static void expireGenericCommand(redisClient *c, robj *key, robj *param, long of dictEntry *de; time_t seconds; - if (getLongFromObject(c, param, &seconds) != REDIS_OK) return; + if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; seconds -= offset; @@ -7047,7 +7247,7 @@ static void expireGenericCommand(redisClient *c, robj *key, robj *param, long of addReply(c,shared.czero); return; } - if (seconds < 0) { + if (seconds <= 0) { if (deleteKey(c->db,key)) server.dirty++; addReply(c, shared.cone); return; @@ -7140,6 +7340,20 @@ static void discardCommand(redisClient *c) { addReply(c,shared.ok); } +/* Send a MULTI command to all the slaves and AOF file. Check the execCommand + * implememntation for more information. */ +static void execCommandReplicateMulti(redisClient *c) { + struct redisCommand *cmd; + robj *multistring = createStringObject("MULTI",5); + + cmd = lookupCommand("multi"); + if (server.appendonly) + feedAppendOnlyFile(cmd,c->db->id,&multistring,1); + if (listLength(server.slaves)) + replicationFeedSlaves(server.slaves,c->db->id,&multistring,1); + decrRefCount(multistring); +} + static void execCommand(redisClient *c) { int j; robj **orig_argv; @@ -7150,6 +7364,13 @@ static void execCommand(redisClient *c) { return; } + /* Replicate a MULTI request now that we are sure the block is executed. + * This way we'll deliver the MULTI/..../EXEC block as a whole and + * both the AOF and the replication link will have the same consistency + * and atomicity guarantees. */ + execCommandReplicateMulti(c); + + /* Exec all the queued commands */ orig_argv = c->argv; orig_argc = c->argc; addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->mstate.count)); @@ -7163,6 +7384,10 @@ static void execCommand(redisClient *c) { freeClientMultiState(c); initClientMultiState(c); c->flags &= (~REDIS_MULTI); + /* Make sure the EXEC command is always replicated / AOF, since we + * always send the MULTI command (we can't know beforehand if the + * next operations will contain at least a modification to the DB). */ + server.dirty++; } /* =========================== Blocking Operations ========================= */ @@ -8186,7 +8411,7 @@ static int rewriteAppendOnlyFile(char *filename) { dictReleaseIterator(di); } } else { - redisAssert(0); + redisPanic("Unknown object type"); } /* Save the expire time */ if (expiretime != -1) { @@ -9673,8 +9898,9 @@ static int pubsubPublishMessage(robj *channel, robj *message) { sdslen(pat->pattern->ptr), (char*)channel->ptr, sdslen(channel->ptr),0)) { - addReply(pat->client,shared.mbulk3); - addReply(pat->client,shared.messagebulk); + addReply(pat->client,shared.mbulk4); + addReply(pat->client,shared.pmessagebulk); + addReplyBulk(pat->client,pat->pattern); addReplyBulk(pat->client,channel); addReplyBulk(pat->client,message); receivers++; @@ -9833,6 +10059,15 @@ static void _redisAssert(char *estr, char *file, int line) { #endif } +static void _redisPanic(char *msg, char *file, int line) { + redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue"); + redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line); +#ifdef HAVE_BACKTRACE + redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); + *((char*)-1) = 'x'; +#endif +} + /* =================================== Main! ================================ */ #ifdef __linux__ @@ -9852,7 +10087,7 @@ int linuxOvercommitMemoryValue(void) { void linuxOvercommitMemoryWarning(void) { if (linuxOvercommitMemoryValue() == 0) { - redisLog(REDIS_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low condition memory. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect."); + redisLog(REDIS_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect."); } } #endif /* __linux__ */