]> git.saurik.com Git - redis.git/commitdiff
conflicts resolved
authorantirez <antirez@gmail.com>
Thu, 13 May 2010 12:24:41 +0000 (14:24 +0200)
committerantirez <antirez@gmail.com>
Thu, 13 May 2010 12:24:41 +0000 (14:24 +0200)
1  2 
redis.c
redis.conf

diff --combined redis.c
index 155af351e12c29162467c7c598eeb55ec06544f9,f52dbb82c672efb973ee18dfe74ef6dfbe0860d3..45caae8fbf4741b96f7715e1039a23b0440f29c8
+++ b/redis.c
@@@ -57,7 -57,6 +57,7 @@@
  #include <sys/resource.h>
  #include <sys/uio.h>
  #include <limits.h>
 +#include <float.h>
  #include <math.h>
  #include <pthread.h>
  
@@@ -451,6 -450,7 +451,7 @@@ typedef struct pubsubPattern 
  } pubsubPattern;
  
  typedef void redisCommandProc(redisClient *c);
+ typedef void redisVmPreloadProc(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
  struct redisCommand {
      char *name;
      redisCommandProc *proc;
      /* Use a function to determine which keys need to be loaded
       * in the background prior to executing this command. Takes precedence
       * over vm_firstkey and others, ignored when NULL */
-     redisCommandProc *vm_preload_proc;
+     redisVmPreloadProc *vm_preload_proc;
      /* What keys should be loaded in background when calling this command? */
      int vm_firstkey; /* The first argument that's a key (0 = no keys) */
      int vm_lastkey;  /* THe last argument that's a key */
@@@ -609,8 -609,9 +610,9 @@@ static robj *vmReadObjectFromSwap(off_
  static void waitEmptyIOJobsQueue(void);
  static void vmReopenSwapFile(void);
  static int vmFreePage(off_t page);
- static void zunionInterBlockClientOnSwappedKeys(redisClient *c);
- static int blockClientOnSwappedKeys(struct redisCommand *cmd, redisClient *c);
+ static void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
+ static void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
+ static int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd);
  static int dontWaitForSwappedKey(redisClient *c, robj *key);
  static void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
  static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
@@@ -623,10 -624,8 +625,10 @@@ static int pubsubUnsubscribeAllPatterns
  static void freePubsubPattern(void *p);
  static int listMatchPubsubPattern(void *a, void *b);
  static int compareStringObjects(robj *a, robj *b);
 +static int equalStringObjects(robj *a, robj *b);
  static void usage();
  static int rewriteAppendOnlyFileBackground(void);
 +static int vmSwapObjectBlocking(robj *key, robj *val);
  
  static void authCommand(redisClient *c);
  static void pingCommand(redisClient *c);
@@@ -828,7 -827,7 +830,7 @@@ static struct redisCommand cmdTable[] 
      {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
      {"type",typeCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
      {"multi",multiCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,0,0,0},
+     {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0},
      {"discard",discardCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
      {"sync",syncCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
      {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
@@@ -1024,30 -1023,6 +1026,30 @@@ static long long memtoll(const char *p
      return val*mul;
  }
  
 +/* Convert a long long into a string. Returns the number of
 + * characters needed to represent the number, that can be shorter if passed
 + * buffer length is not enough to store the whole number. */
 +static int ll2string(char *s, size_t len, long long value) {
 +    char buf[32], *p;
 +    unsigned long long v;
 +    size_t l;
 +
 +    if (len == 0) return 0;
 +    v = (value < 0) ? -value : value;
 +    p = buf+31; /* point to the last character */
 +    do {
 +        *p-- = '0'+(v%10);
 +        v /= 10;
 +    } while(v);
 +    if (value < 0) *p-- = '-';
 +    p++;
 +    l = 32-(p-buf);
 +    if (l+1 > len) l = len-1; /* Make sure it fits, including the nul term */
 +    memcpy(s,p,l);
 +    s[l] = '\0';
 +    return l;
 +}
 +
  static void redisLog(int level, const char *fmt, ...) {
      va_list ap;
      FILE *fp;
@@@ -1130,8 -1105,8 +1132,8 @@@ static int dictEncObjKeyCompare(void *p
      int cmp;
  
      if (o1->encoding == REDIS_ENCODING_INT &&
 -        o2->encoding == REDIS_ENCODING_INT &&
 -        o1->ptr == o2->ptr) return 1;
 +        o2->encoding == REDIS_ENCODING_INT)
 +            return o1->ptr == o2->ptr;
  
      o1 = getDecodedObject(o1);
      o2 = getDecodedObject(o2);
@@@ -1151,7 -1126,7 +1153,7 @@@ static unsigned int dictEncObjHash(cons
              char buf[32];
              int len;
  
 -            len = snprintf(buf,32,"%ld",(long)o->ptr);
 +            len = ll2string(buf,32,(long)o->ptr);
              return dictGenHashFunction((unsigned char*)buf, len);
          } else {
              unsigned int hash;
@@@ -1661,7 -1636,7 +1663,7 @@@ static void initServerConfig() 
      server.glueoutputbuf = 1;
      server.daemonize = 0;
      server.appendonly = 0;
 -    server.appendfsync = APPENDFSYNC_ALWAYS;
 +    server.appendfsync = APPENDFSYNC_EVERYSEC;
      server.lastfsync = time(NULL);
      server.appendfd = -1;
      server.appendseldb = -1; /* Make sure the first time will not match */
@@@ -1913,6 -1888,9 +1915,9 @@@ static void loadServerConfig(char *file
              if ((server.appendonly = yesnotoi(argv[1])) == -1) {
                  err = "argument must be 'yes' or 'no'"; goto loaderr;
              }
+         } else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
+             zfree(server.appendfilename);
+             server.appendfilename = zstrdup(argv[1]);
          } else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
              if (!strcasecmp(argv[1],"no")) {
                  server.appendfsync = APPENDFSYNC_NO;
@@@ -2418,7 -2396,7 +2423,7 @@@ static int processCommand(redisClient *
          addReply(c,shared.queued);
      } else {
          if (server.vm_enabled && server.vm_max_threads > 0 &&
-             blockClientOnSwappedKeys(cmd,c)) return 1;
+             blockClientOnSwappedKeys(c,cmd)) return 1;
          call(c,cmd);
      }
  
@@@ -2682,7 -2660,7 +2687,7 @@@ static void *dupClientReplyValue(void *
  }
  
  static int listMatchObjects(void *a, void *b) {
 -    return compareStringObjects(a,b) == 0;
 +    return equalStringObjects(a,b);
  }
  
  static redisClient *createClient(int fd) {
@@@ -2922,7 -2900,7 +2927,7 @@@ static robj *createStringObjectFromLong
              o->encoding = REDIS_ENCODING_INT;
              o->ptr = (void*)((long)value);
          } else {
 -            o->ptr = sdscatprintf(sdsempty(),"%lld",value);
 +            o = createObject(REDIS_STRING,sdsfromlonglong(value));
          }
      }
      return o;
@@@ -3137,7 -3115,7 +3142,7 @@@ static int isStringRepresentableAsLong(
  
      value = strtol(s, &endptr, 10);
      if (endptr[0] != '\0') return REDIS_ERR;
 -    slen = snprintf(buf,32,"%ld",value);
 +    slen = ll2string(buf,32,value);
  
      /* If the number converted back into a string is not identical
       * then it's not possible to encode the string as integer */
@@@ -3190,7 -3168,7 +3195,7 @@@ static robj *getDecodedObject(robj *o) 
      if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
          char buf[32];
  
 -        snprintf(buf,32,"%ld",(long)o->ptr);
 +        ll2string(buf,32,(long)o->ptr);
          dec = createStringObject(buf,strlen(buf));
          return dec;
      } else {
  
  /* Compare two string objects via strcmp() or alike.
   * Note that the objects may be integer-encoded. In such a case we
 - * use snprintf() to get a string representation of the numbers on the stack
 + * use ll2string() to get a string representation of the numbers on the stack
   * and compare the strings, it's much faster than calling getDecodedObject().
   *
   * Important note: if objects are not integer encoded, but binary-safe strings,
@@@ -3213,14 -3191,14 +3218,14 @@@ static int compareStringObjects(robj *a
  
      if (a == b) return 0;
      if (a->encoding != REDIS_ENCODING_RAW) {
 -        snprintf(bufa,sizeof(bufa),"%ld",(long) a->ptr);
 +        ll2string(bufa,sizeof(bufa),(long) a->ptr);
          astr = bufa;
          bothsds = 0;
      } else {
          astr = a->ptr;
      }
      if (b->encoding != REDIS_ENCODING_RAW) {
 -        snprintf(bufb,sizeof(bufb),"%ld",(long) b->ptr);
 +        ll2string(bufb,sizeof(bufb),(long) b->ptr);
          bstr = bufb;
          bothsds = 0;
      } else {
      return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
  }
  
 +/* Equal string objects return 1 if the two objects are the same from the
 + * point of view of a string comparison, otherwise 0 is returned. Note that
 + * this function is faster then checking for (compareStringObject(a,b) == 0)
 + * because it can perform some more optimization. */
 +static int equalStringObjects(robj *a, robj *b) {
 +    if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){
 +        return a->ptr == b->ptr;
 +    } else {
 +        return compareStringObjects(a,b) == 0;
 +    }
 +}
 +
  static size_t stringObjectLen(robj *o) {
      redisAssert(o->type == REDIS_STRING);
      if (o->encoding == REDIS_ENCODING_RAW) {
      } else {
          char buf[32];
  
 -        return snprintf(buf,32,"%ld",(long)o->ptr);
 +        return ll2string(buf,32,(long)o->ptr);
      }
  }
  
@@@ -3379,12 -3345,22 +3384,12 @@@ static int rdbSaveLen(FILE *fp, uint32_
      return 0;
  }
  
 -/* String objects in the form "2391" "-100" without any space and with a
 - * range of values that can fit in an 8, 16 or 32 bit signed value can be
 - * encoded as integers to save space */
 -static int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
 -    long long value;
 -    char *endptr, buf[32];
 -
 -    /* Check if it's possible to encode this value as a number */
 -    value = strtoll(s, &endptr, 10);
 -    if (endptr[0] != '\0') return 0;
 -    snprintf(buf,32,"%lld",value);
 -
 -    /* If the number converted back into a string is not identical
 -     * then it's not possible to encode the string as integer */
 -    if (strlen(buf) != len || memcmp(buf,s,len)) return 0;
 -
 +/* Encode 'value' as an integer if possible (if integer will fit the
 + * supported range). If the function sucessful encoded the integer
 + * then the (up to 5 bytes) encoded representation is written in the
 + * string pointed by 'enc' and the length is returned. Otherwise
 + * 0 is returned. */
 +static int rdbEncodeInteger(long long value, unsigned char *enc) {
      /* Finally check if it fits in our ranges */
      if (value >= -(1<<7) && value <= (1<<7)-1) {
          enc[0] = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_INT8;
      }
  }
  
 +/* String objects in the form "2391" "-100" without any space and with a
 + * range of values that can fit in an 8, 16 or 32 bit signed value can be
 + * encoded as integers to save space */
 +static int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
 +    long long value;
 +    char *endptr, buf[32];
 +
 +    /* Check if it's possible to encode this value as a number */
 +    value = strtoll(s, &endptr, 10);
 +    if (endptr[0] != '\0') return 0;
 +    ll2string(buf,32,value);
 +
 +    /* If the number converted back into a string is not identical
 +     * then it's not possible to encode the string as integer */
 +    if (strlen(buf) != len || memcmp(buf,s,len)) return 0;
 +
 +    return rdbEncodeInteger(value,enc);
 +}
 +
  static int rdbSaveLzfStringObject(FILE *fp, unsigned char *s, size_t len) {
      size_t comprlen, outlen;
      unsigned char byte;
@@@ -3489,21 -3446,6 +3494,21 @@@ static int rdbSaveRawString(FILE *fp, u
  static int rdbSaveStringObject(FILE *fp, robj *obj) {
      int retval;
  
 +    /* Avoid to decode the object, then encode it again, if the
 +     * object is alrady integer encoded. */
 +    if (obj->encoding == REDIS_ENCODING_INT) {
 +        long val = (long) obj->ptr;
 +        unsigned char buf[5];
 +        int enclen;
 +
 +        if ((enclen = rdbEncodeInteger(val,buf)) > 0) {
 +            if (fwrite(buf,enclen,1,fp) == 0) return -1;
 +            return 0;
 +        }
 +        /* otherwise... fall throught and continue with the usual
 +         * code path. */
 +    }
 +
      /* Avoid incr/decr ref count business when possible.
       * This plays well with copy-on-write given that we are probably
       * in a child process (BGSAVE). Also this makes sure key objects
@@@ -3538,23 -3480,7 +3543,23 @@@ static int rdbSaveDoubleValue(FILE *fp
          len = 1;
          buf[0] = (val < 0) ? 255 : 254;
      } else {
 -        snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
 +#if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL)
 +        /* Check if the float is in a safe range to be casted into a
 +         * long long. We are assuming that long long is 64 bit here.
 +         * Also we are assuming that there are no implementations around where
 +         * double has precision < 52 bit.
 +         *
 +         * Under this assumptions we test if a double is inside an interval
 +         * where casting to long long is safe. Then using two castings we
 +         * make sure the decimal part is zero. If all this is true we use
 +         * integer printing function that is much faster. */
 +        double min = -4503599627370495; /* (2^52)-1 */
 +        double max = 4503599627370496; /* -(2^52) */
 +        if (val > min && val < max && val == ((double)((long long)val)))
 +            ll2string((char*)buf+1,sizeof(buf),(long long)val);
 +        else
 +#endif
 +            snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
          buf[0] = strlen((char*)buf+1);
          len = buf[0]+1;
      }
@@@ -3838,11 -3764,7 +3843,11 @@@ static uint32_t rdbLoadLen(FILE *fp, in
      }
  }
  
 -static robj *rdbLoadIntegerObject(FILE *fp, int enctype) {
 +/* Load an integer-encoded object from file 'fp', with the specified
 + * encoding type 'enctype'. If encode is true the function may return
 + * an integer-encoded object as reply, otherwise the returned object
 + * will always be encoded as a raw string. */
 +static robj *rdbLoadIntegerObject(FILE *fp, int enctype, int encode) {
      unsigned char enc[4];
      long long val;
  
          val = 0; /* anti-warning */
          redisPanic("Unknown RDB integer encoding type");
      }
 -    return createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",val));
 +    if (encode)
 +        return createStringObjectFromLongLong(val);
 +    else
 +        return createObject(REDIS_STRING,sdsfromlonglong(val));
  }
  
  static robj *rdbLoadLzfStringObject(FILE*fp) {
@@@ -3888,7 -3807,7 +3893,7 @@@ err
      return NULL;
  }
  
 -static robj *rdbLoadStringObject(FILE*fp) {
 +static robj *rdbGenericLoadStringObject(FILE*fp, int encode) {
      int isencoded;
      uint32_t len;
      sds val;
          case REDIS_RDB_ENC_INT8:
          case REDIS_RDB_ENC_INT16:
          case REDIS_RDB_ENC_INT32:
 -            return rdbLoadIntegerObject(fp,len);
 +            return rdbLoadIntegerObject(fp,len,encode);
          case REDIS_RDB_ENC_LZF:
              return rdbLoadLzfStringObject(fp);
          default:
      return createObject(REDIS_STRING,val);
  }
  
 +static robj *rdbLoadStringObject(FILE *fp) {
 +    return rdbGenericLoadStringObject(fp,0);
 +}
 +
 +static robj *rdbLoadEncodedStringObject(FILE *fp) {
 +    return rdbGenericLoadStringObject(fp,1);
 +}
 +
  /* For information about double serialization check rdbSaveDoubleValue() */
  static int rdbLoadDoubleValue(FILE *fp, double *val) {
      char buf[128];
@@@ -3950,7 -3861,7 +3955,7 @@@ static robj *rdbLoadObject(int type, FI
      redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
      if (type == REDIS_STRING) {
          /* Read string value */
 -        if ((o = rdbLoadStringObject(fp)) == NULL) return NULL;
 +        if ((o = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
          o = tryObjectEncoding(o);
      } else if (type == REDIS_LIST || type == REDIS_SET) {
          /* Read list/set value */
          while(listlen--) {
              robj *ele;
  
 -            if ((ele = rdbLoadStringObject(fp)) == NULL) return NULL;
 +            if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
              ele = tryObjectEncoding(ele);
              if (type == REDIS_LIST) {
                  listAddNodeTail((list*)o->ptr,ele);
              robj *ele;
              double *score = zmalloc(sizeof(double));
  
 -            if ((ele = rdbLoadStringObject(fp)) == NULL) return NULL;
 +            if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
              ele = tryObjectEncoding(ele);
              if (rdbLoadDoubleValue(fp,score) == -1) return NULL;
              dictAdd(zs->dict,ele,score);
  
  static int rdbLoad(char *filename) {
      FILE *fp;
 -    robj *keyobj = NULL;
      uint32_t dbid;
      int type, retval, rdbver;
 +    int swap_all_values = 0;
      dict *d = server.db[0].dict;
      redisDb *db = server.db+0;
      char buf[1024];
 -    time_t expiretime = -1, now = time(NULL);
 +    time_t expiretime, now = time(NULL);
      long long loadedkeys = 0;
  
      fp = fopen(filename,"r");
          return REDIS_ERR;
      }
      while(1) {
 -        robj *o;
 +        robj *key, *val;
  
 +        expiretime = -1;
          /* Read type. */
          if ((type = rdbLoadType(fp)) == -1) goto eoferr;
          if (type == REDIS_EXPIRETIME) {
              continue;
          }
          /* Read key */
 -        if ((keyobj = rdbLoadStringObject(fp)) == NULL) goto eoferr;
 +        if ((key = rdbLoadStringObject(fp)) == NULL) goto eoferr;
          /* Read value */
 -        if ((o = rdbLoadObject(type,fp)) == NULL) goto eoferr;
 +        if ((val = rdbLoadObject(type,fp)) == NULL) goto eoferr;
 +        /* Check if the key already expired */
 +        if (expiretime != -1 && expiretime < now) {
 +            decrRefCount(key);
 +            decrRefCount(val);
 +            continue;
 +        }
          /* Add the new object in the hash table */
 -        retval = dictAdd(d,keyobj,o);
 +        retval = dictAdd(d,key,val);
          if (retval == DICT_ERR) {
 -            redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", keyobj->ptr);
 +            redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", key->ptr);
              exit(1);
          }
 +        loadedkeys++;
          /* Set the expire time if needed */
 -        if (expiretime != -1) {
 -            setExpire(db,keyobj,expiretime);
 -            /* Delete this key if already expired */
 -            if (expiretime < now) deleteKey(db,keyobj);
 -            expiretime = -1;
 -        }
 -        keyobj = o = NULL;
 +        if (expiretime != -1) setExpire(db,key,expiretime);
 +
          /* Handle swapping while loading big datasets when VM is on */
 -        loadedkeys++;
 -        if (server.vm_enabled && (loadedkeys % 5000) == 0) {
 +
 +        /* If we detecter we are hopeless about fitting something in memory
 +         * we just swap every new key on disk. Directly...
 +         * Note that's important to check for this condition before resorting
 +         * to random sampling, otherwise we may try to swap already
 +         * swapped keys. */
 +        if (swap_all_values) {
 +            dictEntry *de = dictFind(d,key);
 +
 +            /* de may be NULL since the key already expired */
 +            if (de) {
 +                key = dictGetEntryKey(de);
 +                val = dictGetEntryVal(de);
 +
 +                if (vmSwapObjectBlocking(key,val) == REDIS_OK) {
 +                    dictGetEntryVal(de) = NULL;
 +                }
 +            }
 +            continue;
 +        }
 +
 +        /* If we have still some hope of having some value fitting memory
 +         * then we try random sampling. */
 +        if (!swap_all_values && server.vm_enabled && (loadedkeys % 5000) == 0) {
              while (zmalloc_used_memory() > server.vm_max_memory) {
                  if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
              }
 +            if (zmalloc_used_memory() > server.vm_max_memory)
 +                swap_all_values = 1; /* We are already using too much mem */
          }
      }
      fclose(fp);
      return REDIS_OK;
  
  eoferr: /* unexpected end of file is handled here with a fatal exit */
 -    if (keyobj) decrRefCount(keyobj);
      redisLog(REDIS_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
      exit(1);
      return REDIS_ERR; /* Just to avoid warning */
@@@ -4324,8 -4209,8 +4329,8 @@@ static void incrDecrCommand(redisClien
      robj *o;
  
      o = lookupKeyWrite(c->db,c->argv[1]);
 -
 -    if (getLongLongFromObjectOrReply(c, o, &value, NULL) != REDIS_OK) return;
 +    if (o != NULL && checkType(c,o,REDIS_STRING)) return;
 +    if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
  
      value += incr;
      o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
@@@ -4463,7 -4348,12 +4468,12 @@@ static void delCommand(redisClient *c) 
  }
  
  static void existsCommand(redisClient *c) {
-     addReply(c,lookupKeyRead(c->db,c->argv[1]) ? shared.cone : shared.czero);
+     expireIfNeeded(c->db,c->argv[1]);
+     if (dictFind(c->db->dict,c->argv[1])) {
+         addReply(c, shared.cone);
+     } else {
+         addReply(c, shared.czero);
+     }
  }
  
  static void selectCommand(redisClient *c) {
@@@ -4943,7 -4833,7 +4953,7 @@@ static void lremCommand(redisClient *c
          robj *ele = listNodeValue(ln);
  
          next = fromtail ? ln->prev : ln->next;
 -        if (compareStringObjects(ele,c->argv[3]) == 0) {
 +        if (equalStringObjects(ele,c->argv[3])) {
              listDelNode(list,ln);
              server.dirty++;
              removed++;
@@@ -5547,7 -5437,7 +5557,7 @@@ static int zslDelete(zskiplist *zsl, do
      /* We may have multiple elements with the same score, what we need
       * is to find the element with both the right score and object. */
      x = x->forward[0];
 -    if (x && score == x->score && compareStringObjects(x->obj,obj) == 0) {
 +    if (x && score == x->score && equalStringObjects(x->obj,obj)) {
          zslDeleteNode(zsl, x, update);
          zslFreeNode(x);
          return 1;
@@@ -5652,7 -5542,7 +5662,7 @@@ static unsigned long zslGetRank(zskipli
          }
  
          /* x might be equal to zsl->header, so test if obj is non-NULL */
 -        if (x->obj && compareStringObjects(x->obj,o) == 0) {
 +        if (x->obj && equalStringObjects(x->obj,o)) {
              return rank;
          }
      }
@@@ -8178,9 -8068,41 +8188,41 @@@ static void flushAppendOnlyFile(void) 
      }
  }
  
+ static sds catAppendOnlyGenericCommand(sds buf, int argc, robj **argv) {
+     int j;
+     buf = sdscatprintf(buf,"*%d\r\n",argc);
+     for (j = 0; j < argc; j++) {
+         robj *o = getDecodedObject(argv[j]);
+         buf = sdscatprintf(buf,"$%lu\r\n",(unsigned long)sdslen(o->ptr));
+         buf = sdscatlen(buf,o->ptr,sdslen(o->ptr));
+         buf = sdscatlen(buf,"\r\n",2);
+         decrRefCount(o);
+     }
+     return buf;
+ }
+ static sds catAppendOnlyExpireAtCommand(sds buf, robj *key, robj *seconds) {
+     int argc = 3;
+     long when;
+     robj *argv[3];
+     /* Make sure we can use strtol */
+     seconds = getDecodedObject(seconds);
+     when = time(NULL)+strtol(seconds->ptr,NULL,10);
+     decrRefCount(seconds);
+     argv[0] = createStringObject("EXPIREAT",8);
+     argv[1] = key;
+     argv[2] = createObject(REDIS_STRING,
+         sdscatprintf(sdsempty(),"%ld",when));
+     buf = catAppendOnlyGenericCommand(buf, argc, argv);
+     decrRefCount(argv[0]);
+     decrRefCount(argv[2]);
+     return buf;
+ }
  static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
      sds buf = sdsempty();
-     int j;
      robj *tmpargv[3];
  
      /* The DB this command was targetting is not the same as the last command
          server.appendseldb = dictid;
      }
  
-     /* "Fix" the argv vector if the command is EXPIRE. We want to translate
-      * EXPIREs into EXPIREATs calls */
      if (cmd->proc == expireCommand) {
-         long when;
-         tmpargv[0] = createStringObject("EXPIREAT",8);
+         /* Translate EXPIRE into EXPIREAT */
+         buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
+     } else if (cmd->proc == setexCommand) {
+         /* Translate SETEX to SET and EXPIREAT */
+         tmpargv[0] = createStringObject("SET",3);
          tmpargv[1] = argv[1];
-         incrRefCount(argv[1]);
-         when = time(NULL)+strtol(argv[2]->ptr,NULL,10);
-         tmpargv[2] = createObject(REDIS_STRING,
-             sdscatprintf(sdsempty(),"%ld",when));
-         argv = tmpargv;
-     }
-     /* Append the actual command */
-     buf = sdscatprintf(buf,"*%d\r\n",argc);
-     for (j = 0; j < argc; j++) {
-         robj *o = argv[j];
-         o = getDecodedObject(o);
-         buf = sdscatprintf(buf,"$%lu\r\n",(unsigned long)sdslen(o->ptr));
-         buf = sdscatlen(buf,o->ptr,sdslen(o->ptr));
-         buf = sdscatlen(buf,"\r\n",2);
-         decrRefCount(o);
-     }
-     /* Free the objects from the modified argv for EXPIREAT */
-     if (cmd->proc == expireCommand) {
-         for (j = 0; j < 3; j++)
-             decrRefCount(argv[j]);
+         tmpargv[2] = argv[3];
+         buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
+         decrRefCount(tmpargv[0]);
+         buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
+     } else {
+         buf = catAppendOnlyGenericCommand(buf,argc,argv);
      }
  
      /* Append to the AOF buffer. This will be flushed on disk just before
@@@ -9660,12 -9565,56 +9685,56 @@@ static int waitForSwappedKey(redisClien
      return 1;
  }
  
- /* Preload keys needed for the ZUNION and ZINTER commands. */
- static void zunionInterBlockClientOnSwappedKeys(redisClient *c) {
+ /* Preload keys for any command with first, last and step values for
+  * the command keys prototype, as defined in the command table. */
+ static void waitForMultipleSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) {
+     int j, last;
+     if (cmd->vm_firstkey == 0) return;
+     last = cmd->vm_lastkey;
+     if (last < 0) last = argc+last;
+     for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep) {
+         redisAssert(j < argc);
+         waitForSwappedKey(c,argv[j]);
+     }
+ }
+ /* Preload keys needed for the ZUNION and ZINTER commands.
+  * Note that the number of keys to preload is user-defined, so we need to
+  * apply a sanity check against argc. */
+ static void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) {
      int i, num;
-     num = atoi(c->argv[2]->ptr);
+     REDIS_NOTUSED(cmd);
+     num = atoi(argv[2]->ptr);
+     if (num > (argc-3)) return;
      for (i = 0; i < num; i++) {
-         waitForSwappedKey(c,c->argv[3+i]);
+         waitForSwappedKey(c,argv[3+i]);
+     }
+ }
+ /* Preload keys needed to execute the entire MULTI/EXEC block.
+  *
+  * This function is called by blockClientOnSwappedKeys when EXEC is issued,
+  * and will block the client when any command requires a swapped out value. */
+ static void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) {
+     int i, margc;
+     struct redisCommand *mcmd;
+     robj **margv;
+     REDIS_NOTUSED(cmd);
+     REDIS_NOTUSED(argc);
+     REDIS_NOTUSED(argv);
+     if (!(c->flags & REDIS_MULTI)) return;
+     for (i = 0; i < c->mstate.count; i++) {
+         mcmd = c->mstate.commands[i].cmd;
+         margc = c->mstate.commands[i].argc;
+         margv = c->mstate.commands[i].argv;
+         if (mcmd->vm_preload_proc != NULL) {
+             mcmd->vm_preload_proc(c,mcmd,margc,margv);
+         } else {
+             waitForMultipleSwappedKeys(c,mcmd,margc,margv);
+         }
      }
  }
  
   *
   * Return 1 if the client is marked as blocked, 0 if the client can
   * continue as the keys it is going to access appear to be in memory. */
- static int blockClientOnSwappedKeys(struct redisCommand *cmd, redisClient *c) {
-     int j, last;
+ static int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd) {
      if (cmd->vm_preload_proc != NULL) {
-         cmd->vm_preload_proc(c);
+         cmd->vm_preload_proc(c,cmd,c->argc,c->argv);
      } else {
-         if (cmd->vm_firstkey == 0) return 0;
-         last = cmd->vm_lastkey;
-         if (last < 0) last = c->argc+last;
-         for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep)
-             waitForSwappedKey(c,c->argv[j]);
+         waitForMultipleSwappedKeys(c,cmd,c->argc,c->argv);
      }
  
      /* If the client was blocked for at least one key, mark it as blocked. */
@@@ -9716,7 -9659,7 +9779,7 @@@ static int dontWaitForSwappedKey(redisC
      /* Remove the key from the list of keys this client is waiting for. */
      listRewind(c->io_keys,&li);
      while ((ln = listNext(&li)) != NULL) {
 -        if (compareStringObjects(ln->value,key) == 0) {
 +        if (equalStringObjects(ln->value,key)) {
              listDelNode(c->io_keys,ln);
              break;
          }
@@@ -9776,50 -9719,6 +9839,50 @@@ static void configSetCommand(redisClien
          server.masterauth = zstrdup(o->ptr);
      } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
          server.maxmemory = strtoll(o->ptr, NULL, 10);
 +    } else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
 +        if (!strcasecmp(o->ptr,"no")) {
 +            server.appendfsync = APPENDFSYNC_NO;
 +        } else if (!strcasecmp(o->ptr,"everysec")) {
 +            server.appendfsync = APPENDFSYNC_EVERYSEC;
 +        } else if (!strcasecmp(o->ptr,"always")) {
 +            server.appendfsync = APPENDFSYNC_ALWAYS;
 +        } else {
 +            goto badfmt;
 +        }
 +    } else if (!strcasecmp(c->argv[2]->ptr,"save")) {
 +        int vlen, j;
 +        sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
 +
 +        /* Perform sanity check before setting the new config:
 +         * - Even number of args
 +         * - Seconds >= 1, changes >= 0 */
 +        if (vlen & 1) {
 +            sdsfreesplitres(v,vlen);
 +            goto badfmt;
 +        }
 +        for (j = 0; j < vlen; j++) {
 +            char *eptr;
 +            long val;
 +
 +            val = strtoll(v[j], &eptr, 10);
 +            if (eptr[0] != '\0' ||
 +                ((j & 1) == 0 && val < 1) ||
 +                ((j & 1) == 1 && val < 0)) {
 +                sdsfreesplitres(v,vlen);
 +                goto badfmt;
 +            }
 +        }
 +        /* Finally set the new config */
 +        resetServerSaveParams();
 +        for (j = 0; j < vlen; j += 2) {
 +            time_t seconds;
 +            int changes;
 +
 +            seconds = strtoll(v[j],NULL,10);
 +            changes = strtoll(v[j+1],NULL,10);
 +            appendServerSaveParams(seconds, changes);
 +        }
 +        sdsfreesplitres(v,vlen);
      } else {
          addReplySds(c,sdscatprintf(sdsempty(),
              "-ERR not supported CONFIG parameter %s\r\n",
      }
      decrRefCount(o);
      addReply(c,shared.ok);
 +    return;
 +
 +badfmt: /* Bad format errors */
 +    addReplySds(c,sdscatprintf(sdsempty(),
 +        "-ERR invalid argument '%s' for CONFIG SET '%s'\r\n",
 +            (char*)o->ptr,
 +            (char*)c->argv[2]->ptr));
 +    decrRefCount(o);
  }
  
  static void configGetCommand(redisClient *c) {
          addReplyBulkCString(c,buf);
          matches++;
      }
 +    if (stringmatch(pattern,"appendfsync",0)) {
 +        char *policy;
 +
 +        switch(server.appendfsync) {
 +        case APPENDFSYNC_NO: policy = "no"; break;
 +        case APPENDFSYNC_EVERYSEC: policy = "everysec"; break;
 +        case APPENDFSYNC_ALWAYS: policy = "always"; break;
 +        default: policy = "unknown"; break; /* too harmless to panic */
 +        }
 +        addReplyBulkCString(c,"appendfsync");
 +        addReplyBulkCString(c,policy);
 +        matches++;
 +    }
 +    if (stringmatch(pattern,"save",0)) {
 +        sds buf = sdsempty();
 +        int j;
 +
 +        for (j = 0; j < server.saveparamslen; j++) {
 +            buf = sdscatprintf(buf,"%ld %d",
 +                    server.saveparams[j].seconds,
 +                    server.saveparams[j].changes);
 +            if (j != server.saveparamslen-1)
 +                buf = sdscatlen(buf," ",1);
 +        }
 +        addReplyBulkCString(c,"save");
 +        addReplyBulkCString(c,buf);
 +        sdsfree(buf);
 +        matches++;
 +    }
      decrRefCount(o);
      lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2);
  }
@@@ -9943,7 -9805,7 +10006,7 @@@ static int listMatchPubsubPattern(void 
      pubsubPattern *pa = a, *pb = b;
  
      return (pa->client == pb->client) &&
 -           (compareStringObjects(pa->pattern,pb->pattern) == 0);
 +           (equalStringObjects(pa->pattern,pb->pattern));
  }
  
  /* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
@@@ -10280,25 -10142,6 +10343,25 @@@ static void debugCommand(redisClient *c
          } else {
              addReply(c,shared.err);
          }
 +    } else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
 +        long keys, j;
 +        robj *key, *val;
 +        char buf[128];
 +
 +        if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
 +            return;
 +        for (j = 0; j < keys; j++) {
 +            snprintf(buf,sizeof(buf),"key:%lu",j);
 +            key = createStringObject(buf,strlen(buf));
 +            if (lookupKeyRead(c->db,key) != NULL) {
 +                decrRefCount(key);
 +                continue;
 +            }
 +            snprintf(buf,sizeof(buf),"value:%lu",j);
 +            val = createStringObject(buf,strlen(buf));
 +            dictAdd(c->db->dict,key,val);
 +        }
 +        addReply(c,shared.ok);
      } else {
          addReplySds(c,sdsnew(
              "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n"));
diff --combined redis.conf
index f8e64dc76c6d0965f7804aef41383628933b96df,4d3689ed00b5a0e3c6610ad0069ab945ab7c9b9f..c48a2fb82a345baf9c0a73c055883b04e876cf62
@@@ -112,10 -112,6 +112,10 @@@ dir .
  #
  # This should stay commented out for backward compatibility and because most
  # people do not need auth (e.g. they run their own servers).
 +# 
 +# Warning: since Redis is pretty fast an outside user can try up to
 +# 150k passwords per second against a good box. This means that you should
 +# use a very strong password otherwise it will be very easy to break.
  #
  # requirepass foobared
  
  # Still if append only mode is enabled Redis will load the data from the
  # log file at startup ignoring the dump.rdb file.
  #
- # The name of the append only file is "appendonly.aof"
- #
  # IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
  # log file in background when it gets too big.
  
  appendonly no
  
+ # The name of the append only file (default: "appendonly.aof")
+ # appendfilename appendonly.aof
  # The fsync() call tells the Operating System to actually write data on disk
  # instead to wait for more data in the output buffer. Some OS will really flush 
  # data on disk, some other OS will just try to do it ASAP.