struct redisServer server; /* server global state */
  struct redisCommand *commandTable;
  struct redisCommand readonlyCommandTable[] = {
-     {"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},
-     {"strlen",strlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"mget",mgetCommand,-2,REDIS_CMD_INLINE,NULL,1,-1,1},
-     {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"rpushx",rpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"lpushx",lpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"linsert",linsertCommand,5,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"rpop",rpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"lpop",lpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"brpop",brpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"blpop",blpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"llen",llenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"lindex",lindexCommand,3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"lrange",lrangeCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"lrem",lremCommand,4,REDIS_CMD_BULK,NULL,1,1,1},
-     {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,2,1},
-     {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"srem",sremCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"smove",smoveCommand,4,REDIS_CMD_BULK,NULL,1,2,1},
-     {"sismember",sismemberCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"scard",scardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"spop",spopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1},
-     {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1},
-     {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1},
-     {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1},
-     {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1},
-     {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1},
-     {"smembers",sinterCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"zincrby",zincrbyCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"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},
-     {"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},
-     {"zrevrangebyscore",zrevrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"zcount",zcountCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"zcard",zcardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"hsetnx",hsetnxCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"hmset",hmsetCommand,-4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"hmget",hmgetCommand,-3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"hvals",hvalsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"hgetall",hgetallCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"hexists",hexistsCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
-     {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2},
-     {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2},
-     {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"select",selectCommand,2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"move",moveCommand,3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"rename",renameCommand,3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"expire",expireCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"expireat",expireatCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"keys",keysCommand,2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"auth",authCommand,2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"ping",pingCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"echo",echoCommand,2,REDIS_CMD_BULK,NULL,0,0,0},
-     {"save",saveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"bgrewriteaof",bgrewriteaofCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"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,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},
-     {"flushall",flushallCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
-     {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0},
-     {"subscribe",subscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"unsubscribe",unsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"psubscribe",psubscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"punsubscribe",punsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"publish",publishCommand,3,REDIS_CMD_BULK|REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0},
-     {"watch",watchCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
-     {"unwatch",unwatchCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}
+     {"get",getCommand,2,0,NULL,1,1,1},
+     {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
+     {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
+     {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0},
+     {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"substr",substrCommand,4,0,NULL,1,1,1},
+     {"strlen",strlenCommand,2,0,NULL,1,1,1},
+     {"del",delCommand,-2,0,NULL,0,0,0},
+     {"exists",existsCommand,2,0,NULL,1,1,1},
+     {"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"mget",mgetCommand,-2,0,NULL,1,-1,1},
+     {"rpush",rpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"lpush",lpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"rpushx",rpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"lpushx",lpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"linsert",linsertCommand,5,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"rpop",rpopCommand,2,0,NULL,1,1,1},
+     {"lpop",lpopCommand,2,0,NULL,1,1,1},
+     {"brpop",brpopCommand,-3,0,NULL,1,1,1},
+     {"blpop",blpopCommand,-3,0,NULL,1,1,1},
+     {"llen",llenCommand,2,0,NULL,1,1,1},
+     {"lindex",lindexCommand,3,0,NULL,1,1,1},
+     {"lset",lsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"lrange",lrangeCommand,4,0,NULL,1,1,1},
+     {"ltrim",ltrimCommand,4,0,NULL,1,1,1},
+     {"lrem",lremCommand,4,0,NULL,1,1,1},
+     {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
+     {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"srem",sremCommand,3,0,NULL,1,1,1},
+     {"smove",smoveCommand,4,0,NULL,1,2,1},
+     {"sismember",sismemberCommand,3,0,NULL,1,1,1},
+     {"scard",scardCommand,2,0,NULL,1,1,1},
+     {"spop",spopCommand,2,0,NULL,1,1,1},
+     {"srandmember",srandmemberCommand,2,0,NULL,1,1,1},
+     {"sinter",sinterCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1},
+     {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1},
+     {"sunion",sunionCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1},
+     {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1},
+     {"sdiff",sdiffCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1},
+     {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1},
+     {"smembers",sinterCommand,2,0,NULL,1,1,1},
+     {"zadd",zaddCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"zincrby",zincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"zrem",zremCommand,3,0,NULL,1,1,1},
+     {"zremrangebyscore",zremrangebyscoreCommand,4,0,NULL,1,1,1},
+     {"zremrangebyrank",zremrangebyrankCommand,4,0,NULL,1,1,1},
+     {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
+     {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0},
+     {"zrange",zrangeCommand,-4,0,NULL,1,1,1},
+     {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1},
++    {"zrevrangebyscore",zrevrangebyscoreCommand,-4,0,NULL,1,1,1},
+     {"zcount",zcountCommand,4,0,NULL,1,1,1},
+     {"zrevrange",zrevrangeCommand,-4,0,NULL,1,1,1},
+     {"zcard",zcardCommand,2,0,NULL,1,1,1},
+     {"zscore",zscoreCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"zrank",zrankCommand,3,0,NULL,1,1,1},
+     {"zrevrank",zrevrankCommand,3,0,NULL,1,1,1},
+     {"hset",hsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"hsetnx",hsetnxCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"hget",hgetCommand,3,0,NULL,1,1,1},
+     {"hmset",hmsetCommand,-4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"hmget",hmgetCommand,-3,0,NULL,1,1,1},
+     {"hincrby",hincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"hdel",hdelCommand,3,0,NULL,1,1,1},
+     {"hlen",hlenCommand,2,0,NULL,1,1,1},
+     {"hkeys",hkeysCommand,2,0,NULL,1,1,1},
+     {"hvals",hvalsCommand,2,0,NULL,1,1,1},
+     {"hgetall",hgetallCommand,2,0,NULL,1,1,1},
+     {"hexists",hexistsCommand,3,0,NULL,1,1,1},
+     {"incrby",incrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"decrby",decrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"getset",getsetCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"mset",msetCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2},
+     {"msetnx",msetnxCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2},
+     {"randomkey",randomkeyCommand,1,0,NULL,0,0,0},
+     {"select",selectCommand,2,0,NULL,0,0,0},
+     {"move",moveCommand,3,0,NULL,1,1,1},
+     {"rename",renameCommand,3,0,NULL,1,1,1},
+     {"renamenx",renamenxCommand,3,0,NULL,1,1,1},
+     {"expire",expireCommand,3,0,NULL,0,0,0},
+     {"expireat",expireatCommand,3,0,NULL,0,0,0},
+     {"keys",keysCommand,2,0,NULL,0,0,0},
+     {"dbsize",dbsizeCommand,1,0,NULL,0,0,0},
+     {"auth",authCommand,2,0,NULL,0,0,0},
+     {"ping",pingCommand,1,0,NULL,0,0,0},
+     {"echo",echoCommand,2,0,NULL,0,0,0},
+     {"save",saveCommand,1,0,NULL,0,0,0},
+     {"bgsave",bgsaveCommand,1,0,NULL,0,0,0},
+     {"bgrewriteaof",bgrewriteaofCommand,1,0,NULL,0,0,0},
+     {"shutdown",shutdownCommand,1,0,NULL,0,0,0},
+     {"lastsave",lastsaveCommand,1,0,NULL,0,0,0},
+     {"type",typeCommand,2,0,NULL,1,1,1},
+     {"multi",multiCommand,1,0,NULL,0,0,0},
+     {"exec",execCommand,1,REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0},
+     {"discard",discardCommand,1,0,NULL,0,0,0},
+     {"sync",syncCommand,1,0,NULL,0,0,0},
+     {"flushdb",flushdbCommand,1,0,NULL,0,0,0},
+     {"flushall",flushallCommand,1,0,NULL,0,0,0},
+     {"sort",sortCommand,-2,REDIS_CMD_DENYOOM,NULL,1,1,1},
+     {"info",infoCommand,1,0,NULL,0,0,0},
+     {"monitor",monitorCommand,1,0,NULL,0,0,0},
+     {"ttl",ttlCommand,2,0,NULL,1,1,1},
+     {"persist",persistCommand,2,0,NULL,1,1,1},
+     {"slaveof",slaveofCommand,3,0,NULL,0,0,0},
+     {"debug",debugCommand,-2,0,NULL,0,0,0},
+     {"config",configCommand,-2,0,NULL,0,0,0},
+     {"subscribe",subscribeCommand,-2,0,NULL,0,0,0},
+     {"unsubscribe",unsubscribeCommand,-1,0,NULL,0,0,0},
+     {"psubscribe",psubscribeCommand,-2,0,NULL,0,0,0},
+     {"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0},
+     {"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0},
+     {"watch",watchCommand,-2,0,NULL,0,0,0},
+     {"unwatch",unwatchCommand,1,0,NULL,0,0,0}
  };
  
  /*============================ Utility functions ============================ */
      }
  }
  
 +void updateLRUClock(void) {
 +    server.lruclock = (time(NULL)/REDIS_LRU_CLOCK_RESOLUTION) &
 +                                                REDIS_LRU_CLOCK_MAX;
 +}
  
  int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
      int j, loops = server.cronloops++;
       * in objects at every object access, and accuracy is not needed.
       * To access a global var is faster than calling time(NULL) */
      server.unixtime = time(NULL);
 -    /* We have just 21 bits per object for LRU information.
 -     * So we use an (eventually wrapping) LRU clock with minutes resolution.
 +    /* We have just 22 bits per object for LRU information.
 +     * So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
 +     * 2^22 bits with 10 seconds resoluton is more or less 1.5 years.
       *
 -     * When we need to select what object to swap, we compute the minimum
 -     * time distance between the current lruclock and the object last access
 -     * lruclock info. Even if clocks will wrap on overflow, there is
 -     * the interesting property that we are sure that at least
 -     * ABS(A-B) minutes passed between current time and timestamp B.
 +     * Note that even if this will wrap after 1.5 years it's not a problem,
 +     * everything will still work but just some object will appear younger
 +     * to Redis. But for this to happen a given object should never be touched
 +     * for 1.5 years.
       *
 -     * This is not precise but we don't need at all precision, but just
 -     * something statistically reasonable.
 +     * Note that you can change the resolution altering the
 +     * REDIS_LRU_CLOCK_RESOLUTION define.
       */
 -    server.lruclock = (time(NULL)/60)&((1<<21)-1);
 +    updateLRUClock();
  
      /* We received a SIGTERM, shutting down here in a safe way, as it is
       * not ok doing so inside the signal handler. */
      server.maxclients = 0;
      server.blpop_blocked_clients = 0;
      server.maxmemory = 0;
 +    server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
 +    server.maxmemory_samples = 3;
      server.vm_enabled = 0;
      server.vm_swap_file = zstrdup("/tmp/redis-%p.vm");
      server.vm_page_size = 256;          /* 256 bytes per page */
      server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
      server.shutdown_asap = 0;
  
 +    updateLRUClock();
      resetServerSaveParams();
  
      appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
      server.stat_numconnections = 0;
      server.stat_expiredkeys = 0;
      server.stat_starttime = time(NULL);
 +    server.stat_keyspace_misses = 0;
 +    server.stat_keyspace_hits = 0;
      server.unixtime = time(NULL);
      aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
      if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
  int processCommand(redisClient *c) {
      struct redisCommand *cmd;
  
-     /* Handle the multi bulk command type. This is an alternative protocol
-      * supported by Redis in order to receive commands that are composed of
-      * multiple binary-safe "bulk" arguments. The latency of processing is
-      * a bit higher but this allows things like multi-sets, so if this
-      * protocol is used only for MSET and similar commands this is a big win. */
-     if (c->multibulk == 0 && c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '*') {
-         c->multibulk = atoi(((char*)c->argv[0]->ptr)+1);
-         if (c->multibulk <= 0) {
-             resetClient(c);
-             return 1;
-         } else {
-             decrRefCount(c->argv[c->argc-1]);
-             c->argc--;
-             return 1;
-         }
-     } else if (c->multibulk) {
-         if (c->bulklen == -1) {
-             if (((char*)c->argv[0]->ptr)[0] != '$') {
-                 addReplyError(c,"multi bulk protocol error");
-                 resetClient(c);
-                 return 1;
-             } else {
-                 char *eptr;
-                 long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10);
-                 int perr = eptr[0] != '\0';
- 
-                 decrRefCount(c->argv[0]);
-                 if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
-                     bulklen < 0 || bulklen > 1024*1024*1024)
-                 {
-                     c->argc--;
-                     addReplyError(c,"invalid bulk write count");
-                     resetClient(c);
-                     return 1;
-                 }
-                 c->argc--;
-                 c->bulklen = bulklen+2; /* add two bytes for CR+LF */
-                 return 1;
-             }
-         } else {
-             c->mbargv = zrealloc(c->mbargv,(sizeof(robj*))*(c->mbargc+1));
-             c->mbargv[c->mbargc] = c->argv[0];
-             c->mbargc++;
-             c->argc--;
-             c->multibulk--;
-             if (c->multibulk == 0) {
-                 robj **auxargv;
-                 int auxargc;
- 
-                 /* Here we need to swap the multi-bulk argc/argv with the
-                  * normal argc/argv of the client structure. */
-                 auxargv = c->argv;
-                 c->argv = c->mbargv;
-                 c->mbargv = auxargv;
- 
-                 auxargc = c->argc;
-                 c->argc = c->mbargc;
-                 c->mbargc = auxargc;
- 
-                 /* We need to set bulklen to something different than -1
-                  * in order for the code below to process the command without
-                  * to try to read the last argument of a bulk command as
-                  * a special argument. */
-                 c->bulklen = 0;
-                 /* continue below and process the command */
-             } else {
-                 c->bulklen = -1;
-                 return 1;
-             }
-         }
-     }
-     /* -- end of multi bulk commands processing -- */
- 
-     /* The QUIT command is handled as a special case. Normal command
-      * procs are unable to close the client connection safely */
+     /* The QUIT command is handled separately. Normal command procs will
+      * go through checking for replication and QUIT will cause trouble
+      * when FORCE_REPLICATION is enabled and would be implemented in
+      * a regular command proc. */
      if (!strcasecmp(c->argv[0]->ptr,"quit")) {
-         freeClient(c);
-         return 0;
+         addReply(c,shared.ok);
+         c->flags |= REDIS_CLOSE_AFTER_REPLY;
+         return REDIS_ERR;
      }
  
      /* Now lookup the command and check ASAP about trivial error conditions
      if (!cmd) {
          addReplyErrorFormat(c,"unknown command '%s'",
              (char*)c->argv[0]->ptr);
-         resetClient(c);
-         return 1;
+         return REDIS_OK;
      } else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
                 (c->argc < -cmd->arity)) {
          addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
              cmd->name);
-         resetClient(c);
-         return 1;
-     } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
-         /* This is a bulk command, we have to read the last argument yet. */
-         char *eptr;
-         long bulklen = strtol(c->argv[c->argc-1]->ptr,&eptr,10);
-         int perr = eptr[0] != '\0';
- 
-         decrRefCount(c->argv[c->argc-1]);
-         if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN ||
-             bulklen < 0 || bulklen > 1024*1024*1024)
-         {
-             c->argc--;
-             addReplyError(c,"invalid bulk write count");
-             resetClient(c);
-             return 1;
-         }
-         c->argc--;
-         c->bulklen = bulklen+2; /* add two bytes for CR+LF */
-         /* It is possible that the bulk read is already in the
-          * buffer. Check this condition and handle it accordingly.
-          * This is just a fast path, alternative to call processInputBuffer().
-          * It's a good idea since the code is small and this condition
-          * happens most of the times. */
-         if ((signed)sdslen(c->querybuf) >= c->bulklen) {
-             c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2);
-             c->argc++;
-             c->querybuf = sdsrange(c->querybuf,c->bulklen,-1);
-         } else {
-             /* Otherwise return... there is to read the last argument
-              * from the socket. */
-             return 1;
-         }
+         return REDIS_OK;
      }
-     /* Let's try to encode the bulk object to save space. */
-     if (cmd->flags & REDIS_CMD_BULK)
-         c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]);
  
      /* Check if the user is authenticated */
      if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
          addReplyError(c,"operation not permitted");
-         resetClient(c);
-         return 1;
+         return REDIS_OK;
      }
  
      /* Handle the maxmemory directive.
          zmalloc_used_memory() > server.maxmemory)
      {
          addReplyError(c,"command not allowed when used memory > 'maxmemory'");
-         resetClient(c);
-         return 1;
+         return REDIS_OK;
      }
  
      /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
          cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
          cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) {
          addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
-         resetClient(c);
-         return 1;
+         return REDIS_OK;
      }
  
      /* Exec the command */
          addReply(c,shared.queued);
      } else {
          if (server.vm_enabled && server.vm_max_threads > 0 &&
-             blockClientOnSwappedKeys(c,cmd)) return 1;
+             blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR;
          call(c,cmd);
      }
- 
-     /* Prepare the client for the next command */
-     resetClient(c);
-     return 1;
+     return REDIS_OK;
  }
  
  /*================================== Shutdown =============================== */
          /* Append only file: fsync() the AOF and exit */
          aof_fsync(server.appendfd);
          if (server.vm_enabled) unlink(server.vm_swap_file);
 -    } else {
 +    } else if (server.saveparamslen > 0) {
          /* Snapshotting. Perform a SYNC SAVE and exit */
          if (rdbSave(server.dbfilename) != REDIS_OK) {
              /* Ooops.. error saving! The best we can do is to continue
              redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
              return REDIS_ERR;
          }
 +    } else {
 +        redisLog(REDIS_WARNING,"Not saving DB.");
      }
      if (server.daemonize) unlink(server.pidfile);
      redisLog(REDIS_WARNING,"Server exit now, bye bye...");
          "process_id:%ld\r\n"
          "uptime_in_seconds:%ld\r\n"
          "uptime_in_days:%ld\r\n"
 +        "lru_clock:%ld\r\n"
          "used_cpu_sys:%.2f\r\n"
          "used_cpu_user:%.2f\r\n"
          "used_cpu_sys_childrens:%.2f\r\n"
          "used_memory:%zu\r\n"
          "used_memory_human:%s\r\n"
          "mem_fragmentation_ratio:%.2f\r\n"
 +        "use_tcmalloc:%d\r\n"
          "changes_since_last_save:%lld\r\n"
          "bgsave_in_progress:%d\r\n"
          "last_save_time:%ld\r\n"
          "total_connections_received:%lld\r\n"
          "total_commands_processed:%lld\r\n"
          "expired_keys:%lld\r\n"
 +        "keyspace_hits:%lld\r\n"
 +        "keyspace_misses:%lld\r\n"
          "hash_max_zipmap_entries:%zu\r\n"
          "hash_max_zipmap_value:%zu\r\n"
          "pubsub_channels:%ld\r\n"
          (long) getpid(),
          uptime,
          uptime/(3600*24),
 +        (unsigned long) server.lruclock,
          (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
          (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
          (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
          zmalloc_used_memory(),
          hmem,
          zmalloc_get_fragmentation_ratio(),
 +#ifdef USE_TCMALLOC
 +        1,
 +#else
 +        0,
 +#endif
          server.dirty,
          server.bgsavechildpid != -1,
          server.lastsave,
          server.stat_numconnections,
          server.stat_numcommands,
          server.stat_expiredkeys,
 +        server.stat_keyspace_hits,
 +        server.stat_keyspace_misses,
          server.hash_max_zipmap_entries,
          server.hash_max_zipmap_value,
          dictSize(server.pubsub_channels),
   * memory usage.
   */
  void freeMemoryIfNeeded(void) {
 +    /* Remove keys accordingly to the active policy as long as we are
 +     * over the memory limit. */
      while (server.maxmemory && zmalloc_used_memory() > server.maxmemory) {
          int j, k, freed = 0;
  
 +        /* Basic strategy -- remove objects from the free list. */
          if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
 +
 +        for (j = 0; j < server.dbnum; j++) {
 +            long bestval;
 +            sds bestkey = NULL;
 +            struct dictEntry *de;
 +            redisDb *db = server.db+j;
 +            dict *dict;
 +
 +            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
 +                server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)
 +            {
 +                dict = server.db[j].dict;
 +            } else {
 +                dict = server.db[j].expires;
 +            }
 +            if (dictSize(dict) == 0) continue;
 +
 +            /* volatile-random and allkeys-random policy */
 +            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
 +                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
 +            {
 +                de = dictGetRandomKey(dict);
 +                bestkey = dictGetEntryKey(de);
 +            }
 +
 +            /* volatile-lru and allkeys-lru policy */
 +            else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
 +                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
 +            {
 +                for (k = 0; k < server.maxmemory_samples; k++) {
 +                    sds thiskey;
 +                    long thisval;
 +                    robj *o;
 +
 +                    de = dictGetRandomKey(dict);
 +                    thiskey = dictGetEntryKey(de);
 +                    o = dictGetEntryVal(de);
 +                    thisval = estimateObjectIdleTime(o);
 +
 +                    /* Higher idle time is better candidate for deletion */
 +                    if (bestkey == NULL || thisval > bestval) {
 +                        bestkey = thiskey;
 +                        bestval = thisval;
 +                    }
 +                }
 +            }
 +
 +            /* volatile-ttl */
 +            else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
 +                for (k = 0; k < server.maxmemory_samples; k++) {
 +                    sds thiskey;
 +                    long thisval;
 +
 +                    de = dictGetRandomKey(dict);
 +                    thiskey = dictGetEntryKey(de);
 +                    thisval = (long) dictGetEntryVal(de);
 +
 +                    /* Expire sooner (minor expire unix timestamp) is better
 +                     * candidate for deletion */
 +                    if (bestkey == NULL || thisval < bestval) {
 +                        bestkey = thiskey;
 +                        bestval = thisval;
 +                    }
 +                }
 +            }
 +
 +            /* Finally remove the selected key. */
 +            if (bestkey) {
 +                robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
 +                dbDelete(db,keyobj);
 +                server.stat_expiredkeys++;
 +                decrRefCount(keyobj);
 +                freed++;
 +            }
 +        }
 +        if (!freed) return; /* nothing to free... */
 +    }
 +
 +    while(0) {
 +        int j, k, freed = 0;
          for (j = 0; j < server.dbnum; j++) {
              int minttl = -1;
              sds minkey = NULL;
      int i, trace_size = 0;
      ucontext_t *uc = (ucontext_t*) secret;
      sds infostring;
 +    struct sigaction act;
      REDIS_NOTUSED(info);
  
      redisLog(REDIS_WARNING,
  
      /* free(messages); Don't call free() with possibly corrupted memory. */
      if (server.daemonize) unlink(server.pidfile);
 -    _exit(0);
 +
 +    /* Make sure we exit with the right signal at the end. So for instance
 +     * the core will be dumped if enabled. */
 +    sigemptyset (&act.sa_mask);
 +    /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction
 +     * is used. Otherwise, sa_handler is used */
 +    act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
 +    act.sa_handler = SIG_DFL;
 +    sigaction (sig, &act, NULL);
 +    kill(getpid(),sig);
  }
  
  void sigtermHandler(int sig) {
 
      return 0; /* not found */
  }
  
 +/* Struct to hold a inclusive/exclusive range spec. */
 +typedef struct {
 +    double min, max;
 +    int minex, maxex; /* are min or max exclusive? */
 +} zrangespec;
 +
  /* Delete all the elements with score between min and max from the skiplist.
   * Min and mx are inclusive, so a score >= min || score <= max is deleted.
   * Note that this function takes the reference to the hash table view of the
   * sorted set, in order to remove the elements from the hash table too. */
 -unsigned long zslDeleteRangeByScore(zskiplist *zsl, double min, double max, dict *dict) {
 +unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec range, dict *dict) {
      zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
      unsigned long removed = 0;
      int i;
  
      x = zsl->header;
      for (i = zsl->level-1; i >= 0; i--) {
 -        while (x->level[i].forward && x->level[i].forward->score < min)
 -            x = x->level[i].forward;
 +        while (x->level[i].forward && (range.minex ?
 +            x->level[i].forward->score <= range.min :
 +            x->level[i].forward->score < range.min))
 +                x = x->level[i].forward;
          update[i] = x;
      }
 -    /* We may have multiple elements with the same score, what we need
 -     * is to find the element with both the right score and object. */
 +
 +    /* Current node is the last with score < or <= min. */
      x = x->level[0].forward;
 -    while (x && x->score <= max) {
 +
 +    /* Delete nodes while in range. */
 +    while (x && (range.maxex ? x->score < range.max : x->score <= range.max)) {
          zskiplistNode *next = x->level[0].forward;
          zslDeleteNode(zsl,x,update);
          dictDelete(dict,x->obj);
          removed++;
          x = next;
      }
 -    return removed; /* not found */
 +    return removed;
  }
  
  /* Delete all the elements with rank between start and end from the skiplist.
      return NULL;
  }
  
 +/* Populate the rangespec according to the objects min and max. */
 +static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
 +    char *eptr;
 +    spec->minex = spec->maxex = 0;
 +
 +    /* Parse the min-max interval. If one of the values is prefixed
 +     * by the "(" character, it's considered "open". For instance
 +     * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max
 +     * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */
 +    if (min->encoding == REDIS_ENCODING_INT) {
 +        spec->min = (long)min->ptr;
 +    } else {
 +        if (((char*)min->ptr)[0] == '(') {
 +            spec->min = strtod((char*)min->ptr+1,&eptr);
 +            if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR;
 +            spec->minex = 1;
 +        } else {
 +            spec->min = strtod((char*)min->ptr,&eptr);
 +            if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR;
 +        }
 +    }
 +    if (max->encoding == REDIS_ENCODING_INT) {
 +        spec->max = (long)max->ptr;
 +    } else {
 +        if (((char*)max->ptr)[0] == '(') {
 +            spec->max = strtod((char*)max->ptr+1,&eptr);
 +            if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR;
 +            spec->maxex = 1;
 +        } else {
 +            spec->max = strtod((char*)max->ptr,&eptr);
 +            if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR;
 +        }
 +    }
 +
 +    return REDIS_OK;
 +}
 +
 +
  /*-----------------------------------------------------------------------------
   * Sorted set commands 
   *----------------------------------------------------------------------------*/
  void zaddCommand(redisClient *c) {
      double scoreval;
      if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
+     c->argv[3] = tryObjectEncoding(c->argv[3]);
      zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
  }
  
  void zincrbyCommand(redisClient *c) {
      double scoreval;
      if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
+     c->argv[3] = tryObjectEncoding(c->argv[3]);
      zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
  }
  
          checkType(c,zsetobj,REDIS_ZSET)) return;
  
      zs = zsetobj->ptr;
+     c->argv[2] = tryObjectEncoding(c->argv[2]);
      de = dictFind(zs->dict,c->argv[2]);
      if (de == NULL) {
          addReply(c,shared.czero);
  }
  
  void zremrangebyscoreCommand(redisClient *c) {
 -    double min;
 -    double max;
 +    zrangespec range;
      long deleted;
 -    robj *zsetobj;
 +    robj *o;
      zset *zs;
  
 -    if ((getDoubleFromObjectOrReply(c, c->argv[2], &min, NULL) != REDIS_OK) ||
 -        (getDoubleFromObjectOrReply(c, c->argv[3], &max, NULL) != REDIS_OK)) return;
 +    /* Parse the range arguments. */
 +    if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
 +        addReplyError(c,"min or max is not a double");
 +        return;
 +    }
  
 -    if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
 -        checkType(c,zsetobj,REDIS_ZSET)) return;
 +    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
 +        checkType(c,o,REDIS_ZSET)) return;
  
 -    zs = zsetobj->ptr;
 -    deleted = zslDeleteRangeByScore(zs->zsl,min,max,zs->dict);
 +    zs = o->ptr;
 +    deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
      if (htNeedsResize(zs->dict)) dictResize(zs->dict);
      if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
      if (deleted) touchWatchedKey(c->db,c->argv[1]);
                      dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
                      if (other) {
                          value = src[j].weight * zunionInterDictValue(other);
 -                        zunionInterAggregate(&score, value, aggregate);
 +                        zunionInterAggregate(&score,value,aggregate);
                      } else {
                          break;
                      }
                  }
  
 -                /* accept entry only when present in every source dict */
 +                /* Only continue when present in every source dict. */
                  if (j == setnum) {
                      robj *o = dictGetEntryKey(de);
                      znode = zslInsert(dstzset->zsl,score,o);
                  /* skip key when already processed */
                  if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL)
                      continue;
 +
 +                /* initialize score */
                  score = src[i].weight * zunionInterDictValue(de);
  
                  /* because the zsets are sorted by size, its only possible
                      dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
                      if (other) {
                          value = src[j].weight * zunionInterDictValue(other);
 -                        zunionInterAggregate(&score, value, aggregate);
 +                        zunionInterAggregate(&score,value,aggregate);
                      }
                  }
  
      zrangeGenericCommand(c,1);
  }
  
 -/* This command implements both ZRANGEBYSCORE and ZCOUNT.
 - * If justcount is non-zero, just the count is returned. */
 -void genericZrangebyscoreCommand(redisClient *c, int justcount) {
 -    robj *o;
 -    double min, max;
 -    int minex = 0, maxex = 0; /* are min or max exclusive? */
 +/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE and ZCOUNT.
 + * If "justcount", only the number of elements in the range is returned. */
 +void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
 +    zrangespec range;
 +    robj *o, *emptyreply;
 +    zset *zsetobj;
 +    zskiplist *zsl;
 +    zskiplistNode *ln;
      int offset = 0, limit = -1;
      int withscores = 0;
 -    int badsyntax = 0;
 +    unsigned long rangelen = 0;
 +    void *replylen = NULL;
  
 -    /* Parse the min-max interval. If one of the values is prefixed
 -     * by the "(" character, it's considered "open". For instance
 -     * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max
 -     * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */
 -    if (((char*)c->argv[2]->ptr)[0] == '(') {
 -        min = strtod((char*)c->argv[2]->ptr+1,NULL);
 -        minex = 1;
 -    } else {
 -        min = strtod(c->argv[2]->ptr,NULL);
 -    }
 -    if (((char*)c->argv[3]->ptr)[0] == '(') {
 -        max = strtod((char*)c->argv[3]->ptr+1,NULL);
 -        maxex = 1;
 -    } else {
 -        max = strtod(c->argv[3]->ptr,NULL);
 +    /* Parse the range arguments. */
 +    if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
 +        addReplyError(c,"min or max is not a double");
 +        return;
      }
  
 -    /* Parse "WITHSCORES": note that if the command was called with
 -     * the name ZCOUNT then we are sure that c->argc == 4, so we'll never
 -     * enter the following paths to parse WITHSCORES and LIMIT. */
 -    if (c->argc == 5 || c->argc == 8) {
 -        if (strcasecmp(c->argv[c->argc-1]->ptr,"withscores") == 0)
 -            withscores = 1;
 -        else
 -            badsyntax = 1;
 +    /* Parse optional extra arguments. Note that ZCOUNT will exactly have
 +     * 4 arguments, so we'll never enter the following code path. */
 +    if (c->argc > 4) {
 +        int remaining = c->argc - 4;
 +        int pos = 4;
 +
 +        while (remaining) {
 +            if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
 +                pos++; remaining--;
 +                withscores = 1;
 +            } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
 +                offset = atoi(c->argv[pos+1]->ptr);
 +                limit = atoi(c->argv[pos+2]->ptr);
 +                pos += 3; remaining -= 3;
 +            } else {
 +                addReply(c,shared.syntaxerr);
 +                return;
 +            }
 +        }
      }
 -    if (c->argc != (4 + withscores) && c->argc != (7 + withscores))
 -        badsyntax = 1;
 -    if (badsyntax) {
 -        addReplyError(c,"wrong number of arguments for ZRANGEBYSCORE");
 -        return;
 +
 +    /* Ok, lookup the key and get the range */
 +    emptyreply = justcount ? shared.czero : shared.emptymultibulk;
 +    if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL ||
 +        checkType(c,o,REDIS_ZSET)) return;
 +    zsetobj = o->ptr;
 +    zsl = zsetobj->zsl;
 +
 +    /* If reversed, assume the elements are sorted from high to low score. */
 +    ln = zslFirstWithScore(zsl,range.min);
 +    if (reverse) {
 +        /* If range.min is out of range, ln will be NULL and we need to use
 +         * the tail of the skiplist as first node of the range. */
 +        if (ln == NULL) ln = zsl->tail;
 +
 +        /* zslFirstWithScore returns the first element with where with
 +         * score >= range.min, so backtrack to make sure the element we use
 +         * here has score <= range.min. */
 +        while (ln && ln->score > range.min) ln = ln->backward;
 +
 +        /* Move to the right element according to the range spec. */
 +        if (range.minex) {
 +            /* Find last element with score < range.min */
 +            while (ln && ln->score == range.min) ln = ln->backward;
 +        } else {
 +            /* Find last element with score <= range.min */
 +            while (ln && ln->level[0].forward &&
 +                         ln->level[0].forward->score == range.min)
 +                ln = ln->level[0].forward;
 +        }
 +    } else {
 +        if (range.minex) {
 +            /* Find first element with score > range.min */
 +            while (ln && ln->score == range.min) ln = ln->level[0].forward;
 +        }
      }
  
 -    /* Parse "LIMIT" */
 -    if (c->argc == (7 + withscores) && strcasecmp(c->argv[4]->ptr,"limit")) {
 -        addReply(c,shared.syntaxerr);
 +    /* No "first" element in the specified interval. */
 +    if (ln == NULL) {
 +        addReply(c,emptyreply);
          return;
 -    } else if (c->argc == (7 + withscores)) {
 -        offset = atoi(c->argv[5]->ptr);
 -        limit = atoi(c->argv[6]->ptr);
 -        if (offset < 0) offset = 0;
      }
  
 -    /* Ok, lookup the key and get the range */
 -    o = lookupKeyRead(c->db,c->argv[1]);
 -    if (o == NULL) {
 -        addReply(c,justcount ? shared.czero : shared.emptymultibulk);
 -    } else {
 -        if (o->type != REDIS_ZSET) {
 -            addReply(c,shared.wrongtypeerr);
 -        } else {
 -            zset *zsetobj = o->ptr;
 -            zskiplist *zsl = zsetobj->zsl;
 -            zskiplistNode *ln;
 -            robj *ele;
 -            void *replylen = NULL;
 -            unsigned long rangelen = 0;
 -
 -            /* Get the first node with the score >= min, or with
 -             * score > min if 'minex' is true. */
 -            ln = zslFirstWithScore(zsl,min);
 -            while (minex && ln && ln->score == min) ln = ln->level[0].forward;
 -
 -            if (ln == NULL) {
 -                /* No element matching the speciifed interval */
 -                addReply(c,justcount ? shared.czero : shared.emptymultibulk);
 -                return;
 -            }
 +    /* We don't know in advance how many matching elements there
 +     * are in the list, so we push this object that will represent
 +     * the multi-bulk length in the output buffer, and will "fix"
 +     * it later */
 +    if (!justcount)
 +        replylen = addDeferredMultiBulkLength(c);
  
 -            /* We don't know in advance how many matching elements there
 -             * are in the list, so we push this object that will represent
 -             * the multi-bulk length in the output buffer, and will "fix"
 -             * it later */
 -            if (!justcount)
 -                replylen = addDeferredMultiBulkLength(c);
 -
 -            while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) {
 -                if (offset) {
 -                    offset--;
 -                    ln = ln->level[0].forward;
 -                    continue;
 -                }
 -                if (limit == 0) break;
 -                if (!justcount) {
 -                    ele = ln->obj;
 -                    addReplyBulk(c,ele);
 -                    if (withscores)
 -                        addReplyDouble(c,ln->score);
 -                }
 -                ln = ln->level[0].forward;
 -                rangelen++;
 -                if (limit > 0) limit--;
 +    /* If there is an offset, just traverse the number of elements without
 +     * checking the score because that is done in the next loop. */
 +    while(ln && offset--) {
 +        if (reverse)
 +            ln = ln->backward;
 +        else
 +            ln = ln->level[0].forward;
 +    }
 +
 +    while (ln && limit--) {
 +        /* Check if this this element is in range. */
 +        if (reverse) {
 +            if (range.maxex) {
 +                /* Element should have score > range.max */
 +                if (ln->score <= range.max) break;
 +            } else {
 +                /* Element should have score >= range.max */
 +                if (ln->score < range.max) break;
              }
 -            if (justcount) {
 -                addReplyLongLong(c,(long)rangelen);
 +        } else {
 +            if (range.maxex) {
 +                /* Element should have score < range.max */
 +                if (ln->score >= range.max) break;
              } else {
 -                setDeferredMultiBulkLength(c,replylen,
 -                     withscores ? (rangelen*2) : rangelen);
 +                /* Element should have score <= range.max */
 +                if (ln->score > range.max) break;
              }
          }
 +
 +        /* Do our magic */
 +        rangelen++;
 +        if (!justcount) {
 +            addReplyBulk(c,ln->obj);
 +            if (withscores)
 +                addReplyDouble(c,ln->score);
 +        }
 +
 +        if (reverse)
 +            ln = ln->backward;
 +        else
 +            ln = ln->level[0].forward;
 +    }
 +
 +    if (justcount) {
 +        addReplyLongLong(c,(long)rangelen);
 +    } else {
 +        setDeferredMultiBulkLength(c,replylen,
 +             withscores ? (rangelen*2) : rangelen);
      }
  }
  
  void zrangebyscoreCommand(redisClient *c) {
 -    genericZrangebyscoreCommand(c,0);
 +    genericZrangebyscoreCommand(c,0,0);
 +}
 +
 +void zrevrangebyscoreCommand(redisClient *c) {
 +    genericZrangebyscoreCommand(c,1,0);
  }
  
  void zcountCommand(redisClient *c) {
 -    genericZrangebyscoreCommand(c,1);
 +    genericZrangebyscoreCommand(c,0,1);
  }
  
  void zcardCommand(redisClient *c) {
          checkType(c,o,REDIS_ZSET)) return;
  
      zs = o->ptr;
+     c->argv[2] = tryObjectEncoding(c->argv[2]);
      de = dictFind(zs->dict,c->argv[2]);
      if (!de) {
          addReply(c,shared.nullbulk);
  
      zs = o->ptr;
      zsl = zs->zsl;
+     c->argv[2] = tryObjectEncoding(c->argv[2]);
      de = dictFind(zs->dict,c->argv[2]);
      if (!de) {
          addReply(c,shared.nullbulk);