]> git.saurik.com Git - redis.git/commitdiff
Merge http://github.com/ngmoco/redis
authorantirez <antirez@gmail.com>
Thu, 20 May 2010 10:38:43 +0000 (12:38 +0200)
committerantirez <antirez@gmail.com>
Thu, 20 May 2010 10:38:43 +0000 (12:38 +0200)
1  2 
redis.c

diff --combined redis.c
index c0f2d0faf3f6ed375d34a44822027efc235cc2df,0488d7f8204e462d95a3f85c2293520291bf83ea..97b0c5c9a75bc85cb6a76744eb1e95dd08fab500
+++ b/redis.c
@@@ -27,7 -27,7 +27,7 @@@
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
 -#define REDIS_VERSION "1.3.10"
 +#define REDIS_VERSION "1.3.12"
  
  #include "fmacros.h"
  #include "config.h"
@@@ -74,9 -74,7 +74,9 @@@
  #include "zmalloc.h" /* total memory usage aware version of malloc/free */
  #include "lzf.h"    /* LZF compression library */
  #include "pqsort.h" /* Partial qsort for SORT+LIMIT */
 -#include "zipmap.h"
 +#include "zipmap.h" /* Compact dictionary-alike data structure */
 +#include "sha1.h"   /* SHA1 is used for DEBUG DIGEST */
 +#include "release.h" /* Release and/or git repository information */
  
  /* Error codes */
  #define REDIS_OK                0
