From 2ef8c2f6c22e27507e62bf937a5b4e9f7429fdbd Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 18 Aug 2017 11:27:04 +0800 Subject: [PATCH 001/219] Update the comment --- src/notify.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notify.c b/src/notify.c index 94a1f2e79..79c1fc048 100644 --- a/src/notify.c +++ b/src/notify.c @@ -29,8 +29,8 @@ #include "server.h" -/* This file implements keyspace events notification via Pub/Sub ad - * described at http://redis.io/topics/keyspace-events. */ +/* This file implements keyspace events notification via Pub/Sub and + * described at https://redis.io/topics/notifications. */ /* Turn a string representing notification classes into an integer * representing notification classes flags xored. From e8901b2fe489013b17e943d2721f961a50bda07c Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Fri, 8 Dec 2017 15:37:08 +0800 Subject: [PATCH 002/219] zset: fix the int problem --- src/server.h | 2 +- src/t_zset.c | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/server.h b/src/server.h index ee3b7df5c..9917c1f88 100644 --- a/src/server.h +++ b/src/server.h @@ -1590,7 +1590,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr); void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr); unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range); unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range); -unsigned int zsetLength(const robj *zobj); +unsigned long zsetLength(const robj *zobj); void zsetConvert(robj *zobj, int encoding); void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen); int zsetScore(robj *zobj, sds member, double *score); diff --git a/src/t_zset.c b/src/t_zset.c index f7f4c6eb2..cfd5f2b9b 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -1100,8 +1100,8 @@ unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsig * Common sorted set API *----------------------------------------------------------------------------*/ -unsigned int zsetLength(const robj *zobj) { - int length = -1; +unsigned long zsetLength(const robj *zobj) { + unsigned long length = 0; if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { length = zzlLength(zobj->ptr); } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { @@ -1878,7 +1878,7 @@ void zuiClearIterator(zsetopsrc *op) { } } -int zuiLength(zsetopsrc *op) { +unsigned long zuiLength(zsetopsrc *op) { if (op->subject == NULL) return 0; @@ -2085,7 +2085,11 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) { } int zuiCompareByCardinality(const void *s1, const void *s2) { - return zuiLength((zsetopsrc*)s1) - zuiLength((zsetopsrc*)s2); + unsigned long first = zuiLength((zsetopsrc*)s1); + unsigned long second = zuiLength((zsetopsrc*)s2); + if (first > second) return 1; + if (first < second) return -1; + return 0; } #define REDIS_AGGR_SUM 1 @@ -2129,7 +2133,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) { zsetopsrc *src; zsetopval zval; sds tmp; - unsigned int maxelelen = 0; + size_t maxelelen = 0; robj *dstobj; zset *dstzset; zskiplistNode *znode; @@ -2363,8 +2367,8 @@ void zrangeGenericCommand(client *c, int reverse) { int withscores = 0; long start; long end; - int llen; - int rangelen; + long llen; + long rangelen; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return; @@ -2671,7 +2675,7 @@ void zcountCommand(client *c) { robj *key = c->argv[1]; robj *zobj; zrangespec range; - int count = 0; + unsigned long count = 0; /* Parse the range arguments */ if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) { @@ -2748,7 +2752,7 @@ void zlexcountCommand(client *c) { robj *key = c->argv[1]; robj *zobj; zlexrangespec range; - int count = 0; + unsigned long count = 0; /* Parse the range arguments */ if (zslParseLexRange(c->argv[2],c->argv[3],&range) != C_OK) { From 109ee497be24906e7931d33b71e3a6e78c5de77b Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Fri, 8 Dec 2017 16:09:27 +0800 Subject: [PATCH 003/219] zset: change the span of zskiplistNode to unsigned long --- src/server.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server.h b/src/server.h index 9917c1f88..f3a970659 100644 --- a/src/server.h +++ b/src/server.h @@ -335,7 +335,7 @@ typedef long long mstime_t; /* millisecond time type. */ /* Anti-warning macro... */ #define UNUSED(V) ((void) V) -#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */ +#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */ #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ /* Append only defines */ @@ -774,7 +774,7 @@ typedef struct zskiplistNode { struct zskiplistNode *backward; struct zskiplistLevel { struct zskiplistNode *forward; - unsigned int span; + unsigned long span; } level[]; } zskiplistNode; From 968cb2669346ebcb20016ac16923dcd31e31ad2f Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Fri, 26 Jan 2018 22:49:39 +0800 Subject: [PATCH 004/219] config: handle special configuration "" for auth --- src/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index eddfe1f11..1e3fd1848 100644 --- a/src/config.c +++ b/src/config.c @@ -390,7 +390,7 @@ void loadServerConfigFromString(char *config) { } } else if (!strcasecmp(argv[0],"masterauth") && argc == 2) { zfree(server.masterauth); - server.masterauth = zstrdup(argv[1]); + server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL; } else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) { if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; @@ -496,7 +496,7 @@ void loadServerConfigFromString(char *config) { err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN"; goto loaderr; } - server.requirepass = zstrdup(argv[1]); + server.requirepass = argv[1][0] ? zstrdup(argv[1]) : NULL; } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) { zfree(server.pidfile); server.pidfile = zstrdup(argv[1]); From f4eb64cd3537ce34e0125fa1fb6cccb6dbfc3e48 Mon Sep 17 00:00:00 2001 From: "pan.liangp" Date: Fri, 2 Mar 2018 17:16:00 +0800 Subject: [PATCH 005/219] move get clients max buffer calculate into info clients command --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 1a6f30381..8e62dd176 100644 --- a/src/server.c +++ b/src/server.c @@ -2866,7 +2866,6 @@ sds genRedisInfoString(char *section) { getrusage(RUSAGE_SELF, &self_ru); getrusage(RUSAGE_CHILDREN, &c_ru); - getClientsMaxBuffers(&lol,&bib); /* Server */ if (allsections || defsections || !strcasecmp(section,"server")) { @@ -2936,6 +2935,7 @@ sds genRedisInfoString(char *section) { /* Clients */ if (allsections || defsections || !strcasecmp(section,"clients")) { + getClientsMaxBuffers(&lol,&bib); if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, "# Clients\r\n" From 54cae05ea736340b4afe463eb750f5acdfe07590 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Fri, 16 Mar 2018 00:44:50 +0800 Subject: [PATCH 006/219] rdb: incremental fsync when redis saves rdb --- src/aof.c | 10 +++++----- src/bio.c | 2 +- src/config.c | 12 ++++++++++++ src/config.h | 6 +++--- src/rdb.c | 4 ++++ src/rio.c | 2 +- src/server.c | 3 ++- src/server.h | 6 ++++-- 8 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/aof.c b/src/aof.c index 4a7d749d0..7712a779e 100644 --- a/src/aof.c +++ b/src/aof.c @@ -228,7 +228,7 @@ static void killAppendOnlyChild(void) { void stopAppendOnly(void) { serverAssert(server.aof_state != AOF_OFF); flushAppendOnlyFile(1); - aof_fsync(server.aof_fd); + redis_fsync(server.aof_fd); close(server.aof_fd); server.aof_fd = -1; @@ -476,10 +476,10 @@ void flushAppendOnlyFile(int force) { /* Perform the fsync if needed. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { - /* aof_fsync is defined as fdatasync() for Linux in order to avoid + /* redis_fsync is defined as fdatasync() for Linux in order to avoid * flushing metadata. */ latencyStartMonitor(latency); - aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */ + redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */ latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fsync-always",latency); server.aof_last_fsync = server.unixtime; @@ -1241,7 +1241,7 @@ int rewriteAppendOnlyFile(char *filename) { rioInitWithFile(&aof,fp); if (server.aof_rewrite_incremental_fsync) - rioSetAutoSync(&aof,AOF_AUTOSYNC_BYTES); + rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES); if (server.aof_use_rdb_preamble) { int error; @@ -1609,7 +1609,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { oldfd = server.aof_fd; server.aof_fd = newfd; if (server.aof_fsync == AOF_FSYNC_ALWAYS) - aof_fsync(newfd); + redis_fsync(newfd); else if (server.aof_fsync == AOF_FSYNC_EVERYSEC) aof_background_fsync(newfd); server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ diff --git a/src/bio.c b/src/bio.c index da11f7b86..0c92d053b 100644 --- a/src/bio.c +++ b/src/bio.c @@ -187,7 +187,7 @@ void *bioProcessBackgroundJobs(void *arg) { if (type == BIO_CLOSE_FILE) { close((long)job->arg1); } else if (type == BIO_AOF_FSYNC) { - aof_fsync((long)job->arg1); + redis_fsync((long)job->arg1); } else if (type == BIO_LAZY_FREE) { /* What we free changes depending on what arguments are set: * arg1 -> free the object at pointer. diff --git a/src/config.c b/src/config.c index eddfe1f11..f4187633e 100644 --- a/src/config.c +++ b/src/config.c @@ -483,6 +483,13 @@ void loadServerConfigFromString(char *config) { yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; } + } else if (!strcasecmp(argv[0],"rdb-save-incremental-fsync") && + argc == 2) + { + if ((server.rdb_save_incremental_fsync = + yesnotoi(argv[1])) == -1) { + err = "argument must be 'yes' or 'no'"; goto loaderr; + } } else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) { if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; @@ -999,6 +1006,8 @@ void configSetCommand(client *c) { "cluster-require-full-coverage",server.cluster_require_full_coverage) { } config_set_bool_field( "aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) { + } config_set_bool_field( + "rdb-save-incremental-fsync",server.rdb_save_incremental_fsync) { } config_set_bool_field( "aof-load-truncated",server.aof_load_truncated) { } config_set_bool_field( @@ -1311,6 +1320,8 @@ void configGetCommand(client *c) { server.repl_diskless_sync); config_get_bool_field("aof-rewrite-incremental-fsync", server.aof_rewrite_incremental_fsync); + config_get_bool_field("rdb-save-incremental-fsync", + server.rdb_save_incremental_fsync); config_get_bool_field("aof-load-truncated", server.aof_load_truncated); config_get_bool_field("aof-use-rdb-preamble", @@ -2044,6 +2055,7 @@ int rewriteConfig(char *path) { rewriteConfigClientoutputbufferlimitOption(state); rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ); rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC); + rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC); rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED); rewriteConfigYesNoOption(state,"aof-use-rdb-preamble",server.aof_use_rdb_preamble,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE); rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE); diff --git a/src/config.h b/src/config.h index c23f1c789..ee3ad508e 100644 --- a/src/config.h +++ b/src/config.h @@ -87,11 +87,11 @@ #endif #endif -/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */ +/* Define redis_fsync to fdatasync() in Linux and fsync() for all the rest */ #ifdef __linux__ -#define aof_fsync fdatasync +#define redis_fsync fdatasync #else -#define aof_fsync fsync +#define redis_fsync fsync #endif /* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use diff --git a/src/rdb.c b/src/rdb.c index 8f896983b..c824ab208 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -1060,6 +1060,10 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) { } rioInitWithFile(&rdb,fp); + + if (server.rdb_save_incremental_fsync) + rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES); + if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) { errno = error; goto werr; diff --git a/src/rio.c b/src/rio.c index 23b907060..c9c76b8f2 100644 --- a/src/rio.c +++ b/src/rio.c @@ -116,7 +116,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) { r->io.file.buffered >= r->io.file.autosync) { fflush(r->io.file.fp); - aof_fsync(fileno(r->io.file.fp)); + redis_fsync(fileno(r->io.file.fp)); r->io.file.buffered = 0; } return retval; diff --git a/src/server.c b/src/server.c index 1a6f30381..f4d6e95a3 100644 --- a/src/server.c +++ b/src/server.c @@ -1416,6 +1416,7 @@ void initServerConfig(void) { server.aof_selected_db = -1; /* Make sure the first time will not match */ server.aof_flush_postponed_start = 0; server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; + server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC; server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED; server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE; server.pidfile = NULL; @@ -2585,7 +2586,7 @@ int prepareForShutdown(int flags) { /* Append only file: flush buffers and fsync() the AOF at exit */ serverLog(LL_NOTICE,"Calling fsync() on the AOF file."); flushAppendOnlyFile(1); - aof_fsync(server.aof_fd); + redis_fsync(server.aof_fd); } /* Create a new RDB file before exiting. */ diff --git a/src/server.h b/src/server.h index 29919f5ee..d25613244 100644 --- a/src/server.h +++ b/src/server.h @@ -143,6 +143,7 @@ typedef long long mstime_t; /* millisecond time type. */ #define CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE 0 #define CONFIG_DEFAULT_ACTIVE_REHASHING 1 #define CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1 +#define CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC 1 #define CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE 0 #define CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG 10 #define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */ @@ -183,7 +184,7 @@ typedef long long mstime_t; /* millisecond time type. */ #define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */ #define PROTO_MBULK_BIG_ARG (1024*32) #define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */ -#define AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */ +#define REDIS_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */ /* When configuring the server eventloop, we setup it so that the total number * of file descriptors we can handle are server.maxclients + RESERVED_FDS + @@ -1021,7 +1022,8 @@ struct redisServer { time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */ int aof_lastbgrewrite_status; /* C_OK or C_ERR */ unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */ - int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */ + int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */ + int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */ int aof_last_write_status; /* C_OK or C_ERR */ int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */ int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */ From fd0c4c027713d1d34a8dd5ef62556b9ac0940d45 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Fri, 16 Mar 2018 00:48:59 +0800 Subject: [PATCH 007/219] add rdb-save-incremental-fsync option in redis.conf --- redis.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/redis.conf b/redis.conf index 1e1f5313f..427fc7dda 100644 --- a/redis.conf +++ b/redis.conf @@ -1193,6 +1193,12 @@ hz 10 # big latency spikes. aof-rewrite-incremental-fsync yes +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + # Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good # idea to start with the default settings and only change them after investigating # how to improve the performances and how the keys LFU change over time, which From 83cf0e3668f61a57bbaaedf47d35d8943352d893 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Thu, 29 Mar 2018 17:36:15 +0800 Subject: [PATCH 008/219] adjust position of _dictNextPower in dictExpand --- src/dict.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dict.c b/src/dict.c index 97e636805..a7b77aad5 100644 --- a/src/dict.c +++ b/src/dict.c @@ -146,14 +146,14 @@ int dictResize(dict *d) /* Expand or create the hash table */ int dictExpand(dict *d, unsigned long size) { - dictht n; /* the new hash table */ - unsigned long realsize = _dictNextPower(size); - /* the size is invalid if it is smaller than the number of * elements already inside the hash table */ if (dictIsRehashing(d) || d->ht[0].used > size) return DICT_ERR; + dictht n; /* the new hash table */ + unsigned long realsize = _dictNextPower(size); + /* Rehashing to the same table size is not useful. */ if (realsize == d->ht[0].size) return DICT_ERR; From 0fd2b25c8dc646dd86fe95021f2e5c0224f075da Mon Sep 17 00:00:00 2001 From: charpty Date: Fri, 6 Apr 2018 16:42:48 +0800 Subject: [PATCH 009/219] Remove unnecessary return statements Signed-off-by: charpty --- src/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server.c b/src/server.c index 7f51778d7..6210fa18e 100644 --- a/src/server.c +++ b/src/server.c @@ -2866,7 +2866,6 @@ void bytesToHuman(char *s, unsigned long long n) { if (n < 1024) { /* Bytes */ sprintf(s,"%lluB",n); - return; } else if (n < (1024*1024)) { d = (double)n/(1024); sprintf(s,"%.2fK",d); From 24036b4d32d857066a2ccfc6cef2e8a751634ad5 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Sun, 22 Apr 2018 22:30:44 +0800 Subject: [PATCH 010/219] RDB: expand dict if needed when rdb load object --- src/rdb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rdb.c b/src/rdb.c index 27c3aa786..639be1ea5 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -1427,6 +1427,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { o = createZsetObject(); zs = o->ptr; + if (zsetlen > DICT_HT_INITIAL_SIZE) + dictExpand(zs->dict,zsetlen); + /* Load every single element of the sorted set. */ while(zsetlen--) { sds sdsele; @@ -1495,6 +1498,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { sdsfree(value); } + if (o->encoding == OBJ_ENCODING_HT && len > DICT_HT_INITIAL_SIZE) + dictExpand(o->ptr,len); + /* Load remaining fields and values into the hash table */ while (o->encoding == OBJ_ENCODING_HT && len > 0) { len--; From fd9177dd33de2bc8fddf1d304b7b5810d98c2a1d Mon Sep 17 00:00:00 2001 From: Krzysztof Filipek Date: Sun, 6 May 2018 20:18:48 +0200 Subject: [PATCH 011/219] Typo in preprocessor condition --- src/zmalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zmalloc.c b/src/zmalloc.c index 7cb4e3729..638bf60ff 100644 --- a/src/zmalloc.c +++ b/src/zmalloc.c @@ -415,7 +415,7 @@ size_t zmalloc_get_memory_size(void) { mib[0] = CTL_HW; #if defined(HW_REALMEM) mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */ -#elif defined(HW_PYSMEM) +#elif defined(HW_PHYSMEM) mib[1] = HW_PHYSMEM; /* Others. ------------------ */ #endif unsigned int size = 0; /* 32-bit */ From 8d93f924ea44c4139d690a914af662b6f059709a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E7=A3=8A?= Date: Tue, 8 May 2018 15:30:11 +0800 Subject: [PATCH 012/219] Fix dictScan(): It can't scan all buckets when dict is shrinking. --- src/dict.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/dict.c b/src/dict.c index 97e636805..18cb9ee79 100644 --- a/src/dict.c +++ b/src/dict.c @@ -858,6 +858,15 @@ unsigned long dictScan(dict *d, de = next; } + /* Set unmasked bits so incrementing the reversed cursor + * operates on the masked bits */ + v |= ~m0; + + /* Increment the reverse cursor */ + v = rev(v); + v++; + v = rev(v); + } else { t0 = &d->ht[0]; t1 = &d->ht[1]; @@ -892,22 +901,16 @@ unsigned long dictScan(dict *d, de = next; } - /* Increment bits not covered by the smaller mask */ - v = (((v | m0) + 1) & ~m0) | (v & m0); + /* Increment the reverse cursor not covered by the smaller mask.*/ + v |= ~m1; + v = rev(v); + v++; + v = rev(v); /* Continue while bits covered by mask difference is non-zero */ } while (v & (m0 ^ m1)); } - /* Set unmasked bits so incrementing the reversed cursor - * operates on the masked bits of the smaller table */ - v |= ~m0; - - /* Increment the reverse cursor */ - v = rev(v); - v++; - v = rev(v); - return v; } From af3471a5dc1bf26e7bc2d1578f9bf82e9c7168a2 Mon Sep 17 00:00:00 2001 From: Mota Date: Fri, 25 May 2018 16:49:23 +0800 Subject: [PATCH 013/219] Fix debug crash-and-recover help info. --- src/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index f239eea5a..0ab864e7f 100644 --- a/src/debug.c +++ b/src/debug.c @@ -287,7 +287,7 @@ void debugCommand(client *c) { const char *help[] = { "assert -- Crash by assertion failed.", "change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.", -"crash-and-recovery -- Hard crash and restart after delay.", +"crash-and-recover -- Hard crash and restart after delay.", "digest -- Outputs an hex signature representing the current DB content.", "htstats -- Return hash table statistics of the specified Redis database.", "loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.", From ce2e0c02fc67658e3e891ff4f732a17db3adfb96 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Sun, 27 May 2018 16:34:58 +0800 Subject: [PATCH 014/219] MEMORY: fix the missing of monitor clients buffers --- src/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object.c b/src/object.c index 82b82632b..214edda5c 100644 --- a/src/object.c +++ b/src/object.c @@ -968,7 +968,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) { listRewind(server.clients,&li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); - if (c->flags & CLIENT_SLAVE) + if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR)) continue; mem += getClientOutputBufferMemoryUsage(c); mem += sdsAllocSize(c->querybuf); From 9561fec496bc7e753a12d919a72d49cf56f1fa4b Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 30 May 2018 15:33:06 +0200 Subject: [PATCH 015/219] include stdint.h for unit64_t definition --- src/zmalloc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zmalloc.c b/src/zmalloc.c index 31e686c4f..3a3c579f2 100644 --- a/src/zmalloc.c +++ b/src/zmalloc.c @@ -30,6 +30,7 @@ #include #include +#include /* This function provide us access to the original libc free(). This is useful * for instance to free results obtained by backtrace_symbols(). We need From 4bce88b11fdf68ab754cf9125ffaac02c6428ff5 Mon Sep 17 00:00:00 2001 From: artix Date: Mon, 28 May 2018 17:23:42 +0200 Subject: [PATCH 016/219] Cluster Manager: fixed infinite loop in rebalance (Issue #4941). --- src/redis-cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index d106d8f5d..e1c1983d5 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -4887,7 +4887,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) { listRewind(involved, &li); while ((ln = listNext(&li)) != NULL) { clusterManagerNode *n = ln->value; - if (n->balance < 0 && total_balance > 0) { + if (n->balance <= 0 && total_balance > 0) { n->balance--; total_balance--; } From 2f963080c16b3645c4b5ae443c85ee9f9025789b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 31 May 2018 17:11:46 +0200 Subject: [PATCH 017/219] Capitalize OBJECT HELP subcommands. --- src/object.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/object.c b/src/object.c index 82b82632b..a61023c75 100644 --- a/src/object.c +++ b/src/object.c @@ -1161,10 +1161,10 @@ void objectCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"encoding -- Return the kind of internal representation used in order to store the value associated with a key.", -"freq -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.", -"idletime -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.", -"refcount -- Return the number of references of the value associated with the specified key.", +"ENCODING -- Return the kind of internal representation used in order to store the value associated with a key.", +"FREQ -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.", +"IDLETIME -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.", +"REFCOUNT -- Return the number of references of the value associated with the specified key.", NULL }; addReplyHelp(c, help); From b9d19371e402fc8e6d1ca70863ab0e06889ff1c3 Mon Sep 17 00:00:00 2001 From: "zhaozhao.zz" Date: Thu, 31 May 2018 23:35:47 +0800 Subject: [PATCH 018/219] ZPOP: unblock multiple clients in right way --- src/blocked.c | 4 +++- src/t_zset.c | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/blocked.c b/src/blocked.c index 023fba0cd..8d1157c2c 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -314,8 +314,9 @@ void handleClientsBlockedOnKeys(void) { if (de) { list *clients = dictGetVal(de); int numclients = listLength(clients); + unsigned long zcard = zsetLength(o); - while(numclients--) { + while(numclients-- && zcard) { listNode *clientnode = listFirst(clients); client *receiver = clientnode->value; @@ -332,6 +333,7 @@ void handleClientsBlockedOnKeys(void) { ? ZSET_MIN : ZSET_MAX; unblockClient(receiver); genericZpopCommand(receiver,&rl->key,1,where,1,NULL); + zcard--; /* Replicate the command. */ robj *argv[2]; diff --git a/src/t_zset.c b/src/t_zset.c index 11c8a051c..ae0922342 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -3216,9 +3216,9 @@ void blockingGenericZpopCommand(client *c, int where) { return; } else { if (zsetLength(o) != 0) { - /* Non empty zset, this is like a normal Z[REV]POP. */ + /* Non empty zset, this is like a normal ZPOP[MIN|MAX]. */ genericZpopCommand(c,&c->argv[j],1,where,1,NULL); - /* Replicate it as an Z[REV]POP instead of BZ[REV]POP. */ + /* Replicate it as an ZPOP[MIN|MAX] instead of BZPOP[MIN|MAX]. */ rewriteClientCommandVector(c,2, where == ZSET_MAX ? shared.zpopmax : shared.zpopmin, c->argv[j]); From d168b245b331e5fd24e7edb15d0d04213f3a180f Mon Sep 17 00:00:00 2001 From: artix Date: Fri, 1 Jun 2018 15:32:44 +0200 Subject: [PATCH 019/219] Cluster Manager: fixed master_id check in clusterManagerNodeLoadInfo --- src/redis-cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index e1c1983d5..f406d505a 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -3088,7 +3088,7 @@ static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts, currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL; else if (strcmp(flag, "slave") == 0) { currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE; - if (master_id == 0) { + if (master_id != NULL) { if (currentNode->replicate) sdsfree(currentNode->replicate); currentNode->replicate = sdsnew(master_id); } From 7d269d5e633911628625d279b54e48c8b38fec90 Mon Sep 17 00:00:00 2001 From: WuYunlong Date: Sat, 2 Jun 2018 14:29:43 +0800 Subject: [PATCH 020/219] Fix DEBUG LOADAOF so that redis-server will not crash unexpectedly and will not be inconsistent after we call debug loadaof. Before this commit, there were 2 problems: 1, When appendonly is set to no and there is not a appendonly file, redis-server will crash if we call DEBUG LOADAOF. 2, When appendonly is set to no and there is a appendonly file, redis-server will hold different data after loading appendonly file. --- src/debug.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index 0ab864e7f..26ea41323 100644 --- a/src/debug.c +++ b/src/debug.c @@ -347,7 +347,11 @@ NULL serverLog(LL_WARNING,"DB reloaded by DEBUG RELOAD"); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { - if (server.aof_state == AOF_ON) flushAppendOnlyFile(1); + if (server.aof_state == AOF_OFF) { + addReply(c, shared.err); + return; + } + flushAppendOnlyFile(1); emptyDb(-1,EMPTYDB_NO_FLAGS,NULL); if (loadAppendOnlyFile(server.aof_filename) != C_OK) { addReply(c,shared.err); From 1749fe7a26e6ce53ab90271aa807380bd9458d3a Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Sat, 2 Jun 2018 18:22:20 -0700 Subject: [PATCH 021/219] Return early in XPENDING if sent a nonexistent consumer group. --- src/t_stream.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/t_stream.c b/src/t_stream.c index 6cbef56cb..ebcf1a558 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -1728,8 +1728,10 @@ void xpendingCommand(client *c) { /* If a consumer name was mentioned but it does not exist, we can * just return an empty array. */ - if (consumername && consumer == NULL) + if (consumername && consumer == NULL) { addReplyMultiBulkLen(c,0); + return; + } rax *pel = consumer ? consumer->pel : group->pel; unsigned char startkey[sizeof(streamID)]; From e1222d8b10552cda6f6fdf1178a2ca25ad77894b Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Sun, 3 Jun 2018 15:54:30 +0300 Subject: [PATCH 022/219] Clean gcc 7.x warnings, redis-cli cluster fix. --- src/hyperloglog.c | 14 +++++++------- src/lzf_d.c | 23 +++++++++++++---------- src/redis-cli.c | 2 +- src/siphash.c | 24 ++++++++++++------------ 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/hyperloglog.c b/src/hyperloglog.c index 0670c1cf5..01a409bb5 100644 --- a/src/hyperloglog.c +++ b/src/hyperloglog.c @@ -429,14 +429,14 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) { } switch(len & 7) { - case 7: h ^= (uint64_t)data[6] << 48; - case 6: h ^= (uint64_t)data[5] << 40; - case 5: h ^= (uint64_t)data[4] << 32; - case 4: h ^= (uint64_t)data[3] << 24; - case 3: h ^= (uint64_t)data[2] << 16; - case 2: h ^= (uint64_t)data[1] << 8; + case 7: h ^= (uint64_t)data[6] << 48; /* fall-thru */ + case 6: h ^= (uint64_t)data[5] << 40; /* fall-thru */ + case 5: h ^= (uint64_t)data[4] << 32; /* fall-thru */ + case 4: h ^= (uint64_t)data[3] << 24; /* fall-thru */ + case 3: h ^= (uint64_t)data[2] << 16; /* fall-thru */ + case 2: h ^= (uint64_t)data[1] << 8; /* fall-thru */ case 1: h ^= (uint64_t)data[0]; - h *= m; + h *= m; /* fall-thru */ }; h ^= h >> r; diff --git a/src/lzf_d.c b/src/lzf_d.c index c32be8e87..93f43c27c 100644 --- a/src/lzf_d.c +++ b/src/lzf_d.c @@ -86,6 +86,8 @@ lzf_decompress (const void *const in_data, unsigned int in_len, #ifdef lzf_movsb lzf_movsb (op, ip, ctrl); #else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" switch (ctrl) { case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++; @@ -97,6 +99,7 @@ lzf_decompress (const void *const in_data, unsigned int in_len, case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++; case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++; } +#pragma GCC diagnostic pop #endif } else /* back reference */ @@ -163,17 +166,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len, break; - case 9: *op++ = *ref++; - case 8: *op++ = *ref++; - case 7: *op++ = *ref++; - case 6: *op++ = *ref++; - case 5: *op++ = *ref++; - case 4: *op++ = *ref++; - case 3: *op++ = *ref++; - case 2: *op++ = *ref++; - case 1: *op++ = *ref++; + case 9: *op++ = *ref++; /* fall-thru */ + case 8: *op++ = *ref++; /* fall-thru */ + case 7: *op++ = *ref++; /* fall-thru */ + case 6: *op++ = *ref++; /* fall-thru */ + case 5: *op++ = *ref++; /* fall-thru */ + case 4: *op++ = *ref++; /* fall-thru */ + case 3: *op++ = *ref++; /* fall-thru */ + case 2: *op++ = *ref++; /* fall-thru */ + case 1: *op++ = *ref++; /* fall-thru */ case 0: *op++ = *ref++; /* two octets more */ - *op++ = *ref++; + *op++ = *ref++; /* fall-thru */ } #endif } diff --git a/src/redis-cli.c b/src/redis-cli.c index f406d505a..05865a925 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -5091,7 +5091,7 @@ static int clusterManagerCommandImport(int argc, char **argv) { // Build a slot -> node map clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS]; - memset(slots_map, 0, sizeof(slots_map) / sizeof(clusterManagerNode *)); + memset(slots_map, 0, sizeof(slots_map)); listIter li; listNode *ln; for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) { diff --git a/src/siphash.c b/src/siphash.c index 6c41fe6b6..6b9419031 100644 --- a/src/siphash.c +++ b/src/siphash.c @@ -142,12 +142,12 @@ uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k) { } switch (left) { - case 7: b |= ((uint64_t)in[6]) << 48; - case 6: b |= ((uint64_t)in[5]) << 40; - case 5: b |= ((uint64_t)in[4]) << 32; - case 4: b |= ((uint64_t)in[3]) << 24; - case 3: b |= ((uint64_t)in[2]) << 16; - case 2: b |= ((uint64_t)in[1]) << 8; + case 7: b |= ((uint64_t)in[6]) << 48; /* fall-thru */ + case 6: b |= ((uint64_t)in[5]) << 40; /* fall-thru */ + case 5: b |= ((uint64_t)in[4]) << 32; /* fall-thru */ + case 4: b |= ((uint64_t)in[3]) << 24; /* fall-thru */ + case 3: b |= ((uint64_t)in[2]) << 16; /* fall-thru */ + case 2: b |= ((uint64_t)in[1]) << 8; /* fall-thru */ case 1: b |= ((uint64_t)in[0]); break; case 0: break; } @@ -202,12 +202,12 @@ uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k) } switch (left) { - case 7: b |= ((uint64_t)siptlw(in[6])) << 48; - case 6: b |= ((uint64_t)siptlw(in[5])) << 40; - case 5: b |= ((uint64_t)siptlw(in[4])) << 32; - case 4: b |= ((uint64_t)siptlw(in[3])) << 24; - case 3: b |= ((uint64_t)siptlw(in[2])) << 16; - case 2: b |= ((uint64_t)siptlw(in[1])) << 8; + case 7: b |= ((uint64_t)siptlw(in[6])) << 48; /* fall-thru */ + case 6: b |= ((uint64_t)siptlw(in[5])) << 40; /* fall-thru */ + case 5: b |= ((uint64_t)siptlw(in[4])) << 32; /* fall-thru */ + case 4: b |= ((uint64_t)siptlw(in[3])) << 24; /* fall-thru */ + case 3: b |= ((uint64_t)siptlw(in[2])) << 16; /* fall-thru */ + case 2: b |= ((uint64_t)siptlw(in[1])) << 8; /* fall-thru */ case 1: b |= ((uint64_t)siptlw(in[0])); break; case 0: break; } From 10dedc25868c08bb1bf5da8252c8b11ad2ab370d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E7=A3=8A?= Date: Fri, 20 Apr 2018 20:19:03 +0800 Subject: [PATCH 023/219] Fix core dump when using 'command getkeys' with wrong arguments. --- src/db.c | 6 +++--- src/server.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/db.c b/src/db.c index 5f733e2d8..694131dd6 100644 --- a/src/db.c +++ b/src/db.c @@ -1224,7 +1224,7 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu num = atoi(argv[2]->ptr); /* Sanity check. Don't return any key if the command is going to * reply with syntax error. */ - if (num > (argc-3)) { + if (num < 1 || num > (argc-3)) { *numkeys = 0; return NULL; } @@ -1253,7 +1253,7 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) num = atoi(argv[2]->ptr); /* Sanity check. Don't return any key if the command is going to * reply with syntax error. */ - if (num > (argc-3)) { + if (num <= 0 || num > (argc-3)) { *numkeys = 0; return NULL; } @@ -1398,7 +1398,7 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) if (streams_pos != -1) num = argc - streams_pos - 1; /* Syntax error. */ - if (streams_pos == -1 || num % 2 != 0) { + if (streams_pos == -1 || num == 0 || num % 2 != 0) { *numkeys = 0; return NULL; } diff --git a/src/server.c b/src/server.c index 404429beb..a9da88f4e 100644 --- a/src/server.c +++ b/src/server.c @@ -2842,7 +2842,10 @@ NULL int *keys, numkeys, j; if (!cmd) { - addReplyErrorFormat(c,"Invalid command specified"); + addReplyError(c,"Invalid command specified"); + return; + } else if (cmd->getkeys_proc == NULL && cmd->firstkey == 0) { + addReplyError(c,"The command has no key arguments"); return; } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) || ((c->argc-2) < -cmd->arity)) @@ -2852,9 +2855,13 @@ NULL } keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys); - addReplyMultiBulkLen(c,numkeys); - for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); - getKeysFreeResult(keys); + if (!keys) { + addReplyError(c,"Invalid arguments specified for command"); + } else { + addReplyMultiBulkLen(c,numkeys); + for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); + getKeysFreeResult(keys); + } } else { addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try COMMAND HELP", (char*)c->argv[1]->ptr); } From 7c6f1be5df4b3f453ab3928b2239ed60138e1cef Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 4 Jun 2018 12:58:26 +0200 Subject: [PATCH 024/219] XGROUP DESTROY implemented. --- src/t_stream.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/t_stream.c b/src/t_stream.c index 6cbef56cb..1f904eff2 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -1542,7 +1542,6 @@ NULL /* Certain subcommands require the group to exist. */ if ((cg = streamLookupCG(s,grpname)) == NULL && (!strcasecmp(opt,"SETID") || - !strcasecmp(opt,"DELGROUP") || !strcasecmp(opt,"DELCONSUMER"))) { addReplyErrorFormat(c, "-NOGROUP No such consumer group '%s' " @@ -1569,7 +1568,14 @@ NULL sdsnew("-BUSYGROUP Consumer Group name already exists\r\n")); } } else if (!strcasecmp(opt,"SETID") && c->argc == 5) { - } else if (!strcasecmp(opt,"DELGROUP") && c->argc == 4) { + } else if (!strcasecmp(opt,"DESTROY") && c->argc == 4) { + if (cg) { + raxRemove(s->cgroups,(unsigned char*)grpname,sdslen(grpname),NULL); + streamFreeCG(cg); + addReply(c,shared.cone); + } else { + addReply(c,shared.czero); + } } else if (!strcasecmp(opt,"DELCONSUMER") && c->argc == 5) { /* Delete the consumer and returns the number of pending messages * that were yet associated with such a consumer. */ From 05a29966410c82bd3603ae290a342e856158e18a Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 4 Jun 2018 17:26:16 +0200 Subject: [PATCH 025/219] Rax library updated. --- src/rax.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++--------- src/rax.h | 1 + 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/rax.c b/src/rax.c index 4e45fd2c3..8764dd8c9 100644 --- a/src/rax.c +++ b/src/rax.c @@ -359,7 +359,18 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi * parent's node is returned as '*plink' if not NULL. Finally, if the * search stopped in a compressed node, '*splitpos' returns the index * inside the compressed node where the search ended. This is useful to - * know where to split the node for insertion. */ + * know where to split the node for insertion. + * + * Note that when we stop in the middle of a compressed node with + * a perfect match, this function will return a length equal to the + * 'len' argument (all the key matched), and will return a *splitpos which is + * always positive (that will represent the index of the character immediately + * *after* the last match in the current compressed node). + * + * When instead we stop at a compressed node and *splitpos is zero, it + * means that the current node represents the key (that is, none of the + * compressed node characters are needed to represent the key, just all + * its parents nodes). */ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) { raxNode *h = rax->head; raxNode **parentlink = &rax->head; @@ -405,10 +416,12 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode /* Insert the element 's' of size 'len', setting as auxiliary data * the pointer 'data'. If the element is already present, the associated - * data is updated, and 0 is returned, otherwise the element is inserted - * and 1 is returned. On out of memory the function returns 0 as well but - * sets errno to ENOMEM, otherwise errno will be set to 0. */ -int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { + * data is updated (only if 'overwrite' is set to 1), and 0 is returned, + * otherwise the element is inserted and 1 is returned. On out of memory the + * function returns 0 as well but sets errno to ENOMEM, otherwise errno will + * be set to 0. + */ +int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) { size_t i; int j = 0; /* Split position. If raxLowWalk() stops in a compressed node, the index 'j' represents the char we stopped within the @@ -426,7 +439,8 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { * data pointer. */ if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) { debugf("### Insert: node representing key exists\n"); - if (!h->iskey || h->isnull) { + /* Make space for the value pointer if needed. */ + if (!h->iskey || (h->isnull && overwrite)) { h = raxReallocForData(h,data); if (h) memcpy(parentlink,&h,sizeof(h)); } @@ -434,12 +448,17 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { errno = ENOMEM; return 0; } + + /* Update the existing key if there is already one. */ if (h->iskey) { if (old) *old = raxGetData(h); - raxSetData(h,data); + if (overwrite) raxSetData(h,data); errno = 0; return 0; /* Element already exists. */ } + + /* Otherwise set the node as a key. Note that raxSetData() + * will set h->iskey. */ raxSetData(h,data); rax->numele++; return 1; /* Element inserted. */ @@ -793,6 +812,19 @@ oom: return 0; } +/* Overwriting insert. Just a wrapper for raxGenericInsert() that will + * update the element if there is already one for the same key. */ +int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { + return raxGenericInsert(rax,s,len,data,old,1); +} + +/* Non overwriting insert function: this if an element with the same key + * exists, the value is not updated and the function returns 0. + * This is a just a wrapper for raxGenericInsert(). */ +int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { + return raxGenericInsert(rax,s,len,data,old,0); +} + /* Find a key in the rax, returns raxNotFound special void pointer value * if the item was not found, otherwise the value associated with the * item is returned. */ @@ -1523,11 +1555,26 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) { /* If there was no mismatch we are into a node representing the * key, (but which is not a key or the seek operator does not * include 'eq'), or we stopped in the middle of a compressed node - * after processing all the key. Cotinue iterating as this was + * after processing all the key. Continue iterating as this was * a legitimate key we stopped at. */ it->flags &= ~RAX_ITER_JUST_SEEKED; - if (gt && !raxIteratorNextStep(it,0)) return 0; - if (lt && !raxIteratorPrevStep(it,0)) return 0; + if (it->node->iscompr && it->node->iskey && splitpos && lt) { + /* If we stopped in the middle of a compressed node with + * perfect match, and the condition is to seek a key "<" than + * the specified one, then if this node is a key it already + * represents our match. For instance we may have nodes: + * + * "f" -> "oobar" = 1 -> "" = 2 + * + * Representing keys "f" = 1, "foobar" = 2. A seek for + * the key < "foo" will stop in the middle of the "oobar" + * node, but will be our match, representing the key "f". + * + * So in that case, we don't seek backward. */ + } else { + if (gt && !raxIteratorNextStep(it,0)) return 0; + if (lt && !raxIteratorPrevStep(it,0)) return 0; + } it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */ } } else { diff --git a/src/rax.h b/src/rax.h index b4e2fd91e..5b4d45167 100644 --- a/src/rax.h +++ b/src/rax.h @@ -145,6 +145,7 @@ extern void *raxNotFound; /* Exported API. */ rax *raxNew(void); int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); +int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); int raxRemove(rax *rax, unsigned char *s, size_t len, void **old); void *raxFind(rax *rax, unsigned char *s, size_t len); void raxFree(rax *rax); From 36b392a0b2528ad22f016e9ca8fce93eb6dab3a0 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 4 Jun 2018 17:28:03 +0200 Subject: [PATCH 026/219] XGROUP SETID implemented + consumer groups core fixes. Now that we have SETID, the inetrnals of consumer groups should be able to handle the case of the same message delivered multiple times just as a side effect of calling XREADGROUP. Normally this should never happen but if the admin manually "XGROUP SETID mykey mygroup 0", messages will get re-delivered to clients waiting for the ">" special ID. The consumer groups internals were not able to handle the case of a message re-delivered in this circumstances that was already assigned to another owner. --- src/t_stream.c | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/t_stream.c b/src/t_stream.c index 1f904eff2..04125bfa7 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -41,6 +41,7 @@ #define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1) /* Same fields as master entry. */ void streamFreeCG(streamCG *cg); +void streamFreeNACK(streamNACK *na); size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer); /* ----------------------------------------------------------------------- @@ -867,18 +868,41 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end /* If a group is passed, we need to create an entry in the * PEL (pending entries list) of this group *and* this consumer. - * Note that we are sure about the fact the message is not already - * associated with some other consumer, because if we reached this - * loop the IDs the user is requesting are greater than any message - * delivered for this group. */ + * + * Note that we cannot be sure about the fact the message is not + * already owned by another consumer, because the admin is able + * to change the consumer group last delivered ID using the + * XGROUP SETID command. So if we find that there is already + * a NACK for the entry, we need to associate it to the new + * consumer. */ if (group && !(flags & STREAM_RWR_NOACK)) { unsigned char buf[sizeof(streamID)]; streamEncodeID(buf,&id); + + /* Try to add a new NACK. Most of the time this will work and + * will not require extra lookups. We'll fix the problem later + * if we find that there is already a entry for this ID. */ streamNACK *nack = streamCreateNACK(consumer); int retval = 0; - retval += raxInsert(group->pel,buf,sizeof(buf),nack,NULL); - retval += raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL); - serverAssert(retval == 2); /* Make sure entry was inserted. */ + retval += raxTryInsert(group->pel,buf,sizeof(buf),nack,NULL); + retval += raxTryInsert(consumer->pel,buf,sizeof(buf),nack,NULL); + + /* Now we can check if the entry was already busy, and + * in that case reassign the entry to the new consumer. */ + if (retval == 0) { + streamFreeNACK(nack); + nack = raxFind(group->pel,buf,sizeof(buf)); + serverAssert(nack != raxNotFound); + raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL); + /* Update the consumer and idle time. */ + nack->consumer = consumer; + nack->delivery_time = mstime(); + nack->delivery_count++; + /* Add the entry in the new consumer local PEL. */ + raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL); + } else if (retval == 1) { + serverPanic("NACK half-created. Should not be possible."); + } /* Propagate as XCLAIM. */ if (spi) { @@ -1568,6 +1592,14 @@ NULL sdsnew("-BUSYGROUP Consumer Group name already exists\r\n")); } } else if (!strcasecmp(opt,"SETID") && c->argc == 5) { + streamID id; + if (!strcmp(c->argv[4]->ptr,"$")) { + id = s->last_id; + } else if (streamParseIDOrReply(c,c->argv[4],&id,0) != C_OK) { + return; + } + cg->last_id = id; + addReply(c,shared.ok); } else if (!strcasecmp(opt,"DESTROY") && c->argc == 4) { if (cg) { raxRemove(s->cgroups,(unsigned char*)grpname,sdslen(grpname),NULL); @@ -1976,7 +2008,7 @@ void xclaimCommand(client *c) { nack->delivery_time = deliverytime; /* Set the delivery attempts counter if given. */ if (retrycount >= 0) nack->delivery_count = retrycount; - /* Add the entry in the new cosnumer local PEL. */ + /* Add the entry in the new consumer local PEL. */ raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL); /* Send the reply for this entry. */ if (justid) { From 9e25f3e1defd29d4cf37380870141c40bf97f121 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 5 Jun 2018 16:34:31 +0200 Subject: [PATCH 027/219] Remove XINFO special form. As observed by Michael Grunder this usage while practical is inconsistent because for instance it does not work against a key called HELP. Removed. --- src/t_stream.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/t_stream.c b/src/t_stream.c index 04125bfa7..fe702110a 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -2131,14 +2131,12 @@ void xtrimCommand(client *c) { /* XINFO CONSUMERS key group * XINFO GROUPS * XINFO STREAM - * XINFO (alias of XINFO STREAM key) * XINFO HELP. */ void xinfoCommand(client *c) { const char *help[] = { "CONSUMERS -- Show consumer groups of group .", "GROUPS -- Show the stream consumer groups.", "STREAM -- Show information about the stream.", -" -- Alias for STREAM .", "HELP -- Print this help.", NULL }; @@ -2150,16 +2148,15 @@ NULL if (!strcasecmp(c->argv[1]->ptr,"HELP")) { addReplyHelp(c, help); return; + } else if (c->argc < 3) { + addReplyError(c,"syntax error, try 'XINFO HELP'"); + return; } - /* Handle the fact that no subcommand means "STREAM". */ - if (c->argc == 2) { - opt = "STREAM"; - key = c->argv[1]; - } else { - opt = c->argv[1]->ptr; - key = c->argv[2]; - } + /* With the exception of HELP handled before any other sub commands, all + * the ones are in the form of " ". */ + opt = c->argv[1]->ptr; + key = c->argv[2]; /* Lookup the key now, this is common for all the subcommands but HELP. */ robj *o = lookupKeyWriteOrReply(c,key,shared.nokeyerr); @@ -2218,9 +2215,7 @@ NULL addReplyLongLong(c,raxSize(cg->pel)); } raxStop(&ri); - } else if (c->argc == 2 || - (!strcasecmp(opt,"STREAM") && c->argc == 3)) - { + } else if (!strcasecmp(opt,"STREAM") && c->argc == 3) { /* XINFO STREAM (or the alias XINFO ). */ addReplyMultiBulkLen(c,12); addReplyStatus(c,"length"); From 0a698e499a0b543b350544c63701ab944151c6ed Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 5 Jun 2018 17:01:47 +0200 Subject: [PATCH 028/219] ZPOP: invert score-ele to match ZRANGE WITHSCORES order. --- src/t_zset.c | 2 +- tests/unit/type/zset.tcl | 50 ++++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/t_zset.c b/src/t_zset.c index ae0922342..196864779 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -3163,8 +3163,8 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey signalModifiedKey(c->db,key); } - addReplyDouble(c,score); addReplyBulkCBuffer(c,ele,sdslen(ele)); + addReplyDouble(c,score); sdsfree(ele); arraylen += 2; diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index f25cfc122..45f024cc3 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -653,11 +653,11 @@ start_server {tags {"zset"}} { r del zset assert_equal {} [r zpopmin zset] create_zset zset {-1 a 1 b 2 c 3 d 4 e} - assert_equal {-1 a} [r zpopmin zset] - assert_equal {1 b} [r zpopmin zset] - assert_equal {4 e} [r zpopmax zset] - assert_equal {3 d} [r zpopmax zset] - assert_equal {2 c} [r zpopmin zset] + assert_equal {a -1} [r zpopmin zset] + assert_equal {b 1} [r zpopmin zset] + assert_equal {e 4} [r zpopmax zset] + assert_equal {d 3} [r zpopmax zset] + assert_equal {c 2} [r zpopmin zset] assert_equal 0 [r exists zset] r set foo bar assert_error "*WRONGTYPE*" {r zpopmin foo} @@ -669,8 +669,8 @@ start_server {tags {"zset"}} { assert_equal {} [r zpopmin z1 2] assert_error "*WRONGTYPE*" {r zpopmin foo 2} create_zset z1 {0 a 1 b 2 c 3 d} - assert_equal {0 a 1 b} [r zpopmin z1 2] - assert_equal {3 d 2 c} [r zpopmax z1 2] + assert_equal {a 0 b 1} [r zpopmin z1 2] + assert_equal {d 3 c 2} [r zpopmax z1 2] } test "BZPOP with a single existing sorted set - $encoding" { @@ -678,11 +678,11 @@ start_server {tags {"zset"}} { create_zset zset {0 a 1 b 2 c} $rd bzpopmin zset 5 - assert_equal {zset 0 a} [$rd read] + assert_equal {zset a 0} [$rd read] $rd bzpopmin zset 5 - assert_equal {zset 1 b} [$rd read] + assert_equal {zset b 1} [$rd read] $rd bzpopmax zset 5 - assert_equal {zset 2 c} [$rd read] + assert_equal {zset c 2} [$rd read] assert_equal 0 [r exists zset] } @@ -692,16 +692,16 @@ start_server {tags {"zset"}} { create_zset z2 {3 d 4 e 5 f} $rd bzpopmin z1 z2 5 - assert_equal {z1 0 a} [$rd read] + assert_equal {z1 a 0} [$rd read] $rd bzpopmax z1 z2 5 - assert_equal {z1 2 c} [$rd read] + assert_equal {z1 c 2} [$rd read] assert_equal 1 [r zcard z1] assert_equal 3 [r zcard z2] $rd bzpopmax z2 z1 5 - assert_equal {z2 5 f} [$rd read] + assert_equal {z2 f 5} [$rd read] $rd bzpopmin z2 z1 5 - assert_equal {z2 3 d} [$rd read] + assert_equal {z2 d 3} [$rd read] assert_equal 1 [r zcard z1] assert_equal 1 [r zcard z2] } @@ -711,9 +711,9 @@ start_server {tags {"zset"}} { r del z1 create_zset z2 {3 d 4 e 5 f} $rd bzpopmax z1 z2 5 - assert_equal {z2 5 f} [$rd read] + assert_equal {z2 f 5} [$rd read] $rd bzpopmin z2 z1 5 - assert_equal {z2 3 d} [$rd read] + assert_equal {z2 d 3} [$rd read] assert_equal 0 [r zcard z1] assert_equal 1 [r zcard z2] } @@ -1107,7 +1107,7 @@ start_server {tags {"zset"}} { r del zset r zadd zset 1 bar $rd read - } {zset 1 bar} + } {zset bar 1} test "BZPOPMIN, ZADD + DEL + SET should not awake blocked client" { set rd [redis_deferring_client] @@ -1124,7 +1124,7 @@ start_server {tags {"zset"}} { r del zset r zadd zset 1 bar $rd read - } {zset 1 bar} + } {zset bar 1} test "BZPOPMIN with same key multiple times should work" { set rd [redis_deferring_client] @@ -1133,18 +1133,18 @@ start_server {tags {"zset"}} { # Data arriving after the BZPOPMIN. $rd bzpopmin z1 z2 z2 z1 0 r zadd z1 0 a - assert_equal [$rd read] {z1 0 a} + assert_equal [$rd read] {z1 a 0} $rd bzpopmin z1 z2 z2 z1 0 r zadd z2 1 b - assert_equal [$rd read] {z2 1 b} + assert_equal [$rd read] {z2 b 1} # Data already there. r zadd z1 0 a r zadd z2 1 b $rd bzpopmin z1 z2 z2 z1 0 - assert_equal [$rd read] {z1 0 a} + assert_equal [$rd read] {z1 a 0} $rd bzpopmin z1 z2 z2 z1 0 - assert_equal [$rd read] {z2 1 b} + assert_equal [$rd read] {z2 b 1} } test "MULTI/EXEC is isolated from the point of view of BZPOPMIN" { @@ -1157,7 +1157,7 @@ start_server {tags {"zset"}} { r zadd zset 2 c r exec $rd read - } {zset 0 a} + } {zset a 0} test "BZPOPMIN with variadic ZADD" { set rd [redis_deferring_client] @@ -1167,7 +1167,7 @@ start_server {tags {"zset"}} { if {$::valgrind} {after 100} assert_equal 2 [r zadd zset -1 foo 1 bar] if {$::valgrind} {after 100} - assert_equal {zset -1 foo} [$rd read] + assert_equal {zset foo -1} [$rd read] assert_equal {bar} [r zrange zset 0 -1] } @@ -1177,7 +1177,7 @@ start_server {tags {"zset"}} { $rd bzpopmin zset 0 after 1000 r zadd zset 0 foo - assert_equal {zset 0 foo} [$rd read] + assert_equal {zset foo 0} [$rd read] } } From b61416bdf47900fc777b205cafb7b7c8248bb76f Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 6 Jun 2018 11:40:19 +0200 Subject: [PATCH 029/219] Fix streamIteratorRemoveEntry() to update elements count. Close #4989. --- src/t_stream.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/t_stream.c b/src/t_stream.c index fe702110a..07af3ff85 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -691,6 +691,9 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) { aux = lpGetInteger(p); lp = lpReplaceInteger(lp,&p,aux+1); + /* Update the number of entries counter. */ + si->stream->length--; + /* Re-seek the iterator to fix the now messed up state. */ streamID start, end; if (si->rev) { From c85ae56edc982d28df5cb29eedcd9d8bc9840a05 Mon Sep 17 00:00:00 2001 From: shenlongxing Date: Mon, 4 Jun 2018 23:59:21 +0800 Subject: [PATCH 030/219] Fix write() errno error --- src/cluster.c | 2 +- src/redis-cli.c | 2 +- src/replication.c | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index 0635d7c07..f457280b8 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -2120,7 +2120,7 @@ void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) { nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf)); if (nwritten <= 0) { serverLog(LL_DEBUG,"I/O error writing to node link: %s", - strerror(errno)); + (nwritten == -1) ? strerror(errno) : "short write"); handleLinkIOError(link); return; } diff --git a/src/redis-cli.c b/src/redis-cli.c index 05865a925..0ee9f84ed 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -5575,7 +5575,7 @@ static void getRDB(void) { nwritten = write(fd, buf, nread); if (nwritten != nread) { fprintf(stderr,"Error writing data to file: %s\n", - strerror(errno)); + (nwritten == -1) ? strerror(errno) : "short write"); exit(1); } payload -= nread; diff --git a/src/replication.c b/src/replication.c index 8c01bfb51..0b7d57910 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1105,7 +1105,7 @@ void restartAOF() { #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { char buf[4096]; - ssize_t nread, readlen; + ssize_t nread, readlen, nwritten; off_t left; UNUSED(el); UNUSED(privdata); @@ -1206,8 +1206,9 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { } server.repl_transfer_lastio = server.unixtime; - if (write(server.repl_transfer_fd,buf,nread) != nread) { - serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno)); + if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) { + serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", + (nwritten == -1) ? strerror(errno) : "short write"); goto error; } server.repl_transfer_read += nread; From 19a438e2c09363d161ed1cfae415222d3d16bfb4 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 14:24:45 +0200 Subject: [PATCH 031/219] Streams: use non static macro node limits. Also add the concept of size/items limit, instead of just having as limit the number of bytes. --- redis.conf | 8 ++++++++ src/server.c | 2 ++ src/server.h | 6 +++++- src/t_stream.c | 14 +++++++++++++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/redis.conf b/redis.conf index f5b7d5fed..e7a227ab8 100644 --- a/redis.conf +++ b/redis.conf @@ -1106,6 +1106,14 @@ zset-max-ziplist-value 64 # composed of many HyperLogLogs with cardinality in the 0 - 15000 range. hll-sparse-max-bytes 3000 +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. +stream-node-max-bytes 4096 +stream-node-max-entires 100 + # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level # keys to values). The hash table implementation Redis uses (see dict.c) diff --git a/src/server.c b/src/server.c index 647aee24b..375c6477c 100644 --- a/src/server.c +++ b/src/server.c @@ -1485,6 +1485,8 @@ void initServerConfig(void) { server.zset_max_ziplist_entries = OBJ_ZSET_MAX_ZIPLIST_ENTRIES; server.zset_max_ziplist_value = OBJ_ZSET_MAX_ZIPLIST_VALUE; server.hll_sparse_max_bytes = CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES; + server.stream_node_max_bytes = OBJ_STREAM_NODE_MAX_BYTES; + server.stream_node_max_entries = OBJ_STREAM_NODE_MAX_ENTRIES; server.shutdown_asap = 0; server.cluster_enabled = 0; server.cluster_node_timeout = CLUSTER_DEFAULT_NODE_TIMEOUT; diff --git a/src/server.h b/src/server.h index d9c512c5e..c34cdcfbf 100644 --- a/src/server.h +++ b/src/server.h @@ -348,12 +348,14 @@ typedef long long mstime_t; /* millisecond time type. */ #define AOF_FSYNC_EVERYSEC 2 #define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC -/* Zip structure related defaults */ +/* Zipped structures related defaults */ #define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512 #define OBJ_HASH_MAX_ZIPLIST_VALUE 64 #define OBJ_SET_MAX_INTSET_ENTRIES 512 #define OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128 #define OBJ_ZSET_MAX_ZIPLIST_VALUE 64 +#define OBJ_STREAM_NODE_MAX_BYTES 4096 +#define OBJ_STREAM_NODE_MAX_ENTRIES 100 /* List defaults */ #define OBJ_LIST_MAX_ZIPLIST_SIZE -2 @@ -1177,6 +1179,8 @@ struct redisServer { size_t zset_max_ziplist_entries; size_t zset_max_ziplist_value; size_t hll_sparse_max_bytes; + size_t stream_node_max_bytes; + int64_t stream_node_max_entries; /* List parameters */ int list_max_ziplist_size; int list_compress_depth; diff --git a/src/t_stream.c b/src/t_stream.c index 07af3ff85..e5d29764e 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -237,8 +237,20 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, * regular stream entries (see below), and marks the fact that there are * no more entires, when we scan the stream from right to left. */ + /* First of all, check if we can append to the current macro node or + * if we need to switch to the next one. 'lp' will be set to NULL if + * the current node is full. */ + if (lp != NULL) { + if (lp_bytes > server.stream_node_max_bytes) { + lp = NULL; + } else { + int64_t count = lpGetInteger(lpFirst(lp)); + if (count > server.stream_node_max_entries) lp = NULL; + } + } + int flags = STREAM_ITEM_FLAG_NONE; - if (lp == NULL || lp_bytes > STREAM_BYTES_PER_LISTPACK) { + if (lp == NULL || lp_bytes > server.stream_node_max_bytes) { master_id = id; streamEncodeID(rax_key,&id); /* Create the listpack having the master entry ID and fields. */ From 6d34ff3645e97e4537ae9136ef4e015591142d01 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 14:30:25 +0200 Subject: [PATCH 032/219] Streams: max node limits only checked if non zero. --- src/t_stream.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/t_stream.c b/src/t_stream.c index e5d29764e..71ed63180 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -241,9 +241,11 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, * if we need to switch to the next one. 'lp' will be set to NULL if * the current node is full. */ if (lp != NULL) { - if (lp_bytes > server.stream_node_max_bytes) { + if (server.stream_node_max_bytes && + lp_bytes > server.stream_node_max_bytes) + { lp = NULL; - } else { + } else if (server.stream_node_max_entries) { int64_t count = lpGetInteger(lpFirst(lp)); if (count > server.stream_node_max_entries) lp = NULL; } From dfb3bf1b1e0b0a85fc18d4b721cbe73efbca2e3b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 14:36:13 +0200 Subject: [PATCH 033/219] Streams: make macro node limits configurable. --- src/config.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/config.c b/src/config.c index 5bc6aa2ed..bfb310303 100644 --- a/src/config.c +++ b/src/config.c @@ -547,6 +547,10 @@ void loadServerConfigFromString(char *config) { server.hash_max_ziplist_entries = memtoll(argv[1], NULL); } else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) { server.hash_max_ziplist_value = memtoll(argv[1], NULL); + } else if (!strcasecmp(argv[0],"stream-node-max-bytes") && argc == 2) { + server.stream_node_max_bytes = memtoll(argv[1], NULL); + } else if (!strcasecmp(argv[0],"stream-node-max-entries") && argc == 2) { + server.stream_node_max_entries = atoi(argv[1]); } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){ /* DEAD OPTION */ } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) { @@ -1083,6 +1087,10 @@ void configSetCommand(client *c) { "hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) { } config_set_numerical_field( "hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) { + } config_set_numerical_field( + "stream-node-max-bytes",server.stream_node_max_bytes,0,LLONG_MAX) { + } config_set_numerical_field( + "stream-node-max-entries",server.stream_node_max_entries,0,LLONG_MAX) { } config_set_numerical_field( "list-max-ziplist-size",server.list_max_ziplist_size,INT_MIN,INT_MAX) { } config_set_numerical_field( @@ -1267,6 +1275,10 @@ void configGetCommand(client *c) { server.hash_max_ziplist_entries); config_get_numerical_field("hash-max-ziplist-value", server.hash_max_ziplist_value); + config_get_numerical_field("stream-node-max-bytes", + server.stream_node_max_bytes); + config_get_numerical_field("stream-node-max-entries", + server.stream_node_max_entries); config_get_numerical_field("list-max-ziplist-size", server.list_max_ziplist_size); config_get_numerical_field("list-compress-depth", @@ -2056,6 +2068,8 @@ int rewriteConfig(char *path) { rewriteConfigNotifykeyspaceeventsOption(state); rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,OBJ_HASH_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,OBJ_HASH_MAX_ZIPLIST_VALUE); + rewriteConfigNumericalOption(state,"stream-node-max-bytes",server.stream_node_max_bytes,OBJ_STREAM_NODE_MAX_BYTES); + rewriteConfigNumericalOption(state,"stream-node-max-entries",server.stream_node_max_entries,OBJ_STREAM_NODE_MAX_ENTRIES); rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,OBJ_LIST_MAX_ZIPLIST_SIZE); rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,OBJ_LIST_COMPRESS_DEPTH); rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,OBJ_SET_MAX_INTSET_ENTRIES); From a7dbe37d53075118c6db37848007df49ca69ce9a Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 14:36:56 +0200 Subject: [PATCH 034/219] Typo: entires -> entries in several places. --- src/cluster.c | 2 +- src/t_stream.c | 8 ++++---- src/ziplist.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index f457280b8..17ba6a744 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -2377,7 +2377,7 @@ void clusterSendPing(clusterLink *link, int type) { * same time. * * Since we have non-voting slaves that lower the probability of an entry - * to feature our node, we set the number of entires per packet as + * to feature our node, we set the number of entries per packet as * 10% of the total nodes we have. */ wanted = floor(dictSize(server.cluster->nodes)/10); if (wanted < 3) wanted = 3; diff --git a/src/t_stream.c b/src/t_stream.c index 71ed63180..e6538bd89 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -222,7 +222,7 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, * +-------+---------+------------+---------+--/--+---------+---------+-+ * * count and deleted just represent respectively the total number of - * entires inside the listpack that are valid, and marked as deleted + * entries inside the listpack that are valid, and marked as deleted * (delted flag in the entry flags set). So the total number of items * actually inside the listpack (both deleted and not) is count+deleted. * @@ -235,7 +235,7 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, * * The "0" entry at the end is the same as the 'lp-count' entry in the * regular stream entries (see below), and marks the fact that there are - * no more entires, when we scan the stream from right to left. */ + * no more entries, when we scan the stream from right to left. */ /* First of all, check if we can append to the current macro node or * if we need to switch to the next one. 'lp' will be set to NULL if @@ -832,7 +832,7 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA * Note that this function is recursive in certian cases. When it's called * with a non NULL group and consumer argument, it may call * streamReplyWithRangeFromConsumerPEL() in order to get entries from the - * consumer pending entires list. However such a function will then call + * consumer pending entries list. However such a function will then call * streamReplyWithRange() in order to emit single entries (found in the * PEL by ID) to the client. This is the use case for the STREAM_RWR_RAWENTRIES * flag. @@ -1840,7 +1840,7 @@ void xpendingCommand(client *c) { * becomes the specified . If the minimum idle time specified * is zero, messages are claimed regardless of their idle time. * - * All the messages that cannot be found inside the pending entires list + * All the messages that cannot be found inside the pending entries list * are ignored, but in case the FORCE option is used. In that case we * create the NACK (representing a not yet acknowledged message) entry in * the consumer group PEL. diff --git a/src/ziplist.c b/src/ziplist.c index ea27db7fb..90bb9c81e 100644 --- a/src/ziplist.c +++ b/src/ziplist.c @@ -27,7 +27,7 @@ * traversal. * * is the number of entries. When there are more than - * 2^16-2 entires, this value is set to 2^16-1 and we need to traverse the + * 2^16-2 entries, this value is set to 2^16-1 and we need to traverse the * entire list to know how many items it holds. * * is a special entry representing the end of the ziplist. @@ -256,7 +256,7 @@ #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1) /* Increment the number of items field in the ziplist header. Note that this - * macro should never overflow the unsigned 16 bit integer, since entires are + * macro should never overflow the unsigned 16 bit integer, since entries are * always pushed one at a time. When UINT16_MAX is reached we want the count * to stay there to signal that a full scan is needed to get the number of * items inside the ziplist. */ From f847dd3ad8aac54f1f5b2919da3b27155926d343 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 14:37:56 +0200 Subject: [PATCH 035/219] Streams: better document the max node limits. --- redis.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/redis.conf b/redis.conf index e7a227ab8..3d1095c72 100644 --- a/redis.conf +++ b/redis.conf @@ -1110,7 +1110,10 @@ hll-sparse-max-bytes 3000 # tree of big nodes that encode multiple items inside. Using this configuration # it is possible to configure how big a single node can be in bytes, and the # maximum number of items it may contain before switching to a new node when -# appending new stream entries. +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entires limit by setting max-bytes to 0 and max-entries to the desired +# value. stream-node-max-bytes 4096 stream-node-max-entires 100 From 018cf118d62e3881ffe6b5ff9e534e309c6ef3ab Mon Sep 17 00:00:00 2001 From: shenlongxing Date: Thu, 7 Jun 2018 23:13:36 +0800 Subject: [PATCH 036/219] fix stream config typo --- redis.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis.conf b/redis.conf index 3d1095c72..507527cc8 100644 --- a/redis.conf +++ b/redis.conf @@ -1115,7 +1115,7 @@ hll-sparse-max-bytes 3000 # max entires limit by setting max-bytes to 0 and max-entries to the desired # value. stream-node-max-bytes 4096 -stream-node-max-entires 100 +stream-node-max-entries 100 # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level From 76ad23d012f194efa1acc0f8356d945b07681851 Mon Sep 17 00:00:00 2001 From: Itamar Haber Date: Thu, 7 Jun 2018 18:34:58 +0300 Subject: [PATCH 037/219] Adds MODULE HELP and implements addReplySubSyntaxError --- src/module.c | 13 +++++++++++-- src/networking.c | 12 ++++++++++++ src/server.h | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/module.c b/src/module.c index cb03ad2cd..e4d6e2cf2 100644 --- a/src/module.c +++ b/src/module.c @@ -4499,7 +4499,15 @@ int moduleUnload(sds name) { * MODULE LOAD [args...] */ void moduleCommand(client *c) { char *subcmd = c->argv[1]->ptr; - + if (c->argc == 2 && !strcasecmp(subcmd,"help")) { + const char *help[] = { +"list -- Return a list of loaded modules.", +"load [arg ...] -- Load a module library from .", +"unload -- Unload a module.", +NULL + }; + addReplyHelp(c, help); + } else if (!strcasecmp(subcmd,"load") && c->argc >= 3) { robj **argv = NULL; int argc = 0; @@ -4548,7 +4556,8 @@ void moduleCommand(client *c) { } dictReleaseIterator(di); } else { - addReply(c,shared.syntaxerr); + addReplySubSyntaxError(c); + return; } } diff --git a/src/networking.c b/src/networking.c index 00558974e..ac28ba2b8 100644 --- a/src/networking.c +++ b/src/networking.c @@ -560,6 +560,18 @@ void addReplyHelp(client *c, const char **help) { setDeferredMultiBulkLength(c,blenp,blen); } +/* Add a suggestive error reply. + * This function is typically invoked by from commands that support + * subcommands in response to an unknown subcommand or argument error. */ +void addReplySubSyntaxError(client *c) { + sds cmd = sdsnew((char*) c->argv[0]->ptr); + sdstoupper(cmd); + addReplyErrorFormat(c, + "Unknown subcommand or wrong number of arguments for '%s'. Try %s HELP.", + c->argv[1]->ptr,cmd); + sdsfree(cmd); +} + /* Copy 'src' client output buffers into 'dst' client output buffers. * The function takes care of freeing the old output buffers of the * destination client. */ diff --git a/src/server.h b/src/server.h index c34cdcfbf..26aee8932 100644 --- a/src/server.h +++ b/src/server.h @@ -1410,6 +1410,7 @@ void addReplyHumanLongDouble(client *c, long double d); void addReplyLongLong(client *c, long long ll); void addReplyMultiBulkLen(client *c, long length); void addReplyHelp(client *c, const char **help); +void addReplySubSyntaxError(client *c); void copyClientOutputBuffer(client *dst, client *src); size_t sdsZmallocSize(sds s); size_t getStringObjectSdsUsedMemory(robj *o); From c199280edb7ad344bb3d2af7572469f74d506da7 Mon Sep 17 00:00:00 2001 From: Itamar Haber Date: Thu, 7 Jun 2018 18:39:36 +0300 Subject: [PATCH 038/219] Globally applies addReplySubSyntaxError --- src/cluster.c | 3 +-- src/config.c | 3 +-- src/debug.c | 3 +-- src/object.c | 2 +- src/pubsub.c | 3 +-- src/scripting.c | 2 +- src/server.c | 2 +- src/slowlog.c | 2 +- 8 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index 17ba6a744..56deb927c 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -4746,8 +4746,7 @@ NULL clusterReset(hard); addReply(c,shared.ok); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLUSTER HELP", - (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); return; } } diff --git a/src/config.c b/src/config.c index bfb310303..4e6438e8e 100644 --- a/src/config.c +++ b/src/config.c @@ -2149,8 +2149,7 @@ NULL addReply(c,shared.ok); } } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CONFIG HELP", - (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); return; } } diff --git a/src/debug.c b/src/debug.c index 0ab864e7f..0c5ea9a9e 100644 --- a/src/debug.c +++ b/src/debug.c @@ -553,8 +553,7 @@ NULL clearReplicationId2(); addReply(c,shared.ok); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try DEBUG HELP", - (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); return; } } diff --git a/src/object.c b/src/object.c index 19af1ec0d..a4942fbe9 100644 --- a/src/object.c +++ b/src/object.c @@ -1197,7 +1197,7 @@ NULL * when the key is read or overwritten. */ addReplyLongLong(c,LFUDecrAndReturn(o)); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help", (char *)c->argv[1]->ptr); + addReplySubSyntaxError(c); } } diff --git a/src/pubsub.c b/src/pubsub.c index d1fffa20a..7f7be52da 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -372,7 +372,6 @@ NULL /* PUBSUB NUMPAT */ addReplyLongLong(c,listLength(server.pubsub_patterns)); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP", - (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); } } diff --git a/src/scripting.c b/src/scripting.c index 3c0597c7a..85bce7bef 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -1514,7 +1514,7 @@ NULL return; } } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP", (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); } } diff --git a/src/server.c b/src/server.c index 375c6477c..1e4d54b8b 100644 --- a/src/server.c +++ b/src/server.c @@ -2866,7 +2866,7 @@ NULL for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); getKeysFreeResult(keys); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try COMMAND HELP", (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); } } diff --git a/src/slowlog.c b/src/slowlog.c index 2613435af..aed5707da 100644 --- a/src/slowlog.c +++ b/src/slowlog.c @@ -187,6 +187,6 @@ NULL } setDeferredMultiBulkLength(c,totentries,sent); } else { - addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SLOWLOG HELP", (char*)c->argv[1]->ptr); + addReplySubSyntaxError(c); } } From b2fc2eaecb85b34a12c2b6a4db91fa5fc466870b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 18:52:01 +0200 Subject: [PATCH 039/219] Add the stream group to the script generating the help. --- utils/generate-command-help.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/generate-command-help.rb b/utils/generate-command-help.rb index f3dfb31b3..29acef69d 100755 --- a/utils/generate-command-help.rb +++ b/utils/generate-command-help.rb @@ -14,7 +14,8 @@ GROUPS = [ "scripting", "hyperloglog", "cluster", - "geo" + "geo", + "stream" ].freeze GROUPS_BY_NAME = Hash[* From 2268d7e5dd3a44a95f0e44ffe1afccebd8264b64 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 7 Jun 2018 18:53:00 +0200 Subject: [PATCH 040/219] redis-cli inline help updated. --- src/help.h | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/src/help.h b/src/help.h index 5f927c303..005afd941 100644 --- a/src/help.h +++ b/src/help.h @@ -1,4 +1,4 @@ -/* Automatically generated by utils/generate-command-help.rb, do not edit. */ +/* Automatically generated by generate-command-help.rb, do not edit. */ #ifndef __REDIS_HELP_H #define __REDIS_HELP_H @@ -17,7 +17,8 @@ static char *commandGroups[] = { "scripting", "hyperloglog", "cluster", - "geo" + "geo", + "stream" }; struct commandHelp { @@ -82,6 +83,16 @@ struct commandHelp { "Pop a value from a list, push it to another list and return it; or block until one is available", 2, "2.2.0" }, + { "BZPOPMAX", + "key [key ...] timeout", + "Remove and return the member with the highest score from one or more sorted sets, or block until one is available", + 4, + "5.0.0" }, + { "BZPOPMIN", + "key [key ...] timeout", + "Remove and return the member with the lowest score from one or more sorted sets, or block until one is available", + 4, + "5.0.0" }, { "CLIENT GETNAME", "-", "Get the current connection name", @@ -318,12 +329,12 @@ struct commandHelp { 0, "1.2.0" }, { "FLUSHALL", - "-", + "[ASYNC]", "Remove all keys from all databases", 9, "1.0.0" }, { "FLUSHDB", - "-", + "[ASYNC]", "Remove all keys from the current database", 9, "1.0.0" }, @@ -532,6 +543,36 @@ struct commandHelp { "Trim a list to the specified range", 2, "1.0.0" }, + { "MEMORY DOCTOR", + "-", + "Outputs memory problems report", + 9, + "4.0.0" }, + { "MEMORY HELP", + "-", + "Show helpful text about the different subcommands", + 9, + "4.0.0" }, + { "MEMORY MALLOC-STATS", + "-", + "Show allocator internal stats", + 9, + "4.0.0" }, + { "MEMORY PURGE", + "-", + "Ask the allocator to release memory", + 9, + "4.0.0" }, + { "MEMORY STATS", + "-", + "Show memory usage details", + 9, + "4.0.0" }, + { "MEMORY USAGE", + "key [SAMPLES count]", + "Estimate the memory usage of a key", + 9, + "4.0.0" }, { "MGET", "key [key ...]", "Get the values of all the given keys", @@ -723,7 +764,7 @@ struct commandHelp { 10, "3.2.0" }, { "SCRIPT EXISTS", - "script [script ...]", + "sha1 [sha1 ...]", "Check existence of scripts in the script cache.", 10, "2.6.0" }, @@ -758,7 +799,7 @@ struct commandHelp { 8, "1.0.0" }, { "SET", - "key value [EX seconds] [PX milliseconds] [NX|XX]", + "key value [expiration EX seconds|PX milliseconds] [NX|XX]", "Set the string value of a key", 1, "1.0.0" }, @@ -867,6 +908,11 @@ struct commandHelp { "Add multiple sets and store the resulting set in a key", 3, "1.0.0" }, + { "SWAPDB", + "index index", + "Swaps two Redis databases", + 8, + "4.0.0" }, { "SYNC", "-", "Internal command used for replication", @@ -877,6 +923,11 @@ struct commandHelp { "Return the current server time", 9, "2.6.0" }, + { "TOUCH", + "key [key ...]", + "Alters the last access time of a key(s). Returns the number of existing keys specified.", + 0, + "3.2.1" }, { "TTL", "key", "Get the time to live for a key", @@ -887,6 +938,11 @@ struct commandHelp { "Determine the type stored at key", 0, "1.0.0" }, + { "UNLINK", + "key [key ...]", + "Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.", + 0, + "4.0.0" }, { "UNSUBSCRIBE", "[channel [channel ...]]", "Stop listening for messages posted to the given channels", @@ -907,6 +963,41 @@ struct commandHelp { "Watch the given keys to determine execution of the MULTI/EXEC block", 7, "2.2.0" }, + { "XADD", + "key ID field string [field string ...]", + "Appends a new entry to a stream", + 14, + "5.0.0" }, + { "XLEN", + "key", + "Return the number of entires in a stream", + 14, + "5.0.0" }, + { "XPENDING", + "key group [start end count] [consumer]", + "Return information and entries from a stream conusmer group pending entries list, that are messages fetched but never acknowledged.", + 14, + "5.0.0" }, + { "XRANGE", + "key start end [COUNT count]", + "Return a range of elements in a stream, with IDs matching the specified IDs interval", + 14, + "5.0.0" }, + { "XREAD", + "[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]", + "Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.", + 14, + "5.0.0" }, + { "XREADGROUP", + "GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]", + "Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.", + 14, + "5.0.0" }, + { "XREVRANGE", + "key end start [COUNT count]", + "Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE", + 14, + "5.0.0" }, { "ZADD", "key [NX|XX] [CH] [INCR] score member [score member ...]", "Add one or more members to a sorted set, or update its score if it already exists", @@ -937,6 +1028,16 @@ struct commandHelp { "Count the number of members in a sorted set between a given lexicographical range", 4, "2.8.9" }, + { "ZPOPMAX", + "key [count]", + "Remove and return members with the highest scores in a sorted set", + 4, + "5.0.0" }, + { "ZPOPMIN", + "key [count]", + "Remove and return members with the lowest scores in a sorted set", + 4, + "5.0.0" }, { "ZRANGE", "key start stop [WITHSCORES]", "Return a range of members in a sorted set, by index", From 269e80526f1f90142661b9e25bff3a08639ce59c Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 8 Jun 2018 11:17:20 +0200 Subject: [PATCH 041/219] Implement DEBUG htstats-key. --- src/debug.c | 29 +++++++++++++++++++++++++++++ src/redis-cli.c | 2 ++ 2 files changed, 31 insertions(+) diff --git a/src/debug.c b/src/debug.c index 0ab864e7f..078ac3c67 100644 --- a/src/debug.c +++ b/src/debug.c @@ -290,6 +290,7 @@ void debugCommand(client *c) { "crash-and-recover -- Hard crash and restart after delay.", "digest -- Outputs an hex signature representing the current DB content.", "htstats -- Return hash table statistics of the specified Redis database.", +"htstats-key -- Like htstats but for the hash table stored as key's value.", "loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.", "lua-always-replicate-commands (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.", "object -- Show low level info about key and associated value.", @@ -547,6 +548,34 @@ NULL stats = sdscat(stats,buf); addReplyBulkSds(c,stats); + } else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) { + robj *o; + dict *ht = NULL; + + if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr)) + == NULL) return; + + /* Get the hash table reference from the object, if possible. */ + switch (o->encoding) { + case OBJ_ENCODING_SKIPLIST: + { + zset *zs = o->ptr; + ht = zs->dict; + } + break; + case OBJ_ENCODING_HT: + ht = o->ptr; + break; + } + + if (ht == NULL) { + addReplyError(c,"The value stored at the specified key is not " + "represented using an hash table"); + } else { + char buf[4096]; + dictGetStats(buf,sizeof(buf),ht); + addReplyBulkCString(c,buf); + } } else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) { serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id"); changeReplicationId(); diff --git a/src/redis-cli.c b/src/redis-cli.c index 0ee9f84ed..af5e6a230 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -1075,6 +1075,8 @@ static int cliSendCommand(int argc, char **argv, long repeat) { if (!strcasecmp(command,"info") || (argc >= 2 && !strcasecmp(command,"debug") && !strcasecmp(argv[1],"htstats")) || + (argc >= 2 && !strcasecmp(command,"debug") && + !strcasecmp(argv[1],"htstats-key")) || (argc >= 2 && !strcasecmp(command,"memory") && (!strcasecmp(argv[1],"malloc-stats") || !strcasecmp(argv[1],"doctor"))) || From 1210dd8a20ae4ffea88c3c68457e7e057410110e Mon Sep 17 00:00:00 2001 From: shenlongxing Date: Sat, 9 Jun 2018 22:24:33 +0800 Subject: [PATCH 042/219] fix integer case error --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index bfb310303..1b229c43e 100644 --- a/src/config.c +++ b/src/config.c @@ -1110,7 +1110,7 @@ void configSetCommand(client *c) { } config_set_numerical_field( "slowlog-max-len",ll,0,LLONG_MAX) { /* Cast to unsigned. */ - server.slowlog_max_len = (unsigned)ll; + server.slowlog_max_len = (unsigned long)ll; } config_set_numerical_field( "latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){ } config_set_numerical_field( From 21ef0376feaaacee22b7913cb08948f65f9b8198 Mon Sep 17 00:00:00 2001 From: Itamar Haber Date: Sat, 9 Jun 2018 20:54:05 +0300 Subject: [PATCH 043/219] Capitalizes subscommands --- src/module.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/module.c b/src/module.c index e4d6e2cf2..3aaa94fe4 100644 --- a/src/module.c +++ b/src/module.c @@ -4501,9 +4501,9 @@ void moduleCommand(client *c) { char *subcmd = c->argv[1]->ptr; if (c->argc == 2 && !strcasecmp(subcmd,"help")) { const char *help[] = { -"list -- Return a list of loaded modules.", -"load [arg ...] -- Load a module library from .", -"unload -- Unload a module.", +"LIST -- Return a list of loaded modules.", +"LOAD [arg ...] -- Load a module library from .", +"UNLOAD -- Unload a module.", NULL }; addReplyHelp(c, help); From fefde6e3e4addf860829d4848386d90889590eac Mon Sep 17 00:00:00 2001 From: Itamar Haber Date: Sat, 9 Jun 2018 21:03:52 +0300 Subject: [PATCH 044/219] Capitalizes subcommands & orders lexicographically --- src/cluster.c | 40 ++++++++++++++++++++-------------------- src/config.c | 8 ++++---- src/debug.c | 38 +++++++++++++++++++------------------- src/pubsub.c | 6 +++--- src/scripting.c | 10 +++++----- src/server.c | 6 +++--- src/slowlog.c | 6 +++--- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index 56deb927c..39aab0cc7 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -4178,27 +4178,27 @@ void clusterCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"addslots [slot ...] -- Assign slots to current node.", -"bumpepoch -- Advance the cluster config epoch.", -"count-failure-reports -- Return number of failure reports for .", -"countkeysinslot - Return the number of keys in .", -"delslots [slot ...] -- Delete slots information from current node.", -"failover [force|takeover] -- Promote current slave node to being a master.", -"forget -- Remove a node from the cluster.", -"getkeysinslot -- Return key names stored by current node in a slot.", -"flushslots -- Delete current node own slots information.", -"info - Return onformation about the cluster.", -"keyslot -- Return the hash slot for .", -"meet [bus-port] -- Connect nodes into a working cluster.", -"myid -- Return the node id.", -"nodes -- Return cluster configuration seen by node. Output format:", +"ADDSLOTS [slot ...] -- Assign slots to current node.", +"BUMPEPOCH -- Advance the cluster config epoch.", +"COUNT-failure-reports -- Return number of failure reports for .", +"COUNTKEYSINSLOT - Return the number of keys in .", +"DELSLOTS [slot ...] -- Delete slots information from current node.", +"FAILOVER [force|takeover] -- Promote current slave node to being a master.", +"FORGET -- Remove a node from the cluster.", +"GETKEYSINSLOT -- Return key names stored by current node in a slot.", +"FLUSHSLOTS -- Delete current node own slots information.", +"INFO - Return onformation about the cluster.", +"KEYSLOT -- Return the hash slot for .", +"MEET [bus-port] -- Connect nodes into a working cluster.", +"MYID -- Return the node id.", +"NODES -- Return cluster configuration seen by node. Output format:", " ... ", -"replicate -- Configure current node as slave to .", -"reset [hard|soft] -- Reset current node (default: soft).", -"set-config-epoch - Set config epoch of current node.", -"setslot (importing|migrating|stable|node ) -- Set slot state.", -"slaves -- Return slaves.", -"slots -- Return information about slots range mappings. Each range is made of:", +"REPLICATE -- Configure current node as slave to .", +"RESET [hard|soft] -- Reset current node (default: soft).", +"SET-config-epoch - Set config epoch of current node.", +"SETSLOT (importing|migrating|stable|node ) -- Set slot state.", +"SLAVES -- Return slaves.", +"SLOTS -- Return information about slots range mappings. Each range is made of:", " start, end, master and replicas IP addresses, ports and ids", NULL }; diff --git a/src/config.c b/src/config.c index 4e6438e8e..edcafa7ad 100644 --- a/src/config.c +++ b/src/config.c @@ -2121,10 +2121,10 @@ void configCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"get -- Return parameters matching the glob-like and their values.", -"set -- Set parameter to value.", -"resetstat -- Reset statistics reported by INFO.", -"rewrite -- Rewrite the configuration file.", +"GET -- Return parameters matching the glob-like and their values.", +"SET -- Set parameter to value.", +"RESETSTAT -- Reset statistics reported by INFO.", +"REWRITE -- Rewrite the configuration file.", NULL }; addReplyHelp(c, help); diff --git a/src/debug.c b/src/debug.c index 0c5ea9a9e..7a9bddebd 100644 --- a/src/debug.c +++ b/src/debug.c @@ -285,25 +285,25 @@ void computeDatasetDigest(unsigned char *final) { void debugCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"assert -- Crash by assertion failed.", -"change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.", -"crash-and-recover -- Hard crash and restart after delay.", -"digest -- Outputs an hex signature representing the current DB content.", -"htstats -- Return hash table statistics of the specified Redis database.", -"loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.", -"lua-always-replicate-commands (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.", -"object -- Show low level info about key and associated value.", -"panic -- Crash the server simulating a panic.", -"populate [prefix] [size] -- Create string keys named key:. If a prefix is specified is used instead of the 'key' prefix.", -"reload -- Save the RDB on disk and reload it back in memory.", -"restart -- Graceful restart: save config, db, restart.", -"sdslen -- Show low level SDS string info representing key and value.", -"segfault -- Crash the server with sigsegv.", -"set-active-expire (0|1) -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.", -"sleep -- Stop the server for . Decimals allowed.", -"structsize -- Return the size of different Redis core C structures.", -"ziplist -- Show low level info about the ziplist encoding.", -"error -- Return a Redis protocol error with as message. Useful for clients unit tests to simulate Redis errors.", +"ASSERT -- Crash by assertion failed.", +"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.", +"CRASH-and-recover -- Hard crash and restart after delay.", +"DIGEST -- Output a hex signature representing the current DB content.", +"ERROR -- Return a Redis protocol error with as message. Useful for clients unit tests to simulate Redis errors.", +"HTSTATS -- Return hash table statistics of the specified Redis database.", +"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.", +"LUA-ALWAYS-REPLICATE-COMMANDS (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.", +"OBJECT -- Show low level info about key and associated value.", +"PANIC -- Crash the server simulating a panic.", +"POPULATE [prefix] [size] -- Create string keys named key:. If a prefix is specified is used instead of the 'key' prefix.", +"RELOAD -- Save the RDB on disk and reload it back in memory.", +"RESTART -- Graceful restart: save config, db, restart.", +"SDSLEN -- Show low level SDS string info representing key and value.", +"SEGFAULT -- Crash the server with sigsegv.", +"SET-ACTIVE-EXPIRE (0|1) -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.", +"SLEEP -- Stop the server for . Decimals allowed.", +"STRUCTSIZE -- Return the size of different Redis core C structures.", +"ZIPLIST -- Show low level info about the ziplist encoding.", NULL }; addReplyHelp(c, help); diff --git a/src/pubsub.c b/src/pubsub.c index 7f7be52da..86a7f1c50 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -327,9 +327,9 @@ void publishCommand(client *c) { void pubsubCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"channels [] -- Return the currently active channels matching a pattern (default: all).", -"numpat -- Return number of subscriptions to patterns.", -"numsub [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).", +"CHANNELS [] -- Return the currently active channels matching a pattern (default: all).", +"NUMPAT -- Return number of subscriptions to patterns.", +"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).", NULL }; addReplyHelp(c, help); diff --git a/src/scripting.c b/src/scripting.c index 85bce7bef..f65540d89 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -1457,11 +1457,11 @@ void evalShaCommand(client *c) { void scriptCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { -"debug (yes|sync|no) -- Set the debug mode for subsequent scripts executed.", -"exists [ ...] -- Return information about the existence of the scripts in the script cache.", -"flush -- Flush the Lua scripts cache. Very dangerous on slaves.", -"kill -- Kill the currently executing Lua script.", -"load