@@@ -724,8 -722,8 +724,8 @@@ static void hmgetCommand(redisClient *c
  static void hdelCommand(redisClient *c);
  static void hlenCommand(redisClient *c);
  static void zremrangebyrankCommand(redisClient *c);
 -static void zunionCommand(redisClient *c);
 -static void zinterCommand(redisClient *c);
 +static void zunionstoreCommand(redisClient *c);
 +static void zinterstoreCommand(redisClient *c);
  static void hkeysCommand(redisClient *c);
  static void hvalsCommand(redisClient *c);
  static void hgetallCommand(redisClient *c);
@@@ -786,8 -784,8 +786,8 @@@ static struct redisCommand cmdTable[] 
      {"zrem",zremCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
      {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
      {"zremrangebyrank",zremrangebyrankCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
 -    {"zunion",zunionCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
 -    {"zinter",zinterCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
 +    {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
 +    {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
      {"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1},
      {"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1},
      {"zcount",zcountCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
@@@ -2761,6 -2759,21 +2761,6 @@@ static void addReplyDouble(redisClient 
          (unsigned long) strlen(buf),buf));
  }
  
 -static void addReplyLong(redisClient *c, long l) {
 -    char buf[128];
 -    size_t len;
 -
 -    if (l == 0) {
 -        addReply(c,shared.czero);
 -        return;
 -    } else if (l == 1) {
 -        addReply(c,shared.cone);
 -        return;
 -    }
 -    len = snprintf(buf,sizeof(buf),":%ld\r\n",l);
 -    addReplySds(c,sdsnewlen(buf,len));
 -}
 -
  static void addReplyLongLong(redisClient *c, long long ll) {
      char buf[128];
      size_t len;
          addReply(c,shared.cone);
          return;
      }
 -    len = snprintf(buf,sizeof(buf),":%lld\r\n",ll);
 -    addReplySds(c,sdsnewlen(buf,len));
 +    buf[0] = ':';
 +    len = ll2string(buf+1,sizeof(buf)-1,ll);
 +    buf[len+1] = '\r';
 +    buf[len+2] = '\n';
 +    addReplySds(c,sdsnewlen(buf,len+3));
  }
  
  static void addReplyUlong(redisClient *c, unsigned long ul) {
  }
  
  static void addReplyBulkLen(redisClient *c, robj *obj) {
 -    size_t len;
 +    size_t len, intlen;
 +    char buf[128];
  
      if (obj->encoding == REDIS_ENCODING_RAW) {
          len = sdslen(obj->ptr);
              len++;
          }
      }
 -    addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",(unsigned long)len));
 +    buf[0] = '$';
 +    intlen = ll2string(buf+1,sizeof(buf)-1,(long long)len);
 +    buf[intlen+1] = '\r';
 +    buf[intlen+2] = '\n';
 +    addReplySds(c,sdsnewlen(buf,intlen+3));
  }
  
  static void addReplyBulk(redisClient *c, robj *obj) {
@@@ -4149,6 -4154,41 +4149,41 @@@ eoferr: /* unexpected end of file is ha
      return REDIS_ERR; /* Just to avoid warning */
  }
  
+ /*================================== Shutdown =============================== */
+ static void redisShutdown() {
+     redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
+     /* Kill the saving child if there is a background saving in progress.
+        We want to avoid race conditions, for instance our saving child may
+        overwrite the synchronous saving did by SHUTDOWN. */
+     if (server.bgsavechildpid != -1) {
+         redisLog(REDIS_WARNING,"There is a live saving child. Killing it!");
+         kill(server.bgsavechildpid,SIGKILL);
+         rdbRemoveTempFile(server.bgsavechildpid);
+     }
+     if (server.appendonly) {
+         /* Append only file: fsync() the AOF and exit */
+         fsync(server.appendfd);
+         if (server.vm_enabled) unlink(server.vm_swap_file);
+         exit(0);
+     } else {
+         /* Snapshotting. Perform a SYNC SAVE and exit */
+         if (rdbSave(server.dbfilename) == REDIS_OK) {
+             if (server.daemonize)
+                 unlink(server.pidfile);
+             redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory());
+             redisLog(REDIS_WARNING,"Server exit now, bye bye...");
+             exit(0);
+         } else {
+             /* Ooops.. error saving! The best we can do is to continue
+              * operating. Note that if there was a background saving process,
+              * in the next cron() Redis will be notified that the background
+              * saving aborted, handling special stuff like slaves pending for
+              * synchronization... */
+             redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
+         }
+     }
+ }
  /*================================== Commands =============================== */
  
  static void authCommand(redisClient *c) {
@@@ -4328,7 -4368,8 +4363,7 @@@ static void incrDecrCommand(redisClien
      if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
  
      value += incr;
 -    o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
 -    o = tryObjectEncoding(o);
 +    o = createStringObjectFromLongLong(value);
      retval = dictAdd(c->db->dict,c->argv[1],o);
      if (retval == DICT_ERR) {
          dictReplace(c->db->dict,c->argv[1],o);
@@@ -4458,7 -4499,7 +4493,7 @@@ static void delCommand(redisClient *c) 
              deleted++;
          }
      }
 -    addReplyLong(c,deleted);
 +    addReplyLongLong(c,deleted);
  }
  
  static void existsCommand(redisClient *c) {
@@@ -4588,39 -4629,10 +4623,10 @@@ static void bgsaveCommand(redisClient *
  }
  
  static void shutdownCommand(redisClient *c) {
-     redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
-     /* Kill the saving child if there is a background saving in progress.
-        We want to avoid race conditions, for instance our saving child may
-        overwrite the synchronous saving did by SHUTDOWN. */
-     if (server.bgsavechildpid != -1) {
-         redisLog(REDIS_WARNING,"There is a live saving child. Killing it!");
-         kill(server.bgsavechildpid,SIGKILL);
-         rdbRemoveTempFile(server.bgsavechildpid);
-     }
-     if (server.appendonly) {
-         /* Append only file: fsync() the AOF and exit */
-         fsync(server.appendfd);
-         if (server.vm_enabled) unlink(server.vm_swap_file);
-         exit(0);
-     } else {
-         /* Snapshotting. Perform a SYNC SAVE and exit */
-         if (rdbSave(server.dbfilename) == REDIS_OK) {
-             if (server.daemonize)
-                 unlink(server.pidfile);
-             redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory());
-             redisLog(REDIS_WARNING,"Server exit now, bye bye...");
-             exit(0);
-         } else {
-             /* Ooops.. error saving! The best we can do is to continue
-              * operating. Note that if there was a background saving process,
-              * in the next cron() Redis will be notified that the background
-              * saving aborted, handling special stuff like slaves pending for
-              * synchronization... */
-             redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
-             addReplySds(c,
-                 sdsnew("-ERR can't quit, problems saving the DB\r\n"));
-         }
-     }
+     redisShutdown();
+     /* If we got here exit was not called so there was an error */
+     addReplySds(c,
+         sdsnew("-ERR can't quit, problems saving the DB\r\n"));
  }
  
  static void renameGenericCommand(redisClient *c, int nx) {
@@@ -4743,7 -4755,7 +4749,7 @@@ static void pushGenericCommand(redisCli
          incrRefCount(c->argv[2]);
      }
      server.dirty++;
 -    addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",listLength(list)));
 +    addReplyLongLong(c,listLength(list));
  }
  
  static void lpushCommand(redisClient *c) {
@@@ -5245,7 -5257,7 +5251,7 @@@ static void sinterGenericCommand(redisC
          if (dictSize((dict*)dstset->ptr) > 0) {
              dictAdd(c->db->dict,dstkey,dstset);
              incrRefCount(dstkey);
 -            addReplyLong(c,dictSize((dict*)dstset->ptr));
 +            addReplyLongLong(c,dictSize((dict*)dstset->ptr));
          } else {
              decrRefCount(dstset);
              addReply(c,shared.czero);
@@@ -5348,7 -5360,7 +5354,7 @@@ static void sunionDiffGenericCommand(re
          if (dictSize((dict*)dstset->ptr) > 0) {
              dictAdd(c->db->dict,dstkey,dstset);
              incrRefCount(dstkey);
 -            addReplyLong(c,dictSize((dict*)dstset->ptr));
 +            addReplyLongLong(c,dictSize((dict*)dstset->ptr));
          } else {
              decrRefCount(dstset);
              addReply(c,shared.czero);
@@@ -5827,7 -5839,7 +5833,7 @@@ static void zremrangebyscoreCommand(red
      if (htNeedsResize(zs->dict)) dictResize(zs->dict);
      if (dictSize(zs->dict) == 0) deleteKey(c->db,c->argv[1]);
      server.dirty += deleted;
 -    addReplyLong(c,deleted);
 +    addReplyLongLong(c,deleted);
  }
  
  static void zremrangebyrankCommand(redisClient *c) {
      if (htNeedsResize(zs->dict)) dictResize(zs->dict);
      if (dictSize(zs->dict) == 0) deleteKey(c->db,c->argv[1]);
      server.dirty += deleted;
 -    addReplyLong(c, deleted);
 +    addReplyLongLong(c, deleted);
  }
  
  typedef struct {
@@@ -5910,7 -5922,7 +5916,7 @@@ static void zunionInterGenericCommand(r
      /* expect zsetnum input keys to be given */
      zsetnum = atoi(c->argv[2]->ptr);
      if (zsetnum < 1) {
 -        addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNION/ZINTER\r\n"));
 +        addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE\r\n"));
          return;
      }
  
      if (dstzset->zsl->length) {
          dictAdd(c->db->dict,dstkey,dstobj);
          incrRefCount(dstkey);
 -        addReplyLong(c, dstzset->zsl->length);
 +        addReplyLongLong(c, dstzset->zsl->length);
          server.dirty++;
      } else {
          decrRefCount(dstobj);
      zfree(src);
  }
  
 -static void zunionCommand(redisClient *c) {
 +static void zunionstoreCommand(redisClient *c) {
      zunionInterGenericCommand(c,c->argv[1], REDIS_OP_UNION);
  }
  
 -static void zinterCommand(redisClient *c) {
 +static void zinterstoreCommand(redisClient *c) {
      zunionInterGenericCommand(c,c->argv[1], REDIS_OP_INTER);
  }
  
@@@ -6247,7 -6259,7 +6253,7 @@@ static void genericZrangebyscoreCommand
                  if (limit > 0) limit--;
              }
              if (justcount) {
 -                addReplyLong(c,(long)rangelen);
 +                addReplyLongLong(c,(long)rangelen);
              } else {
                  lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",
                       withscores ? (rangelen*2) : rangelen);
@@@ -6317,9 -6329,9 +6323,9 @@@ static void zrankGenericCommand(redisCl
      rank = zslGetRank(zsl, *score, c->argv[2]);
      if (rank) {
          if (reverse) {
 -            addReplyLong(c, zsl->length - rank);
 +            addReplyLongLong(c, zsl->length - rank);
          } else {
 -            addReplyLong(c, rank-1);
 +            addReplyLongLong(c, rank-1);
          }
      } else {
          addReply(c,shared.nullbulk);
@@@ -7176,8 -7188,6 +7182,8 @@@ static sds genRedisInfoString(void) 
      bytesToHuman(hmem,zmalloc_used_memory());
      info = sdscatprintf(sdsempty(),
          "redis_version:%s\r\n"
 +        "redis_git_sha1:%s\r\n"
 +        "redis_git_dirty:%d\r\n"
          "arch_bits:%s\r\n"
          "multiplexing_api:%s\r\n"
          "process_id:%ld\r\n"
          "total_connections_received:%lld\r\n"
          "total_commands_processed:%lld\r\n"
          "expired_keys:%lld\r\n"
 -        "hash_max_zipmap_entries:%ld\r\n"
 -        "hash_max_zipmap_value:%ld\r\n"
 +        "hash_max_zipmap_entries:%zu\r\n"
 +        "hash_max_zipmap_value:%zu\r\n"
          "pubsub_channels:%ld\r\n"
          "pubsub_patterns:%u\r\n"
          "vm_enabled:%d\r\n"
          "role:%s\r\n"
          ,REDIS_VERSION,
 +        REDIS_GIT_SHA1,
 +        strtol(REDIS_GIT_DIRTY,NULL,10) > 0,
          (sizeof(long) == 8) ? "64" : "32",
          aeGetApiName(),
          (long) getpid(),
@@@ -8724,48 -8732,6 +8730,48 @@@ static void aofRemoveTempFile(pid_t chi
   * as a fully non-blocking VM.
   */
  
 +/* Called when the user switches from "appendonly yes" to "appendonly no"
 + * at runtime using the CONFIG command. */
 +static void stopAppendOnly(void) {
 +    flushAppendOnlyFile();
 +    fsync(server.appendfd);
 +    close(server.appendfd);
 +
 +    server.appendfd = -1;
 +    server.appendseldb = -1;
 +    server.appendonly = 0;
 +    /* rewrite operation in progress? kill it, wait child exit */
 +    if (server.bgsavechildpid != -1) {
 +        int statloc;
 +
 +        if (kill(server.bgsavechildpid,SIGKILL) != -1)
 +            wait3(&statloc,0,NULL);
 +        /* reset the buffer accumulating changes while the child saves */
 +        sdsfree(server.bgrewritebuf);
 +        server.bgrewritebuf = sdsempty();
 +        server.bgsavechildpid = -1;
 +    }
 +}
 +
 +/* Called when the user switches from "appendonly no" to "appendonly yes"
 + * at runtime using the CONFIG command. */
 +static int startAppendOnly(void) {
 +    server.appendonly = 1;
 +    server.lastfsync = time(NULL);
 +    server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT,0644);
 +    if (server.appendfd == -1) {
 +        redisLog(REDIS_WARNING,"Used tried to switch on AOF via CONFIG, but I can't open the AOF file: %s",strerror(errno));
 +        return REDIS_ERR;
 +    }
 +    if (rewriteAppendOnlyFileBackground() == REDIS_ERR) {
 +        server.appendonly = 0;
 +        close(server.appendfd);
 +        redisLog(REDIS_WARNING,"Used tried to switch on AOF via CONFIG, I can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.",strerror(errno));
 +        return REDIS_ERR;
 +    }
 +    return REDIS_OK;
 +}
 +
  /* =================== Virtual Memory - Blocking Side  ====================== */
  
  static void vmInit(void) {
@@@ -9738,7 -9704,7 +9744,7 @@@ static void waitForMultipleSwappedKeys(
      }
  }
  
 -/* Preload keys needed for the ZUNION and ZINTER commands.
 +/* Preload keys needed for the ZUNIONSTORE and ZINTERSTORE 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) {
@@@ -9868,8 -9834,6 +9874,8 @@@ static void handleClientsBlockedOnSwapp
  
  static void configSetCommand(redisClient *c) {
      robj *o = getDecodedObject(c->argv[3]);
 +    long long ll;
 +
      if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
          zfree(server.dbfilename);
          server.dbfilename = zstrdup(o->ptr);
          zfree(server.masterauth);
          server.masterauth = zstrdup(o->ptr);
      } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
 -        server.maxmemory = strtoll(o->ptr, NULL, 10);
 +        if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
 +            ll < 0) goto badfmt;
 +        server.maxmemory = ll;
 +    } else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
 +        if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
 +            ll < 0 || ll > LONG_MAX) goto badfmt;
 +        server.maxidletime = ll;
      } else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
          if (!strcasecmp(o->ptr,"no")) {
              server.appendfsync = APPENDFSYNC_NO;
          } else {
              goto badfmt;
          }
 +    } else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
 +        int old = server.appendonly;
 +        int new = yesnotoi(o->ptr);
 +
 +        if (new == -1) goto badfmt;
 +        if (old != new) {
 +            if (new == 0) {
 +                stopAppendOnly();
 +            } else {
 +                if (startAppendOnly() == REDIS_ERR) {
 +                    addReplySds(c,sdscatprintf(sdsempty(),
 +                        "-ERR Unable to turn on AOF. Check server logs.\r\n"));
 +                    decrRefCount(o);
 +                    return;
 +                }
 +            }
 +        }
      } else if (!strcasecmp(c->argv[2]->ptr,"save")) {
          int vlen, j;
          sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
@@@ -9994,24 -9935,11 +10000,24 @@@ static void configGetCommand(redisClien
      if (stringmatch(pattern,"maxmemory",0)) {
          char buf[128];
  
 -        snprintf(buf,128,"%llu\n",server.maxmemory);
 +        ll2string(buf,128,server.maxmemory);
          addReplyBulkCString(c,"maxmemory");
          addReplyBulkCString(c,buf);
          matches++;
      }
 +    if (stringmatch(pattern,"timeout",0)) {
 +        char buf[128];
 +
 +        ll2string(buf,128,server.maxidletime);
 +        addReplyBulkCString(c,"timeout");
 +        addReplyBulkCString(c,buf);
 +        matches++;
 +    }
 +    if (stringmatch(pattern,"appendonly",0)) {
 +        addReplyBulkCString(c,"appendonly");
 +        addReplyBulkCString(c,server.appendonly ? "yes" : "no");
 +        matches++;
 +    }
      if (stringmatch(pattern,"appendfsync",0)) {
          char *policy;
  
@@@ -10113,7 -10041,7 +10119,7 @@@ static int pubsubSubscribeChannel(redis
      addReply(c,shared.mbulk3);
      addReply(c,shared.subscribebulk);
      addReplyBulk(c,channel);
 -    addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
 +    addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
      return retval;
  }
  
@@@ -10149,7 -10077,7 +10155,7 @@@ static int pubsubUnsubscribeChannel(red
          addReply(c,shared.mbulk3);
          addReply(c,shared.unsubscribebulk);
          addReplyBulk(c,channel);
 -        addReplyLong(c,dictSize(c->pubsub_channels)+
 +        addReplyLongLong(c,dictSize(c->pubsub_channels)+
                         listLength(c->pubsub_patterns));
  
      }
@@@ -10175,7 -10103,7 +10181,7 @@@ static int pubsubSubscribePattern(redis
      addReply(c,shared.mbulk3);
      addReply(c,shared.psubscribebulk);
      addReplyBulk(c,pattern);
 -    addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
 +    addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
      return retval;
  }
  
@@@ -10200,7 -10128,7 +10206,7 @@@ static int pubsubUnsubscribePattern(red
          addReply(c,shared.mbulk3);
          addReply(c,shared.punsubscribebulk);
          addReplyBulk(c,pattern);
 -        addReplyLong(c,dictSize(c->pubsub_channels)+
 +        addReplyLongLong(c,dictSize(c->pubsub_channels)+
                         listLength(c->pubsub_patterns));
      }
      decrRefCount(pattern);
@@@ -10328,185 -10256,11 +10334,185 @@@ static void punsubscribeCommand(redisCl
  
  static void publishCommand(redisClient *c) {
      int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
 -    addReplyLong(c,receivers);
 +    addReplyLongLong(c,receivers);
  }
  
  /* ================================= Debugging ============================== */
  
 +/* Compute the sha1 of string at 's' with 'len' bytes long.
 + * The SHA1 is then xored againt the string pointed by digest.
 + * Since xor is commutative, this operation is used in order to
 + * "add" digests relative to unordered elements.
 + *
 + * So digest(a,b,c,d) will be the same of digest(b,a,c,d) */
 +static void xorDigest(unsigned char *digest, void *ptr, size_t len) {
 +    SHA1_CTX ctx;
 +    unsigned char hash[20], *s = ptr;
 +    int j;
 +
 +    SHA1Init(&ctx);
 +    SHA1Update(&ctx,s,len);
 +    SHA1Final(hash,&ctx);
 +
 +    for (j = 0; j < 20; j++)
 +        digest[j] ^= hash[j];
 +}
 +
 +static void xorObjectDigest(unsigned char *digest, robj *o) {
 +    o = getDecodedObject(o);
 +    xorDigest(digest,o->ptr,sdslen(o->ptr));
 +    decrRefCount(o);
 +}
 +
 +/* This function instead of just computing the SHA1 and xoring it
 + * against diget, also perform the digest of "digest" itself and
 + * replace the old value with the new one.
 + *
 + * So the final digest will be:
 + *
 + * digest = SHA1(digest xor SHA1(data))
 + *
 + * This function is used every time we want to preserve the order so
 + * that digest(a,b,c,d) will be different than digest(b,c,d,a)
 + *
 + * Also note that mixdigest("foo") followed by mixdigest("bar")
 + * will lead to a different digest compared to "fo", "obar".
 + */
 +static void mixDigest(unsigned char *digest, void *ptr, size_t len) {
 +    SHA1_CTX ctx;
 +    char *s = ptr;
 +
 +    xorDigest(digest,s,len);
 +    SHA1Init(&ctx);
 +    SHA1Update(&ctx,digest,20);
 +    SHA1Final(digest,&ctx);
 +}
 +
 +static void mixObjectDigest(unsigned char *digest, robj *o) {
 +    o = getDecodedObject(o);
 +    mixDigest(digest,o->ptr,sdslen(o->ptr));
 +    decrRefCount(o);
 +}
 +
 +/* Compute the dataset digest. Since keys, sets elements, hashes elements
 + * are not ordered, we use a trick: every aggregate digest is the xor
 + * of the digests of their elements. This way the order will not change
 + * the result. For list instead we use a feedback entering the output digest
 + * as input in order to ensure that a different ordered list will result in
 + * a different digest. */
 +static void computeDatasetDigest(unsigned char *final) {
 +    unsigned char digest[20];
 +    char buf[128];
 +    dictIterator *di = NULL;
 +    dictEntry *de;
 +    int j;
 +    uint32_t aux;
 +
 +    memset(final,0,20); /* Start with a clean result */
 +
 +    for (j = 0; j < server.dbnum; j++) {
 +        redisDb *db = server.db+j;
 +
 +        if (dictSize(db->dict) == 0) continue;
 +        di = dictGetIterator(db->dict);
 +
 +        /* hash the DB id, so the same dataset moved in a different
 +         * DB will lead to a different digest */
 +        aux = htonl(j);
 +        mixDigest(final,&aux,sizeof(aux));
 +
 +        /* Iterate this DB writing every entry */
 +        while((de = dictNext(di)) != NULL) {
 +            robj *key, *o;
 +            time_t expiretime;
 +
 +            memset(digest,0,20); /* This key-val digest */
 +            key = dictGetEntryKey(de);
 +            mixObjectDigest(digest,key);
 +            if (!server.vm_enabled || key->storage == REDIS_VM_MEMORY ||
 +                key->storage == REDIS_VM_SWAPPING) {
 +                o = dictGetEntryVal(de);
 +                incrRefCount(o);
 +            } else {
 +                o = vmPreviewObject(key);
 +            }
 +            aux = htonl(o->type);
 +            mixDigest(digest,&aux,sizeof(aux));
 +            expiretime = getExpire(db,key);
 +
 +            /* Save the key and associated value */
 +            if (o->type == REDIS_STRING) {
 +                mixObjectDigest(digest,o);
 +            } else if (o->type == REDIS_LIST) {
 +                list *list = o->ptr;
 +                listNode *ln;
 +                listIter li;
 +
 +                listRewind(list,&li);
 +                while((ln = listNext(&li))) {
 +                    robj *eleobj = listNodeValue(ln);
 +
 +                    mixObjectDigest(digest,eleobj);
 +                }
 +            } else if (o->type == REDIS_SET) {
 +                dict *set = o->ptr;
 +                dictIterator *di = dictGetIterator(set);
 +                dictEntry *de;
 +
 +                while((de = dictNext(di)) != NULL) {
 +                    robj *eleobj = dictGetEntryKey(de);
 +
 +                    xorObjectDigest(digest,eleobj);
 +                }
 +                dictReleaseIterator(di);
 +            } else if (o->type == REDIS_ZSET) {
 +                zset *zs = o->ptr;
 +                dictIterator *di = dictGetIterator(zs->dict);
 +                dictEntry *de;
 +
 +                while((de = dictNext(di)) != NULL) {
 +                    robj *eleobj = dictGetEntryKey(de);
 +                    double *score = dictGetEntryVal(de);
 +                    unsigned char eledigest[20];
 +
 +                    snprintf(buf,sizeof(buf),"%.17g",*score);
 +                    memset(eledigest,0,20);
 +                    mixObjectDigest(eledigest,eleobj);
 +                    mixDigest(eledigest,buf,strlen(buf));
 +                    xorDigest(digest,eledigest,20);
 +                }
 +                dictReleaseIterator(di);
 +            } else if (o->type == REDIS_HASH) {
 +                hashIterator *hi;
 +                robj *obj;
 +
 +                hi = hashInitIterator(o);
 +                while (hashNext(hi) != REDIS_ERR) {
 +                    unsigned char eledigest[20];
 +
 +                    memset(eledigest,0,20);
 +                    obj = hashCurrent(hi,REDIS_HASH_KEY);
 +                    mixObjectDigest(eledigest,obj);
 +                    decrRefCount(obj);
 +                    obj = hashCurrent(hi,REDIS_HASH_VALUE);
 +                    mixObjectDigest(eledigest,obj);
 +                    decrRefCount(obj);
 +                    xorDigest(digest,eledigest,20);
 +                }
 +                hashReleaseIterator(hi);
 +            } else {
 +                redisPanic("Unknown object type");
 +            }
 +            decrRefCount(o);
 +            /* If the key has an expire, add it to the mix */
 +            if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
 +            /* We can finally xor the key-val digest to the final digest */
 +            xorDigest(final,digest,20);
 +        }
 +        dictReleaseIterator(di);
 +    }
 +}
 +
  static void debugCommand(redisClient *c) {
      if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
          *((char*)-1) = 'x';
              dictAdd(c->db->dict,key,val);
          }
          addReply(c,shared.ok);
 +    } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
 +        unsigned char digest[20];
 +        sds d = sdsnew("+");
 +        int j;
 +
 +        computeDatasetDigest(digest);
 +        for (j = 0; j < 20; j++)
 +            d = sdscatprintf(d, "%02x",digest[j]);
 +
 +        d = sdscatlen(d,"\r\n",2);
 +        addReplySds(c,d);
      } else {
          addReplySds(c,sdsnew(
              "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n"));
  
  static void _redisAssert(char *estr, char *file, int line) {
      redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
 -    redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true\n",file,line,estr);
 +    redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
  #ifdef HAVE_BACKTRACE
      redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
      *((char*)-1) = 'x';
@@@ -10812,6 -10555,15 +10818,15 @@@ static void segvHandler(int sig, siginf
      _exit(0);
  }
  
+ static void sigHandler(int sig) {
+     redisLog(REDIS_WARNING,
+         "======= Redis %s got signal: -%d- =======", REDIS_VERSION, sig);
+     if (sig == SIGTERM) {
+         redisShutdown();
+     }
+ }
  static void setupSigSegvAction(void) {
      struct sigaction act;
  
      sigaction (SIGFPE, &act, NULL);
      sigaction (SIGILL, &act, NULL);
      sigaction (SIGBUS, &act, NULL);
+     act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
+     act.sa_handler = sigHandler;
+     sigaction (SIGTERM, &act, NULL);
      return;
  }