mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
Merge branch 'unstable' into pending-querybuf
This commit is contained in:
commit
cbb2ac0799
@ -8,7 +8,9 @@ each source file that you contribute.
|
||||
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
|
||||
|
||||
* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
|
||||
requests. Everything else belongs to the Redis Google Group.
|
||||
requests. Everything else belongs to the Redis Google Group:
|
||||
|
||||
https://groups.google.com/forum/m/#!forum/Redis-db
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
|
@ -435,7 +435,7 @@ top comment inside `server.c`.
|
||||
After the command operates in some way, it returns a reply to the client,
|
||||
usually using `addReply()` or a similar function defined inside `networking.c`.
|
||||
|
||||
There are tons of commands implementations inside th Redis source code
|
||||
There are tons of commands implementations inside the Redis source code
|
||||
that can serve as examples of actual commands implementations. To write
|
||||
a few toy commands can be a good exercise to familiarize with the code base.
|
||||
|
||||
|
@ -215,4 +215,32 @@ ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra,
|
||||
return arena_ralloc_no_move(tsdn, ptr, oldsize, size, extra, zero);
|
||||
}
|
||||
|
||||
JEMALLOC_ALWAYS_INLINE int
|
||||
iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
|
||||
int defrag = 0;
|
||||
rtree_ctx_t rtree_ctx_fallback;
|
||||
rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
|
||||
szind_t szind;
|
||||
bool is_slab;
|
||||
rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr, true, &szind, &is_slab);
|
||||
if (likely(is_slab)) {
|
||||
/* Small allocation. */
|
||||
extent_t *slab = iealloc(tsdn, ptr);
|
||||
arena_t *arena = extent_arena_get(slab);
|
||||
szind_t binind = extent_szind_get(slab);
|
||||
bin_t *bin = &arena->bins[binind];
|
||||
malloc_mutex_lock(tsdn, &bin->lock);
|
||||
/* don't bother moving allocations from the slab currently used for new allocations */
|
||||
if (slab != bin->slabcur) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
size_t availregs = bin_info->nregs * bin->stats.curslabs;
|
||||
*bin_util = (bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
|
||||
defrag = 1;
|
||||
}
|
||||
malloc_mutex_unlock(tsdn, &bin->lock);
|
||||
}
|
||||
return defrag;
|
||||
}
|
||||
|
||||
#endif /* JEMALLOC_INTERNAL_INLINES_C_H */
|
||||
|
@ -120,3 +120,7 @@
|
||||
# define JEMALLOC_RESTRICT_RETURN
|
||||
# define JEMALLOC_ALLOCATOR
|
||||
#endif
|
||||
|
||||
/* This version of Jemalloc, modified for Redis, has the je_get_defrag_hint()
|
||||
* function. */
|
||||
#define JEMALLOC_FRAG_HINT
|
||||
|
11
deps/jemalloc/src/jemalloc.c
vendored
11
deps/jemalloc/src/jemalloc.c
vendored
@ -3324,3 +3324,14 @@ jemalloc_postfork_child(void) {
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation.
|
||||
* returns 0 if the allocation is in the currently active run,
|
||||
* or when it is not causing any frag issue (large or huge bin)
|
||||
* returns the bin utilization and run utilization both in fixed point 16:16.
|
||||
* If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */
|
||||
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
|
||||
get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
|
||||
assert(ptr != NULL);
|
||||
return iget_defrag_hint(TSDN_NULL, ptr, bin_util, run_util);
|
||||
}
|
||||
|
10
deps/lua/src/lua_cmsgpack.c
vendored
10
deps/lua/src/lua_cmsgpack.c
vendored
@ -385,6 +385,7 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
#endif
|
||||
|
||||
mp_encode_array(L,buf,len);
|
||||
luaL_checkstack(L, 1, "in function mp_encode_lua_table_as_array");
|
||||
for (j = 1; j <= len; j++) {
|
||||
lua_pushnumber(L,j);
|
||||
lua_gettable(L,-2);
|
||||
@ -400,6 +401,7 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
* Lua API, we need to iterate a first time. Note that an alternative
|
||||
* would be to do a single run, and then hack the buffer to insert the
|
||||
* map opcodes for message pack. Too hackish for this lib. */
|
||||
luaL_checkstack(L, 3, "in function mp_encode_lua_table_as_map");
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
lua_pop(L,1); /* remove value, keep key for next iteration. */
|
||||
@ -515,10 +517,14 @@ int mp_pack(lua_State *L) {
|
||||
if (nargs == 0)
|
||||
return luaL_argerror(L, 0, "MessagePack pack needs input.");
|
||||
|
||||
if (!lua_checkstack(L, nargs))
|
||||
return luaL_argerror(L, 0, "Too many arguments for MessagePack pack.");
|
||||
|
||||
buf = mp_buf_new(L);
|
||||
for(i = 1; i <= nargs; i++) {
|
||||
/* Copy argument i to top of stack for _encode processing;
|
||||
* the encode function pops it from the stack when complete. */
|
||||
luaL_checkstack(L, 1, "in function mp_check");
|
||||
lua_pushvalue(L, i);
|
||||
|
||||
mp_encode_lua_type(L,buf,0);
|
||||
@ -547,6 +553,7 @@ void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) {
|
||||
int index = 1;
|
||||
|
||||
lua_newtable(L);
|
||||
luaL_checkstack(L, 1, "in function mp_decode_to_lua_array");
|
||||
while(len--) {
|
||||
lua_pushnumber(L,index++);
|
||||
mp_decode_to_lua_type(L,c);
|
||||
@ -821,6 +828,9 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
/* Results are returned with the arg elements still
|
||||
|
54
deps/lua/src/lua_struct.c
vendored
54
deps/lua/src/lua_struct.c
vendored
@ -1,7 +1,7 @@
|
||||
/*
|
||||
** {======================================================
|
||||
** Library for packing/unpacking structures.
|
||||
** $Id: struct.c,v 1.4 2012/07/04 18:54:29 roberto Exp $
|
||||
** $Id: struct.c,v 1.7 2018/05/11 22:04:31 roberto Exp $
|
||||
** See Copyright Notice at the end of this file
|
||||
** =======================================================
|
||||
*/
|
||||
@ -15,8 +15,8 @@
|
||||
** h/H - signed/unsigned short
|
||||
** l/L - signed/unsigned long
|
||||
** T - size_t
|
||||
** i/In - signed/unsigned integer with size `n' (default is size of int)
|
||||
** cn - sequence of `n' chars (from/to a string); when packing, n==0 means
|
||||
** i/In - signed/unsigned integer with size 'n' (default is size of int)
|
||||
** cn - sequence of 'n' chars (from/to a string); when packing, n==0 means
|
||||
the whole string; when unpacking, n==0 means use the previous
|
||||
read number as the string length
|
||||
** s - zero-terminated string
|
||||
@ -89,14 +89,12 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
static int getnum (const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@ -117,9 +115,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@ -152,7 +150,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
@ -295,21 +293,26 @@ static int b_unpack (lua_State *L) {
|
||||
const char *fmt = luaL_checkstring(L, 1);
|
||||
size_t ld;
|
||||
const char *data = luaL_checklstring(L, 2, &ld);
|
||||
size_t pos = luaL_optinteger(L, 3, 1) - 1;
|
||||
size_t pos = luaL_optinteger(L, 3, 1);
|
||||
luaL_argcheck(L, pos > 0, 3, "offset must be 1 or greater");
|
||||
pos--; /* Lua indexes are 1-based, but here we want 0-based for C
|
||||
* pointer math. */
|
||||
int n = 0; /* number of results */
|
||||
defaultoptions(&h);
|
||||
lua_settop(L, 2);
|
||||
while (*fmt) {
|
||||
int opt = *fmt++;
|
||||
size_t size = optsize(L, opt, &fmt);
|
||||
pos += gettoalign(pos, &h, opt, size);
|
||||
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
|
||||
luaL_checkstack(L, 1, "too many results");
|
||||
luaL_argcheck(L, size <= ld && pos <= ld - size,
|
||||
2, "data string too short");
|
||||
/* stack space for item + next position */
|
||||
luaL_checkstack(L, 2, "too many results");
|
||||
switch (opt) {
|
||||
case 'b': case 'B': case 'h': case 'H':
|
||||
case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */
|
||||
int issigned = islower(opt);
|
||||
lua_Number res = getinteger(data+pos, h.endian, issigned, size);
|
||||
lua_pushnumber(L, res);
|
||||
lua_pushnumber(L, res); n++;
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
@ -319,25 +322,26 @@ static int b_unpack (lua_State *L) {
|
||||
float f;
|
||||
memcpy(&f, data+pos, size);
|
||||
correctbytes((char *)&f, sizeof(f), h.endian);
|
||||
lua_pushnumber(L, f);
|
||||
lua_pushnumber(L, f); n++;
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
double d;
|
||||
memcpy(&d, data+pos, size);
|
||||
correctbytes((char *)&d, sizeof(d), h.endian);
|
||||
lua_pushnumber(L, d);
|
||||
lua_pushnumber(L, d); n++;
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
if (size == 0) {
|
||||
if (!lua_isnumber(L, -1))
|
||||
luaL_error(L, "format `c0' needs a previous size");
|
||||
if (n == 0 || !lua_isnumber(L, -1))
|
||||
luaL_error(L, "format 'c0' needs a previous size");
|
||||
size = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
|
||||
lua_pop(L, 1); n--;
|
||||
luaL_argcheck(L, size <= ld && pos <= ld - size,
|
||||
2, "data string too short");
|
||||
}
|
||||
lua_pushlstring(L, data+pos, size);
|
||||
lua_pushlstring(L, data+pos, size); n++;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
@ -345,15 +349,15 @@ static int b_unpack (lua_State *L) {
|
||||
if (e == NULL)
|
||||
luaL_error(L, "unfinished string in data");
|
||||
size = (e - (data+pos)) + 1;
|
||||
lua_pushlstring(L, data+pos, size - 1);
|
||||
lua_pushlstring(L, data+pos, size - 1); n++;
|
||||
break;
|
||||
}
|
||||
default: controloptions(L, opt, &fmt, &h);
|
||||
}
|
||||
pos += size;
|
||||
}
|
||||
lua_pushinteger(L, pos + 1);
|
||||
return lua_gettop(L) - 2;
|
||||
lua_pushinteger(L, pos + 1); /* next position */
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
|
||||
@ -399,7 +403,7 @@ LUALIB_API int luaopen_struct (lua_State *L) {
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (C) 2010-2012 Lua.org, PUC-Rio. All rights reserved.
|
||||
* Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
17
redis.conf
17
redis.conf
@ -1106,6 +1106,17 @@ 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. 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-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
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
@ -1200,6 +1211,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
|
||||
|
@ -194,3 +194,31 @@ sentinel failover-timeout mymaster 180000
|
||||
#
|
||||
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
|
||||
|
||||
# SECURITY
|
||||
#
|
||||
# By default SENTINEL SET will not be able to change the notification-script
|
||||
# and client-reconfig-script at runtime. This avoids a trivial security issue
|
||||
# where clients can set the script to anything and trigger a failover in order
|
||||
# to get the program executed.
|
||||
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# REDIS COMMANDS RENAMING
|
||||
#
|
||||
# Sometimes the Redis server has certain commands, that are needed for Sentinel
|
||||
# to work correctly, renamed to unguessable strings. This is often the case
|
||||
# of CONFIG and SLAVEOF in the context of providers that provide Redis as
|
||||
# a service, and don't want the customers to reconfigure the instances outside
|
||||
# of the administration console.
|
||||
#
|
||||
# In such case it is possible to tell Sentinel to use different command names
|
||||
# instead of the normal ones. For example if the master "mymaster", and the
|
||||
# associated slaves, have "CONFIG" all renamed to "GUESSME", I could use:
|
||||
#
|
||||
# sentinel rename-command mymaster CONFIG GUESSME
|
||||
#
|
||||
# After such configuration is set, every time Sentinel would use CONFIG it will
|
||||
# use GUESSME instead. Note that there is no actual need to respect the command
|
||||
# case, so writing "config guessme" is the same in the example above.
|
||||
#
|
||||
# SENTINEL SET can also be used in order to perform this configuration at runtime.
|
||||
|
14
src/aof.c
14
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;
|
||||
@ -1221,7 +1221,6 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
size_t processed = 0;
|
||||
long long now = mstime();
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
@ -1247,9 +1246,6 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime != -1 && expiretime < now) continue;
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
/* Emit a SET command */
|
||||
@ -1322,7 +1318,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;
|
||||
@ -1690,7 +1686,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 */
|
||||
|
@ -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.
|
||||
|
@ -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];
|
||||
@ -396,12 +398,6 @@ void handleClientsBlockedOnKeys(void) {
|
||||
1);
|
||||
}
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
@ -417,6 +413,12 @@ void handleClientsBlockedOnKeys(void) {
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, 0, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
@ -4178,27 +4178,27 @@ void clusterCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"addslots <slot> [slot ...] -- Assign slots to current node.",
|
||||
"bumpepoch -- Advance the cluster config epoch.",
|
||||
"count-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"countkeysinslot <slot> - Return the number of keys in <slot>.",
|
||||
"delslots <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"failover [force|takeover] -- Promote current slave node to being a master.",
|
||||
"forget <node-id> -- Remove a node from the cluster.",
|
||||
"getkeysinslot <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"flushslots -- Delete current node own slots information.",
|
||||
"info - Return onformation about the cluster.",
|
||||
"keyslot <key> -- Return the hash slot for <key>.",
|
||||
"meet <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"myid -- Return the node id.",
|
||||
"nodes -- Return cluster configuration seen by node. Output format:",
|
||||
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
|
||||
"BUMPEPOCH -- Advance the cluster config epoch.",
|
||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"FAILOVER [force|takeover] -- Promote current slave node to being a master.",
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return onformation about the cluster.",
|
||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"MYID -- Return the node id.",
|
||||
"NODES -- Return cluster configuration seen by node. Output format:",
|
||||
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
|
||||
"replicate <node-id> -- Configure current node as slave to <node-id>.",
|
||||
"reset [hard|soft] -- Reset current node (default: soft).",
|
||||
"set-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"setslot <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"slaves <node-id> -- Return <node-id> slaves.",
|
||||
"slots -- Return information about slots range mappings. Each range is made of:",
|
||||
"REPLICATE <node-id> -- Configure current node as slave to <node-id>.",
|
||||
"RESET [hard|soft] -- Reset current node (default: soft).",
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"SLAVES <node-id> -- Return <node-id> slaves.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
};
|
||||
@ -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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -4835,15 +4834,39 @@ void dumpCommand(client *c) {
|
||||
|
||||
/* RESTORE key ttl serialized-value [REPLACE] */
|
||||
void restoreCommand(client *c) {
|
||||
long long ttl;
|
||||
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
|
||||
rio payload;
|
||||
int j, type, replace = 0;
|
||||
int j, type, replace = 0, absttl = 0;
|
||||
robj *obj;
|
||||
|
||||
/* Parse additional options */
|
||||
for (j = 4; j < c->argc; j++) {
|
||||
int additional = c->argc-j-1;
|
||||
if (!strcasecmp(c->argv[j]->ptr,"replace")) {
|
||||
replace = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"absttl")) {
|
||||
absttl = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"idletime") && additional >= 1 &&
|
||||
lfu_freq == -1)
|
||||
{
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lru_idle,NULL)
|
||||
!= C_OK) return;
|
||||
if (lru_idle < 0) {
|
||||
addReplyError(c,"Invalid IDLETIME value, must be >= 0");
|
||||
return;
|
||||
}
|
||||
lru_clock = LRU_CLOCK();
|
||||
j++; /* Consume additional arg. */
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"freq") && additional >= 1 &&
|
||||
lru_idle == -1)
|
||||
{
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lfu_freq,NULL)
|
||||
!= C_OK) return;
|
||||
if (lfu_freq < 0 || lfu_freq > 255) {
|
||||
addReplyError(c,"Invalid FREQ value, must be >= 0 and <= 255");
|
||||
return;
|
||||
}
|
||||
j++; /* Consume additional arg. */
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
@ -4884,7 +4907,11 @@ void restoreCommand(client *c) {
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
if (ttl) setExpire(c,c->db,c->argv[1],mstime()+ttl);
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
@ -5589,7 +5616,11 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
* longer handles, the client is sent a redirection error, and the function
|
||||
* returns 1. Otherwise 0 is returned and no operation is performed. */
|
||||
int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
if (c->flags & CLIENT_BLOCKED && c->btype == BLOCKED_LIST) {
|
||||
if (c->flags & CLIENT_BLOCKED &&
|
||||
(c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM))
|
||||
{
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
|
||||
|
110
src/config.c
110
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;
|
||||
@ -431,6 +431,11 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
if (server.active_defrag_enabled) {
|
||||
#ifndef HAVE_DEFRAG
|
||||
err = "active defrag can't be enabled without proper jemalloc support"; goto loaderr;
|
||||
#endif
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
|
||||
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@ -483,6 +488,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;
|
||||
@ -496,7 +508,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]);
|
||||
@ -509,14 +521,16 @@ void loadServerConfigFromString(char *config) {
|
||||
server.rdb_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-threshold-lower") && argc == 2) {
|
||||
server.active_defrag_threshold_lower = atoi(argv[1]);
|
||||
if (server.active_defrag_threshold_lower < 0) {
|
||||
err = "active-defrag-threshold-lower must be 0 or greater";
|
||||
if (server.active_defrag_threshold_lower < 0 ||
|
||||
server.active_defrag_threshold_lower > 1000) {
|
||||
err = "active-defrag-threshold-lower must be between 0 and 1000";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-threshold-upper") && argc == 2) {
|
||||
server.active_defrag_threshold_upper = atoi(argv[1]);
|
||||
if (server.active_defrag_threshold_upper < 0) {
|
||||
err = "active-defrag-threshold-upper must be 0 or greater";
|
||||
if (server.active_defrag_threshold_upper < 0 ||
|
||||
server.active_defrag_threshold_upper > 1000) {
|
||||
err = "active-defrag-threshold-upper must be between 0 and 1000";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-ignore-bytes") && argc == 2) {
|
||||
@ -547,6 +561,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) {
|
||||
@ -1015,6 +1033,8 @@ void configSetCommand(client *c) {
|
||||
"cluster-slave-no-failover",server.cluster_slave_no_failover) {
|
||||
} 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(
|
||||
@ -1056,15 +1076,15 @@ void configSetCommand(client *c) {
|
||||
/* Numerical fields.
|
||||
* config_set_numerical_field(name,var,min,max) */
|
||||
} config_set_numerical_field(
|
||||
"tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
|
||||
"tcp-keepalive",server.tcpkeepalive,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
|
||||
"maxmemory-samples",server.maxmemory_samples,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lfu-log-factor",server.lfu_log_factor,0,LLONG_MAX) {
|
||||
"lfu-log-factor",server.lfu_log_factor,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lfu-decay-time",server.lfu_decay_time,0,LLONG_MAX) {
|
||||
"lfu-decay-time",server.lfu_decay_time,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"timeout",server.maxidletime,0,LONG_MAX) {
|
||||
"timeout",server.maxidletime,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-threshold-lower",server.active_defrag_threshold_lower,0,1000) {
|
||||
} config_set_numerical_field(
|
||||
@ -1076,52 +1096,56 @@ void configSetCommand(client *c) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LLONG_MAX) {
|
||||
"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,INT_MAX){
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
|
||||
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
|
||||
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"stream-node-max-bytes",server.stream_node_max_bytes,0,LONG_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(
|
||||
"list-compress-depth",server.list_compress_depth,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
|
||||
"set-max-intset-entries",server.set_max_intset_entries,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
|
||||
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
|
||||
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
|
||||
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
|
||||
"lua-time-limit",server.lua_time_limit,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-max-len",ll,0,LLONG_MAX) {
|
||||
"slowlog-max-len",ll,0,LONG_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(
|
||||
"repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
|
||||
"repl-ping-slave-period",server.repl_ping_slave_period,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-timeout",server.repl_timeout,1,LLONG_MAX) {
|
||||
"repl-timeout",server.repl_timeout,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
|
||||
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-priority",server.slave_priority,0,LLONG_MAX) {
|
||||
"slave-priority",server.slave_priority,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-announce-port",server.slave_announce_port,0,65535) {
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
|
||||
"min-slaves-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
|
||||
@ -1130,17 +1154,17 @@ void configSetCommand(client *c) {
|
||||
} config_set_numerical_field(
|
||||
"cluster-announce-bus-port",server.cluster_announce_bus_port,0,65535) {
|
||||
} config_set_numerical_field(
|
||||
"cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
|
||||
"cluster-migration-barrier",server.cluster_migration_barrier,0,INT_MAX){
|
||||
} config_set_numerical_field(
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hz",server.hz,0,LLONG_MAX) {
|
||||
"hz",server.hz,0,INT_MAX) {
|
||||
/* Hz is more an hint from the user, so we accept values out of range
|
||||
* but cap them to reasonable values. */
|
||||
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
|
||||
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
|
||||
} config_set_numerical_field(
|
||||
"watchdog-period",ll,0,LLONG_MAX) {
|
||||
"watchdog-period",ll,0,INT_MAX) {
|
||||
if (ll)
|
||||
enableWatchdog(ll);
|
||||
else
|
||||
@ -1267,6 +1291,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",
|
||||
@ -1333,6 +1361,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",
|
||||
@ -2056,6 +2086,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);
|
||||
@ -2068,6 +2100,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);
|
||||
@ -2107,10 +2140,10 @@ void configCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"get <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
|
||||
"set <parameter> <value> -- Set parameter to value.",
|
||||
"resetstat -- Reset statistics reported by INFO.",
|
||||
"rewrite -- Rewrite the configuration file.",
|
||||
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
|
||||
"SET <parameter> <value> -- Set parameter to value.",
|
||||
"RESETSTAT -- Reset statistics reported by INFO.",
|
||||
"REWRITE -- Rewrite the configuration file.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -2135,8 +2168,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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
62
src/db.c
62
src/db.c
@ -90,7 +90,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* LOOKUP_NONE (or zero): no special flags are passed.
|
||||
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
|
||||
*
|
||||
* Note: this function also returns NULL is the key is logically expired
|
||||
* Note: this function also returns NULL if the key is logically expired
|
||||
* but still existing, in case this is a slave, since this API is called only
|
||||
* for read operations. Even if the key expiry is master-driven, we can
|
||||
* correctly report a key is expired on slaves even if the master is lagging
|
||||
@ -223,6 +223,8 @@ int dbExists(redisDb *db, robj *key) {
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
dictEntry *de;
|
||||
int maxtries = 100;
|
||||
int allvolatile = dictSize(db->dict) == dictSize(db->expires);
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
@ -234,6 +236,17 @@ robj *dbRandomKey(redisDb *db) {
|
||||
key = dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (dictFind(db->expires,key)) {
|
||||
if (allvolatile && server.masterhost && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the slave, so the function cannot stop because
|
||||
* expireIfNeeded() is false, nor it can stop because
|
||||
* dictGetRandomKey() returns NULL (there are keys to return).
|
||||
* To prevent the infinite loop we do some tries, but if there
|
||||
* are the conditions for an infinite loop, eventually we
|
||||
* return a key name that may be already expired. */
|
||||
return keyobj;
|
||||
}
|
||||
if (expireIfNeeded(db,keyobj)) {
|
||||
decrRefCount(keyobj);
|
||||
continue; /* search for another key. This expired. */
|
||||
@ -467,8 +480,7 @@ void existsCommand(client *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbExists(c->db,c->argv[j])) count++;
|
||||
if (lookupKeyRead(c->db,c->argv[j])) count++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
@ -942,16 +954,18 @@ void moveCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Helper function for dbSwapDatabases(): scans the list of keys that have
|
||||
* one or more blocked clients for B[LR]POP or other list blocking commands
|
||||
* and signal the keys are ready if they are lists. See the comment where
|
||||
* the function is used for more info. */
|
||||
* one or more blocked clients for B[LR]POP or other blocking commands
|
||||
* and signal the keys as ready if they are of the right type. See the comment
|
||||
* where the function is used for more info. */
|
||||
void scanDatabaseForReadyLists(redisDb *db) {
|
||||
dictEntry *de;
|
||||
dictIterator *di = dictGetSafeIterator(db->blocking_keys);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
robj *value = lookupKey(db,key,LOOKUP_NOTOUCH);
|
||||
if (value && (value->type == OBJ_LIST || value->type == OBJ_STREAM))
|
||||
if (value && (value->type == OBJ_LIST ||
|
||||
value->type == OBJ_STREAM ||
|
||||
value->type == OBJ_ZSET))
|
||||
signalKeyAsReady(db, key);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
@ -1226,7 +1240,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;
|
||||
}
|
||||
@ -1255,7 +1269,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;
|
||||
}
|
||||
@ -1384,23 +1398,37 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
}
|
||||
|
||||
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
|
||||
* [RETRY <milliseconds> <ttl>] STREAMS key_1 key_2 ... key_N
|
||||
* ID_1 ID_2 ... ID_N */
|
||||
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
|
||||
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
int i, num = 0, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* We need to seek the last argument that contains "STREAMS", because other
|
||||
* arguments before may contain it (for example the group name). */
|
||||
/* We need to parse the options of the command in order to seek the first
|
||||
* "STREAMS" string which is actually the option. This is needed because
|
||||
* "STREAMS" could also be the name of the consumer group and even the
|
||||
* name of the stream key. */
|
||||
int streams_pos = -1;
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i]->ptr;
|
||||
if (!strcasecmp(arg, "streams")) streams_pos = i;
|
||||
if (!strcasecmp(arg, "block")) {
|
||||
i++; /* Skip option argument. */
|
||||
} else if (!strcasecmp(arg, "count")) {
|
||||
i++; /* Skip option argument. */
|
||||
} else if (!strcasecmp(arg, "group")) {
|
||||
i += 2; /* Skip option argument. */
|
||||
} else if (!strcasecmp(arg, "noack")) {
|
||||
/* Nothing to do. */
|
||||
} else if (!strcasecmp(arg, "streams")) {
|
||||
streams_pos = i;
|
||||
break;
|
||||
} else {
|
||||
break; /* Syntax error. */
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -1408,7 +1436,7 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
there are also the IDs, one per key. */
|
||||
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
for (i = streams_pos+1; i < argc; i++) keys[i-streams_pos-1] = i;
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
74
src/debug.c
74
src/debug.c
@ -285,25 +285,26 @@ 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-recovery <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"digest -- Outputs an hex signature representing the current DB content.",
|
||||
"htstats <dbid> -- 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 <key> -- Show low level info about key and associated value.",
|
||||
"panic -- Crash the server simulating a panic.",
|
||||
"populate <count> [prefix] [size] -- Create <count> string keys named key:<num>. 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 <key> -- 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 <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"structsize -- Return the size of different Redis core C structures.",
|
||||
"ziplist <key> -- Show low level info about the ziplist encoding.",
|
||||
"error <string> -- Return a Redis protocol error with <string> 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 <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
"HTSTATS-KEY <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 <key> -- Show low level info about key and associated value.",
|
||||
"PANIC -- Crash the server simulating a panic.",
|
||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. 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 <key> -- 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 <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -347,7 +348,7 @@ 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) flushAppendOnlyFile(1);
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
if (loadAppendOnlyFile(server.aof_filename) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
@ -547,14 +548,41 @@ 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();
|
||||
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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1048,7 +1076,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
infostring = genRedisInfoString("all");
|
||||
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ CLIENT LIST OUTPUT ------\n");
|
||||
clients = getAllClientsInfoString();
|
||||
clients = getAllClientsInfoString(-1);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, clients);
|
||||
sdsfree(infostring);
|
||||
sdsfree(clients);
|
||||
|
210
src/defrag.c
210
src/defrag.c
@ -592,6 +592,171 @@ long defragSet(redisDb *db, dictEntry *kde) {
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag callback for radix tree iterator, called for each node,
|
||||
* used in order to defrag the nodes allocations. */
|
||||
int defragRaxNode(raxNode **noderef) {
|
||||
raxNode *newnode = activeDefragAlloc(*noderef);
|
||||
if (newnode) {
|
||||
*noderef = newnode;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
|
||||
static unsigned char last[sizeof(streamID)];
|
||||
raxIterator ri;
|
||||
long iterations = 0;
|
||||
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream *s = ob->ptr;
|
||||
raxStart(&ri,s->rax);
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
defragRaxNode(&s->rax->head);
|
||||
/* assign the iterator node callback before the seek, so that the
|
||||
* initial nodes that are processed till the first item are covered */
|
||||
ri.node_cb = defragRaxNode;
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
} else {
|
||||
/* if cursor is non-zero, we seek to the static 'last' */
|
||||
if (!raxSeek(&ri,">", last, sizeof(last))) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
/* assign the iterator node callback after the seek, so that the
|
||||
* initial nodes that are processed till now aren't covered */
|
||||
ri.node_cb = defragRaxNode;
|
||||
}
|
||||
|
||||
(*cursor)++;
|
||||
while (raxNext(&ri)) {
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
if (++iterations > 16) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
raxStop(&ri);
|
||||
return 1;
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* optional callback used defrag each rax element (not including the element pointer itself) */
|
||||
typedef void *(raxDefragFunction)(raxIterator *ri, void *privdata, long *defragged);
|
||||
|
||||
/* defrag radix tree including:
|
||||
* 1) rax struct
|
||||
* 2) rax nodes
|
||||
* 3) rax entry data (only if defrag_data is specified)
|
||||
* 4) call a callback per element, and allow the callback to return a new pointer for the element */
|
||||
long defragRadixTree(rax **raxref, int defrag_data, raxDefragFunction *element_cb, void *element_cb_data) {
|
||||
long defragged = 0;
|
||||
raxIterator ri;
|
||||
rax* rax;
|
||||
if ((rax = activeDefragAlloc(*raxref)))
|
||||
defragged++, *raxref = rax;
|
||||
rax = *raxref;
|
||||
raxStart(&ri,rax);
|
||||
ri.node_cb = defragRaxNode;
|
||||
defragRaxNode(&rax->head);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while (raxNext(&ri)) {
|
||||
void *newdata = NULL;
|
||||
if (element_cb)
|
||||
newdata = element_cb(&ri, element_cb_data, &defragged);
|
||||
if (defrag_data && !newdata)
|
||||
newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), defragged++;
|
||||
}
|
||||
raxStop(&ri);
|
||||
return defragged;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
streamCG *cg;
|
||||
streamConsumer *c;
|
||||
} PendingEntryContext;
|
||||
|
||||
void* defragStreamConsumerPendingEntry(raxIterator *ri, void *privdata, long *defragged) {
|
||||
UNUSED(defragged);
|
||||
PendingEntryContext *ctx = privdata;
|
||||
streamNACK *nack = ri->data, *newnack;
|
||||
nack->consumer = ctx->c; /* update nack pointer to consumer */
|
||||
newnack = activeDefragAlloc(nack);
|
||||
if (newnack) {
|
||||
/* update consumer group pointer to the nack */
|
||||
void *prev;
|
||||
raxInsert(ctx->cg->pel, ri->key, ri->key_len, newnack, &prev);
|
||||
serverAssert(prev==nack);
|
||||
/* note: we don't increment 'defragged' that's done by the caller */
|
||||
}
|
||||
return newnack;
|
||||
}
|
||||
|
||||
void* defragStreamConsumer(raxIterator *ri, void *privdata, long *defragged) {
|
||||
streamConsumer *c = ri->data;
|
||||
streamCG *cg = privdata;
|
||||
void *newc = activeDefragAlloc(c);
|
||||
if (newc) {
|
||||
/* note: we don't increment 'defragged' that's done by the caller */
|
||||
c = newc;
|
||||
}
|
||||
sds newsds = activeDefragSds(c->name);
|
||||
if (newsds)
|
||||
(*defragged)++, c->name = newsds;
|
||||
if (c->pel) {
|
||||
PendingEntryContext pel_ctx = {cg, c};
|
||||
*defragged += defragRadixTree(&c->pel, 0, defragStreamConsumerPendingEntry, &pel_ctx);
|
||||
}
|
||||
return newc; /* returns NULL if c was not defragged */
|
||||
}
|
||||
|
||||
void* defragStreamConsumerGroup(raxIterator *ri, void *privdata, long *defragged) {
|
||||
streamCG *cg = ri->data;
|
||||
UNUSED(privdata);
|
||||
if (cg->consumers)
|
||||
*defragged += defragRadixTree(&cg->consumers, 0, defragStreamConsumer, cg);
|
||||
if (cg->pel)
|
||||
*defragged += defragRadixTree(&cg->pel, 0, NULL, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long defragStream(redisDb *db, dictEntry *kde) {
|
||||
long defragged = 0;
|
||||
robj *ob = dictGetVal(kde);
|
||||
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
|
||||
stream *s = ob->ptr, *news;
|
||||
|
||||
/* handle the main struct */
|
||||
if ((news = activeDefragAlloc(s)))
|
||||
defragged++, ob->ptr = s = news;
|
||||
|
||||
if (raxSize(s->rax) > server.active_defrag_max_scan_fields) {
|
||||
rax *newrax = activeDefragAlloc(s->rax);
|
||||
if (newrax)
|
||||
defragged++, s->rax = newrax;
|
||||
defragLater(db, kde);
|
||||
} else
|
||||
defragged += defragRadixTree(&s->rax, 1, NULL, NULL);
|
||||
|
||||
if (s->cgroups)
|
||||
defragged += defragRadixTree(&s->cgroups, 1, defragStreamConsumerGroup, NULL);
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* for each key we scan in the main dict, this function will attempt to defrag
|
||||
* all the various pointers it has. Returns a stat of how many pointers were
|
||||
* moved. */
|
||||
@ -660,6 +825,8 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
defragged += defragStream(db, de);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
/* Currently defragmenting modules private data types
|
||||
* is not supported. */
|
||||
@ -680,7 +847,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for for each hash table bicket,
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata); /* NOTE: this function is also used by both activeDefragCycle and scanLaterHash, etc. don't use privdata */
|
||||
@ -728,27 +895,29 @@ long defragOtherGlobals() {
|
||||
return defragged;
|
||||
}
|
||||
|
||||
unsigned long defragLaterItem(dictEntry *de, unsigned long cursor) {
|
||||
long defragged = 0;
|
||||
/* returns 0 more work may or may not be needed (see non-zero cursor),
|
||||
* and 1 if time is up and more work is needed. */
|
||||
int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
defragged += scanLaterList(ob);
|
||||
cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
defragged += scanLaterSet(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
defragged += scanLaterZset(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
defragged += scanLaterHash(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStraemListpacks(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
} else {
|
||||
cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
}
|
||||
} else {
|
||||
cursor = 0; /* object may have been deleted already */
|
||||
*cursor = 0; /* object may have been deleted already */
|
||||
}
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
return cursor;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
@ -788,17 +957,22 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
dictEntry *de = dictFind(db->dict, current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
do {
|
||||
cursor = defragLaterItem(de, cursor);
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &cursor, endtime))
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
|
||||
/* Don't start a new BIG key in this loop, this is because the
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!cursor)
|
||||
quit = 1;
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
||||
* (if we have a lot of pointers in one hash bucket, or rehashing),
|
||||
* check if we reached the time limit.
|
||||
* But regardless, don't start a new BIG key in this loop, this is because the
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!cursor || (++iterations > 16 ||
|
||||
* check if we reached the time limit. */
|
||||
if (quit || (++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
if (!cursor || ustime() > endtime) {
|
||||
if (quit || ustime() > endtime) {
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
|
31
src/dict.c
31
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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -46,9 +46,9 @@ uint64_t intrev64(uint64_t v);
|
||||
/* variants of the function doing the actual convertion only if the target
|
||||
* host is big endian */
|
||||
#if (BYTE_ORDER == LITTLE_ENDIAN)
|
||||
#define memrev16ifbe(p)
|
||||
#define memrev32ifbe(p)
|
||||
#define memrev64ifbe(p)
|
||||
#define memrev16ifbe(p) ((void)(0))
|
||||
#define memrev32ifbe(p) ((void)(0))
|
||||
#define memrev64ifbe(p) ((void)(0))
|
||||
#define intrev16ifbe(v) (v)
|
||||
#define intrev32ifbe(v) (v)
|
||||
#define intrev64ifbe(v) (v)
|
||||
|
@ -112,7 +112,7 @@ void activeExpireCycle(int type) {
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
/* Don't start a fast cycle if the previous cycle did not exit
|
||||
* for time limt. Also don't repeat a fast cycle for the same period
|
||||
* for time limit. Also don't repeat a fast cycle for the same period
|
||||
* as the fast cycle total duration itself. */
|
||||
if (!timelimit_exit) return;
|
||||
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
|
||||
|
@ -144,8 +144,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
|
||||
(longitude - long_range->min) / (long_range->max - long_range->min);
|
||||
|
||||
/* convert to fixed point based on the step size */
|
||||
lat_offset *= (1 << step);
|
||||
long_offset *= (1 << step);
|
||||
lat_offset *= (1ULL << step);
|
||||
long_offset *= (1ULL << step);
|
||||
hash->bits = interleave64(lat_offset, long_offset);
|
||||
return 1;
|
||||
}
|
||||
|
113
src/help.h
113
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 consumer 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",
|
||||
|
@ -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;
|
||||
|
23
src/lzf_d.c
23
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
|
||||
}
|
||||
|
18
src/module.c
18
src/module.c
@ -2239,6 +2239,9 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
* to avoid a useless copy. */
|
||||
if (flags & REDISMODULE_HASH_CFIELDS)
|
||||
low_flags |= HASH_SET_TAKE_FIELD;
|
||||
|
||||
robj *argv[2] = {field,value};
|
||||
hashTypeTryConversion(key->value,argv,0,1);
|
||||
updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags);
|
||||
|
||||
/* If CFIELDS is active, SDS string ownership is now of hashTypeSet(),
|
||||
@ -3396,7 +3399,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
*
|
||||
* If the specified log level is invalid, verbose is used by default.
|
||||
* There is a fixed limit to the length of the log line this function is able
|
||||
* to emit, this limti is not specified but is guaranteed to be more than
|
||||
* to emit, this limit is not specified but is guaranteed to be more than
|
||||
* a few lines of text.
|
||||
*/
|
||||
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
|
||||
@ -4499,7 +4502,15 @@ int moduleUnload(sds name) {
|
||||
* MODULE LOAD <path> [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 <path> [arg ...] -- Load a module library from <path>.",
|
||||
"UNLOAD <name> -- Unload a module.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else
|
||||
if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
|
||||
robj **argv = NULL;
|
||||
int argc = 0;
|
||||
@ -4548,7 +4559,8 @@ void moduleCommand(client *c) {
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
103
src/networking.c
103
src/networking.c
@ -75,6 +75,8 @@ void linkClient(client *c) {
|
||||
* this way removing the client in unlinkClient() will not require
|
||||
* a linear scan, but just a constant time operation. */
|
||||
c->client_list_node = listLast(server.clients);
|
||||
uint64_t id = htonu64(c->id);
|
||||
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
|
||||
}
|
||||
|
||||
client *createClient(int fd) {
|
||||
@ -247,7 +249,7 @@ void _addReplyStringToList(client *c, const char *s, size_t len) {
|
||||
|
||||
/* Append to this object when possible. If tail == NULL it was
|
||||
* set via addDeferredMultiBulkLength(). */
|
||||
if (tail && sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES) {
|
||||
if (tail && (sdsavail(tail) >= len || sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES)) {
|
||||
tail = sdscatlen(tail,s,len);
|
||||
listNodeValue(ln) = tail;
|
||||
c->reply_bytes += len;
|
||||
@ -560,6 +562,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 addReplySubcommandSyntaxError(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. */
|
||||
@ -720,6 +734,8 @@ void unlinkClient(client *c) {
|
||||
if (c->fd != -1) {
|
||||
/* Remove from the list of active clients. */
|
||||
if (c->client_list_node) {
|
||||
uint64_t id = htonu64(c->id);
|
||||
raxRemove(server.clients_index,(unsigned char*)&id,sizeof(id),NULL);
|
||||
listDelNode(server.clients,c->client_list_node);
|
||||
c->client_list_node = NULL;
|
||||
}
|
||||
@ -864,6 +880,15 @@ void freeClientsInAsyncFreeQueue(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return a client by ID, or NULL if the client ID is not in the set
|
||||
* of registered clients. Note that "fake clients", created with -1 as FD,
|
||||
* are not registered clients. */
|
||||
client *lookupClientByID(uint64_t id) {
|
||||
id = htonu64(id);
|
||||
client *c = raxFind(server.clients_index,(unsigned char*)&id,sizeof(id));
|
||||
return (c == raxNotFound) ? NULL : c;
|
||||
}
|
||||
|
||||
/* Write data in output buffers to client. Return C_OK if the client
|
||||
* is still valid after the call, C_ERR if it was freed. */
|
||||
int writeToClient(int fd, client *c, int handler_installed) {
|
||||
@ -1493,6 +1518,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
*p++ = 'S';
|
||||
}
|
||||
if (client->flags & CLIENT_MASTER) *p++ = 'M';
|
||||
if (client->flags & CLIENT_PUBSUB) *p++ = 'P';
|
||||
if (client->flags & CLIENT_MULTI) *p++ = 'x';
|
||||
if (client->flags & CLIENT_BLOCKED) *p++ = 'b';
|
||||
if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd';
|
||||
@ -1531,7 +1557,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
client->lastcmd ? client->lastcmd->name : "NULL");
|
||||
}
|
||||
|
||||
sds getAllClientsInfoString(void) {
|
||||
sds getAllClientsInfoString(int type) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
client *client;
|
||||
@ -1540,6 +1566,7 @@ sds getAllClientsInfoString(void) {
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client = listNodeValue(ln);
|
||||
if (type != -1 && getClientType(client) != type) continue;
|
||||
o = catClientInfoString(o,client);
|
||||
o = sdscatlen(o,"\n",1);
|
||||
}
|
||||
@ -1553,22 +1580,40 @@ void clientCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"getname -- Return the name of the current connection.",
|
||||
"kill <ip:port> -- Kill connection made from <ip:port>.",
|
||||
"id -- Return the ID of the current connection.",
|
||||
"getname -- Return the name of the current connection.",
|
||||
"kill <ip:port> -- Kill connection made from <ip:port>.",
|
||||
"kill <option> <value> [option value ...] -- Kill connections. Options are:",
|
||||
" addr <ip:port> -- Kill connection made from <ip:port>.",
|
||||
" type (normal|master|slave|pubsub) -- Kill connections by type.",
|
||||
" skipme (yes|no) -- Skip killing current connection (default: yes).",
|
||||
"list -- Return information about client connections.",
|
||||
"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
|
||||
"reply (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"setname <name> -- Assign the name <name> to the current connection.",
|
||||
" addr <ip:port> -- Kill connection made from <ip:port>",
|
||||
" type (normal|master|slave|pubsub) -- Kill connections by type.",
|
||||
" skipme (yes|no) -- Skip killing current connection (default: yes).",
|
||||
"list [options ...] -- Return information about client connections. Options:",
|
||||
" type (normal|master|slave|pubsub) -- Return clients of specified type.",
|
||||
"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
|
||||
"reply (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"setname <name> -- Assign the name <name> to the current connection.",
|
||||
"unblock -- Unblock the specified blocked client.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"id") && c->argc == 2) {
|
||||
/* CLIENT ID */
|
||||
addReplyLongLong(c,c->id);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"list")) {
|
||||
/* CLIENT LIST */
|
||||
sds o = getAllClientsInfoString();
|
||||
int type = -1;
|
||||
if (c->argc == 4 && !strcasecmp(c->argv[2]->ptr,"type")) {
|
||||
type = getClientTypeByName(c->argv[3]->ptr);
|
||||
if (type == -1) {
|
||||
addReplyErrorFormat(c,"Unknown client type '%s'",
|
||||
(char*) c->argv[3]->ptr);
|
||||
return;
|
||||
}
|
||||
} else if (c->argc != 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
sds o = getAllClientsInfoString(type);
|
||||
addReplyBulkCBuffer(c,o,sdslen(o));
|
||||
sdsfree(o);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
||||
@ -1671,6 +1716,38 @@ NULL
|
||||
/* If this client has to be closed, flag it as CLOSE_AFTER_REPLY
|
||||
* only after we queued the reply to its output buffers. */
|
||||
if (close_this_client) c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"unblock") && (c->argc == 3 ||
|
||||
c->argc == 4))
|
||||
{
|
||||
/* CLIENT UNBLOCK <id> [timeout|error] */
|
||||
long long id;
|
||||
int unblock_error = 0;
|
||||
|
||||
if (c->argc == 4) {
|
||||
if (!strcasecmp(c->argv[3]->ptr,"timeout")) {
|
||||
unblock_error = 0;
|
||||
} else if (!strcasecmp(c->argv[3]->ptr,"error")) {
|
||||
unblock_error = 1;
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"CLIENT UNBLOCK reason should be TIMEOUT or ERROR");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
|
||||
!= C_OK) return;
|
||||
struct client *target = lookupClientByID(id);
|
||||
if (target && target->flags & CLIENT_BLOCKED) {
|
||||
if (unblock_error)
|
||||
addReplyError(target,
|
||||
"-UNBLOCKED client unblocked via CLIENT UNBLOCK");
|
||||
else
|
||||
replyToBlockedClientTimedOut(target);
|
||||
unblockClient(target);
|
||||
addReply(c,shared.cone);
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
|
||||
int j, len = sdslen(c->argv[2]->ptr);
|
||||
char *p = c->argv[2]->ptr;
|
||||
|
75
src/object.c
75
src/object.c
@ -123,9 +123,25 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
return createRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
/* Create a string object from a long long value. When possible returns a
|
||||
* shared integer object, or at least an integer encoded one.
|
||||
*
|
||||
* If valueobj is non zero, the function avoids returning a a shared
|
||||
* integer, because the object is going to be used as value in the Redis key
|
||||
* space (for instance when the INCR command is used), so we want LFU/LRU
|
||||
* values specific for each key. */
|
||||
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
|
||||
robj *o;
|
||||
if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
|
||||
|
||||
if (server.maxmemory == 0 ||
|
||||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
|
||||
{
|
||||
/* If the maxmemory policy permits, we can still return shared integers
|
||||
* even if valueobj is true. */
|
||||
valueobj = 0;
|
||||
}
|
||||
|
||||
if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
} else {
|
||||
@ -140,6 +156,20 @@ robj *createStringObjectFromLongLong(long long value) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Wrapper for createStringObjectFromLongLongWithOptions() always demanding
|
||||
* to create a shared object if possible. */
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
return createStringObjectFromLongLongWithOptions(value,0);
|
||||
}
|
||||
|
||||
/* Wrapper for createStringObjectFromLongLongWithOptions() avoiding a shared
|
||||
* object when LFU/LRU info are needed, that is, when the object is used
|
||||
* as a value in the key space, and Redis is configured to evict based on
|
||||
* LFU/LRU. */
|
||||
robj *createStringObjectFromLongLongForValue(long long value) {
|
||||
return createStringObjectFromLongLongWithOptions(value,1);
|
||||
}
|
||||
|
||||
/* Create a string object from a long double. If humanfriendly is non-zero
|
||||
* it does not use exponential format and trims trailing zeroes at the end,
|
||||
* however this results in loss of precision. Otherwise exp format is used
|
||||
@ -715,7 +745,7 @@ char *strEncoding(int encoding) {
|
||||
* size of a radix tree that is used to store Stream IDs.
|
||||
*
|
||||
* Note: to guess the size of the radix tree is not trivial, so we
|
||||
* approximate it considering 128 bytes of data overhead for each
|
||||
* approximate it considering 16 bytes of data overhead for each
|
||||
* key (the ID), and then adding the number of bare nodes, plus some
|
||||
* overhead due by the data and child pointers. This secret recipe
|
||||
* was obtained by checking the average radix tree created by real
|
||||
@ -874,6 +904,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
* structures and the PEL memory usage. */
|
||||
raxIterator cri;
|
||||
raxStart(&cri,cg->consumers);
|
||||
raxSeek(&cri,"^",NULL,0);
|
||||
while(raxNext(&cri)) {
|
||||
streamConsumer *consumer = cri.data;
|
||||
asize += sizeof(*consumer);
|
||||
@ -968,7 +999,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);
|
||||
@ -1136,6 +1167,32 @@ sds getMemoryDoctorReport(void) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock) {
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (lfu_freq >= 0) {
|
||||
serverAssert(lfu_freq <= 255);
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Serialized LRU idle time is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maximum
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= The OBJECT and MEMORY commands =================== */
|
||||
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
@ -1161,10 +1218,10 @@ void objectCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"encoding <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
|
||||
"freq <key> -- 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 <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
|
||||
"refcount <key> -- Return the number of references of the value associated with the specified key.",
|
||||
"ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
|
||||
"FREQ <key> -- 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 <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
|
||||
"REFCOUNT <key> -- Return the number of references of the value associated with the specified key.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -1197,7 +1254,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);
|
||||
addReplySubcommandSyntaxError(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 [<pattern>] -- 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 [<pattern>] -- 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);
|
||||
@ -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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
78
src/rax.c
78
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. */
|
||||
@ -1135,6 +1167,7 @@ void raxStart(raxIterator *it, rax *rt) {
|
||||
it->key = it->key_static_string;
|
||||
it->key_max = RAX_ITER_STATIC_LEN;
|
||||
it->data = NULL;
|
||||
it->node_cb = NULL;
|
||||
raxStackInit(&it->stack);
|
||||
}
|
||||
|
||||
@ -1208,6 +1241,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
if (!raxIteratorAddChars(it,it->node->data,
|
||||
it->node->iscompr ? it->node->size : 1)) return 0;
|
||||
memcpy(&it->node,cp,sizeof(it->node));
|
||||
/* Call the node callback if any, and replace the node pointer
|
||||
* if the callback returns true. */
|
||||
if (it->node_cb && it->node_cb(&it->node))
|
||||
memcpy(cp,&it->node,sizeof(it->node));
|
||||
/* For "next" step, stop every time we find a key along the
|
||||
* way, since the key is lexicograhically smaller compared to
|
||||
* what follows in the sub-children. */
|
||||
@ -1260,6 +1297,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
raxIteratorAddChars(it,it->node->data+i,1);
|
||||
if (!raxStackPush(&it->stack,it->node)) return 0;
|
||||
memcpy(&it->node,cp,sizeof(it->node));
|
||||
/* Call the node callback if any, and replace the node
|
||||
* pointer if the callback returns true. */
|
||||
if (it->node_cb && it->node_cb(&it->node))
|
||||
memcpy(cp,&it->node,sizeof(it->node));
|
||||
if (it->node->iskey) {
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
@ -1293,7 +1334,7 @@ int raxSeekGreatest(raxIterator *it) {
|
||||
|
||||
/* Like raxIteratorNextStep() but implements an iteration step moving
|
||||
* to the lexicographically previous element. The 'noup' option has a similar
|
||||
* effect to the one of raxIteratorPrevSte(). */
|
||||
* effect to the one of raxIteratorNextStep(). */
|
||||
int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 1;
|
||||
@ -1523,11 +1564,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 {
|
||||
|
21
src/rax.h
21
src/rax.h
@ -119,6 +119,21 @@ typedef struct raxStack {
|
||||
int oom; /* True if pushing into this stack failed for OOM at some point. */
|
||||
} raxStack;
|
||||
|
||||
/* Optional callback used for iterators and be notified on each rax node,
|
||||
* including nodes not representing keys. If the callback returns true
|
||||
* the callback changed the node pointer in the iterator structure, and the
|
||||
* iterator implementation will have to replace the pointer in the radix tree
|
||||
* internals. This allows the callback to reallocate the node to perform
|
||||
* very special operations, normally not needed by normal applications.
|
||||
*
|
||||
* This callback is used to perform very low level analysis of the radix tree
|
||||
* structure, scanning each possible node (but the root node), or in order to
|
||||
* reallocate the nodes to reduce the allocation fragmentation (this is the
|
||||
* Redis application for this callback).
|
||||
*
|
||||
* This is currently only supported in forward iterations (raxNext) */
|
||||
typedef int (*raxNodeCallback)(raxNode **noderef);
|
||||
|
||||
/* Radix tree iterator state is encapsulated into this data structure. */
|
||||
#define RAX_ITER_STATIC_LEN 128
|
||||
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
|
||||
@ -137,6 +152,7 @@ typedef struct raxIterator {
|
||||
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
|
||||
raxNode *node; /* Current node. Only for unsafe iteration. */
|
||||
raxStack stack; /* Stack used for unsafe iteration. */
|
||||
raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
|
||||
} raxIterator;
|
||||
|
||||
/* A special pointer returned for not found items. */
|
||||
@ -145,6 +161,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);
|
||||
@ -160,4 +177,8 @@ int raxEOF(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
uint64_t raxSize(rax *rax);
|
||||
|
||||
/* Internal API. May be used by the node callback in order to access rax nodes
|
||||
* in a low level way, so this function is exported as well. */
|
||||
void raxSetData(raxNode *n, void *data);
|
||||
|
||||
#endif
|
||||
|
79
src/rdb.c
79
src/rdb.c
@ -100,6 +100,9 @@ int rdbLoadType(rio *rdb) {
|
||||
return type;
|
||||
}
|
||||
|
||||
/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME
|
||||
* opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS
|
||||
* opcode. */
|
||||
time_t rdbLoadTime(rio *rdb) {
|
||||
int32_t t32;
|
||||
rdbLoadRaw(rdb,&t32,4);
|
||||
@ -108,12 +111,26 @@ time_t rdbLoadTime(rio *rdb) {
|
||||
|
||||
int rdbSaveMillisecondTime(rio *rdb, long long t) {
|
||||
int64_t t64 = (int64_t) t;
|
||||
memrev64ifbe(&t64); /* Store in little endian. */
|
||||
return rdbWriteRaw(rdb,&t64,8);
|
||||
}
|
||||
|
||||
long long rdbLoadMillisecondTime(rio *rdb) {
|
||||
/* This function loads a time from the RDB file. It gets the version of the
|
||||
* RDB because, unfortunately, before Redis 5 (RDB version 9), the function
|
||||
* failed to convert data to/from little endian, so RDB files with keys having
|
||||
* expires could not be shared between big endian and little endian systems
|
||||
* (because the expire time will be totally wrong). The fix for this is just
|
||||
* to call memrev64ifbe(), however if we fix this for all the RDB versions,
|
||||
* this call will introduce an incompatibility for big endian systems:
|
||||
* after upgrading to Redis version 5 they will no longer be able to load their
|
||||
* own old RDB files. Because of that, we instead fix the function only for new
|
||||
* RDB versions, and load older RDB versions as we used to do in the past,
|
||||
* allowing big endian systems to load their own old RDB files. */
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
|
||||
int64_t t64;
|
||||
rdbLoadRaw(rdb,&t64,8);
|
||||
if (rdbver >= 9) /* Check the top comment of this function. */
|
||||
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
|
||||
return (long long)t64;
|
||||
}
|
||||
|
||||
@ -271,7 +288,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
memcpy(p,buf,len);
|
||||
return p;
|
||||
} else if (encode) {
|
||||
return createStringObjectFromLongLong(val);
|
||||
return createStringObjectFromLongLongForValue(val);
|
||||
} else {
|
||||
return createObject(OBJ_STRING,sdsfromlonglong(val));
|
||||
}
|
||||
@ -988,8 +1005,7 @@ size_t rdbSavedObjectLen(robj *o) {
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actually saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime)
|
||||
{
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;
|
||||
int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
|
||||
|
||||
@ -1001,7 +1017,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime)
|
||||
|
||||
/* Save the LRU info. */
|
||||
if (savelru) {
|
||||
int idletime = estimateObjectIdleTime(val);
|
||||
uint64_t idletime = estimateObjectIdleTime(val);
|
||||
idletime /= 1000; /* Using seconds is enough and requires less space.*/
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return -1;
|
||||
if (rdbSaveLen(rdb,idletime) == -1) return -1;
|
||||
@ -1111,13 +1127,9 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
* is currently the largest type we are able to represent in RDB sizes.
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
uint32_t db_size, expires_size;
|
||||
db_size = (dictSize(db->dict) <= UINT32_MAX) ?
|
||||
dictSize(db->dict) :
|
||||
UINT32_MAX;
|
||||
expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
|
||||
dictSize(db->expires) :
|
||||
UINT32_MAX;
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = dictSize(db->dict);
|
||||
expires_size = dictSize(db->expires);
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,db_size) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
|
||||
@ -1225,6 +1237,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;
|
||||
@ -1441,6 +1457,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;
|
||||
@ -1509,6 +1528,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--;
|
||||
@ -1683,7 +1705,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
unsigned char rawid[sizeof(streamID)];
|
||||
rdbLoadRaw(rdb,rawid,sizeof(rawid));
|
||||
streamNACK *nack = streamCreateNACK(NULL);
|
||||
nack->delivery_time = rdbLoadMillisecondTime(rdb);
|
||||
nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
nack->delivery_count = rdbLoadLen(rdb,NULL);
|
||||
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL))
|
||||
rdbExitReportCorruptRDB("Duplicated gobal PEL entry "
|
||||
@ -1702,7 +1724,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
|
||||
1);
|
||||
sdsfree(cname);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
|
||||
/* Load the PEL about entries owned by this specific
|
||||
* consumer. */
|
||||
@ -1845,11 +1867,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
}
|
||||
|
||||
/* Key-specific attributes, set by opcodes before the key type. */
|
||||
long long expiretime = -1, now = mstime();
|
||||
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
uint64_t lru_idle = -1;
|
||||
int lfu_freq = -1;
|
||||
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
|
||||
@ -1867,7 +1887,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
|
||||
/* EXPIRETIME_MS: milliseconds precision expire times introduced
|
||||
* with RDB v3. Like EXPIRETIME but no with more precision. */
|
||||
expiretime = rdbLoadMillisecondTime(rdb);
|
||||
expiretime = rdbLoadMillisecondTime(rdb,rdbver);
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_FREQ) {
|
||||
/* FREQ: LFU frequency. */
|
||||
@ -1877,7 +1897,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_IDLE) {
|
||||
/* IDLE: LRU idle time. */
|
||||
if ((lru_idle = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
uint64_t qword;
|
||||
if ((qword = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
lru_idle = qword;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_EOF) {
|
||||
/* EOF: End of file, exit the main loop. */
|
||||
@ -1996,20 +2018,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
if (lfu_freq != -1) {
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
} else {
|
||||
/* LRU idle time loaded from RDB is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normaly 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maxium
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
}
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
|
||||
|
||||
/* Decrement the key refcount since dbAdd() will take its
|
||||
* own reference. */
|
||||
|
@ -129,6 +129,8 @@ int rdbLoadType(rio *rdb);
|
||||
int rdbSaveTime(rio *rdb, time_t t);
|
||||
time_t rdbLoadTime(rio *rdb);
|
||||
int rdbSaveLen(rio *rdb, uint64_t len);
|
||||
int rdbSaveMillisecondTime(rio *rdb, long long t);
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
|
||||
uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||
|
@ -34,7 +34,6 @@
|
||||
|
||||
void createSharedObjects(void);
|
||||
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
|
||||
long long rdbLoadMillisecondTime(rio *rdb);
|
||||
int rdbCheckMode = 0;
|
||||
|
||||
struct {
|
||||
@ -224,7 +223,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
/* EXPIRETIME_MS: milliseconds precision expire times introduced
|
||||
* with RDB v3. Like EXPIRETIME but no with more precision. */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
|
||||
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
|
||||
if ((expiretime = rdbLoadMillisecondTime(&rdb, rdbver)) == -1) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_FREQ) {
|
||||
/* FREQ: LFU frequency. */
|
||||
|
@ -220,6 +220,7 @@ static struct config {
|
||||
int last_cmd_type;
|
||||
int verbose;
|
||||
clusterManagerCommand cluster_manager_command;
|
||||
int no_auth_warning;
|
||||
} config;
|
||||
|
||||
/* User preferences. */
|
||||
@ -258,20 +259,25 @@ static long long mstime(void) {
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
int len;
|
||||
|
||||
if (config.eval_ldb) return;
|
||||
if (config.hostsocket != NULL)
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = anetFormatAddr(config.prompt, sizeof(config.prompt),
|
||||
config.hostip, config.hostport);
|
||||
|
||||
sds prompt = sdsempty();
|
||||
if (config.hostsocket != NULL) {
|
||||
prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
|
||||
} else {
|
||||
char addr[256];
|
||||
anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
|
||||
prompt = sdscatlen(prompt,addr,strlen(addr));
|
||||
}
|
||||
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
|
||||
config.dbnum);
|
||||
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
|
||||
prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
|
||||
|
||||
/* Copy the prompt in the static buffer. */
|
||||
prompt = sdscatlen(prompt,"> ",2);
|
||||
snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
|
||||
sdsfree(prompt);
|
||||
}
|
||||
|
||||
/* Return the name of the dotfile for the specified 'dotfilename'.
|
||||
@ -1075,13 +1081,15 @@ 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"))) ||
|
||||
(argc == 2 && !strcasecmp(command,"cluster") &&
|
||||
(!strcasecmp(argv[1],"nodes") ||
|
||||
!strcasecmp(argv[1],"info"))) ||
|
||||
(argc == 2 && !strcasecmp(command,"client") &&
|
||||
(argc >= 2 && !strcasecmp(command,"client") &&
|
||||
!strcasecmp(argv[1],"list")) ||
|
||||
(argc == 3 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"graph")) ||
|
||||
@ -1228,8 +1236,9 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.interval = seconds*1000000;
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
config.no_auth_warning = 1;
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
fputs("Warning: Using a password with '-a' option on the command line interface may not be safe.\n", stderr);
|
||||
config.auth = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i]);
|
||||
@ -1380,6 +1389,12 @@ static int parseOptions(int argc, char **argv) {
|
||||
fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!config.no_auth_warning && config.auth != NULL) {
|
||||
fputs("Warning: Using a password with '-a' or '-u' option on the command"
|
||||
" line interface may not be safe.\n", stderr);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -1456,9 +1471,14 @@ static void usage(void) {
|
||||
" --cluster <command> [args...] [opts...]\n"
|
||||
" Cluster Manager command and arguments (see below).\n"
|
||||
" --verbose Verbose mode.\n"
|
||||
" --no-auth-warning Don't show warning message when using password on command\n"
|
||||
" line interface.\n"
|
||||
" --help Output this help and exit.\n"
|
||||
" --version Output version and exit.\n"
|
||||
"\n"
|
||||
"\n",
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
|
||||
fprintf(stderr,
|
||||
"Cluster Manager Commands:\n"
|
||||
" Use --cluster help to list all available cluster manager commands.\n"
|
||||
"\n"
|
||||
@ -1475,8 +1495,7 @@ static void usage(void) {
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands\n"
|
||||
"and settings.\n"
|
||||
"\n",
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
"\n");
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@ -3088,7 +3107,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);
|
||||
}
|
||||
@ -4848,7 +4867,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) {
|
||||
}
|
||||
/* Calculate the slots balance for each node. It's the number of
|
||||
* slots the node should lose (if positive) or gain (if negative)
|
||||
* in order to be balanced. */
|
||||
* in order to be balanced. */
|
||||
int threshold_reached = 0, total_balance = 0;
|
||||
float threshold = config.cluster_manager_command.threshold;
|
||||
i = 0;
|
||||
@ -4860,9 +4879,9 @@ static int clusterManagerCommandRebalance(int argc, char **argv) {
|
||||
n->weight);
|
||||
n->balance = n->slots_count - expected;
|
||||
total_balance += n->balance;
|
||||
/* Compute the percentage of difference between the
|
||||
* expected number of slots and the real one, to see
|
||||
* if it's over the threshold specified by the user. */
|
||||
/* Compute the percentage of difference between the
|
||||
* expected number of slots and the real one, to see
|
||||
* if it's over the threshold specified by the user. */
|
||||
int over_threshold = 0;
|
||||
if (threshold > 0) {
|
||||
if (n->slots_count > 0) {
|
||||
@ -4887,7 +4906,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--;
|
||||
}
|
||||
@ -5091,7 +5110,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++) {
|
||||
@ -5575,7 +5594,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;
|
||||
@ -6544,6 +6563,7 @@ int main(int argc, char **argv) {
|
||||
config.enable_ldb_on_eval = 0;
|
||||
config.last_cmd_type = -1;
|
||||
config.verbose = 0;
|
||||
config.no_auth_warning = 0;
|
||||
config.cluster_manager_command.name = NULL;
|
||||
config.cluster_manager_command.argc = 0;
|
||||
config.cluster_manager_command.argv = NULL;
|
||||
|
@ -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;
|
||||
@ -1314,24 +1315,31 @@ error:
|
||||
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
||||
char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
|
||||
/* Create the command to send to the master, we use simple inline
|
||||
* protocol for simplicity as currently we only send simple strings. */
|
||||
/* Create the command to send to the master, we use redis binary
|
||||
* protocol to make sure correct arguments are sent. This function
|
||||
* is not safe for all binary data. */
|
||||
if (flags & SYNC_CMD_WRITE) {
|
||||
char *arg;
|
||||
va_list ap;
|
||||
sds cmd = sdsempty();
|
||||
sds cmdargs = sdsempty();
|
||||
size_t argslen = 0;
|
||||
va_start(ap,fd);
|
||||
|
||||
while(1) {
|
||||
arg = va_arg(ap, char*);
|
||||
if (arg == NULL) break;
|
||||
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
|
||||
argslen++;
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
|
||||
cmd = sdscatprintf(cmd,"*%zu\r\n",argslen);
|
||||
cmd = sdscatsds(cmd,cmdargs);
|
||||
sdsfree(cmdargs);
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
|
@ -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;
|
||||
|
@ -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 <sha1> [<sha1> ...] -- 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 <script> -- Load a script into the scripts cache, without executing it.",
|
||||
"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
|
||||
"EXISTS <sha1> [<sha1> ...] -- 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 <script> -- Load a script into the scripts cache, without executing it.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
228
src/sentinel.c
228
src/sentinel.c
@ -84,6 +84,7 @@ typedef struct sentinelAddr {
|
||||
#define SENTINEL_MAX_PENDING_COMMANDS 100
|
||||
#define SENTINEL_ELECTION_TIMEOUT 10000
|
||||
#define SENTINEL_MAX_DESYNC 1000
|
||||
#define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 1
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
@ -177,6 +178,10 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t o_down_since_time; /* Objectively down since time. */
|
||||
mstime_t down_after_period; /* Consider it down after that period. */
|
||||
mstime_t info_refresh; /* Time at which we received INFO output from it. */
|
||||
dict *renamed_commands; /* Commands renamed in this instance:
|
||||
Sentinel will use the alternative commands
|
||||
mapped on this table to send things like
|
||||
SLAVEOF, CONFING, INFO, ... */
|
||||
|
||||
/* Role and the first time we observed it.
|
||||
* This is useful in order to delay replacing what the instance reports
|
||||
@ -241,6 +246,8 @@ struct sentinelState {
|
||||
int announce_port; /* Port that is gossiped to other sentinels if
|
||||
non zero. */
|
||||
unsigned long simfailure_flags; /* Failures simulation. */
|
||||
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
|
||||
paths at runtime? */
|
||||
} sentinel;
|
||||
|
||||
/* A script execution job. */
|
||||
@ -380,7 +387,9 @@ void sentinelSimFailureCrash(void);
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
uint64_t dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
|
||||
void releaseSentinelRedisInstance(sentinelRedisInstance *ri);
|
||||
|
||||
void dictInstancesValDestructor (void *privdata, void *obj) {
|
||||
@ -414,6 +423,16 @@ dictType leaderVotesDictType = {
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* Instance renamed commands table. */
|
||||
dictType renamedCommandsDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictSdsDestructor /* val destructor */
|
||||
};
|
||||
|
||||
/* =========================== Initialization =============================== */
|
||||
|
||||
void sentinelCommand(client *c);
|
||||
@ -468,6 +487,7 @@ void initSentinel(void) {
|
||||
sentinel.announce_ip = NULL;
|
||||
sentinel.announce_port = 0;
|
||||
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
|
||||
sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
|
||||
memset(sentinel.myid,0,sizeof(sentinel.myid));
|
||||
}
|
||||
|
||||
@ -1207,6 +1227,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->master = master;
|
||||
ri->slaves = dictCreate(&instancesDictType,NULL);
|
||||
ri->info_refresh = 0;
|
||||
ri->renamed_commands = dictCreate(&renamedCommandsDictType,NULL);
|
||||
|
||||
/* Failover state. */
|
||||
ri->leader = NULL;
|
||||
@ -1254,6 +1275,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
|
||||
sdsfree(ri->auth_pass);
|
||||
sdsfree(ri->info);
|
||||
releaseSentinelAddr(ri->addr);
|
||||
dictRelease(ri->renamed_commands);
|
||||
|
||||
/* Clear state into the master if needed. */
|
||||
if ((ri->flags & SRI_SLAVE) && (ri->flags & SRI_PROMOTED) && ri->master)
|
||||
@ -1568,6 +1590,21 @@ char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
|
||||
else return "unknown";
|
||||
}
|
||||
|
||||
/* This function is used in order to send commands to Redis instances: the
|
||||
* commands we send from Sentinel may be renamed, a common case is a master
|
||||
* with CONFIG and SLAVEOF commands renamed for security concerns. In that
|
||||
* case we check the ri->renamed_command table (or if the instance is a slave,
|
||||
* we check the one of the master), and map the command that we should send
|
||||
* to the set of renamed commads. However, if the command was not renamed,
|
||||
* we just return "command" itself. */
|
||||
char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {
|
||||
sds sc = sdsnew(command);
|
||||
if (ri->master) ri = ri->master;
|
||||
char *retval = dictFetchValue(ri->renamed_commands, sc);
|
||||
sdsfree(sc);
|
||||
return retval ? retval : command;
|
||||
}
|
||||
|
||||
/* ============================ Config handling ============================= */
|
||||
char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
sentinelRedisInstance *ri;
|
||||
@ -1677,6 +1714,17 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
si->runid = sdsnew(argv[4]);
|
||||
sentinelTryConnectionSharing(si);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 4) {
|
||||
/* rename-command <name> <command> <renamed-command> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
sds oldcmd = sdsnew(argv[2]);
|
||||
sds newcmd = sdsnew(argv[3]);
|
||||
if (dictAdd(ri->renamed_commands,oldcmd,newcmd) != DICT_OK) {
|
||||
sdsfree(oldcmd);
|
||||
sdsfree(newcmd);
|
||||
return "Same command renamed multiple times with rename-command.";
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
|
||||
/* announce-ip <ip-address> */
|
||||
if (strlen(argv[1]))
|
||||
@ -1684,6 +1732,12 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
} else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {
|
||||
/* announce-port <port> */
|
||||
sentinel.announce_port = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"deny-scripts-reconfig") && argc == 2) {
|
||||
/* deny-scripts-reconfig <yes|no> */
|
||||
if ((sentinel.deny_scripts_reconfig = yesnotoi(argv[1])) == -1) {
|
||||
return "Please specify yes or no for the "
|
||||
"deny-scripts-reconfig options.";
|
||||
}
|
||||
} else {
|
||||
return "Unrecognized sentinel configuration statement.";
|
||||
}
|
||||
@ -1704,6 +1758,12 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
line = sdscatprintf(sdsempty(), "sentinel myid %s", sentinel.myid);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel deny-scripts-reconfig. */
|
||||
line = sdscatprintf(sdsempty(), "sentinel deny-scripts-reconfig %s",
|
||||
sentinel.deny_scripts_reconfig ? "yes" : "no");
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,
|
||||
sentinel.deny_scripts_reconfig != SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG);
|
||||
|
||||
/* For every master emit a "sentinel monitor" config entry. */
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
@ -1811,6 +1871,18 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
|
||||
/* sentinel rename-command */
|
||||
di2 = dictGetIterator(master->renamed_commands);
|
||||
while((de = dictNext(di2)) != NULL) {
|
||||
sds oldname = dictGetKey(de);
|
||||
sds newname = dictGetVal(de);
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel rename-command %s %s %s",
|
||||
master->name, oldname, newname);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
}
|
||||
|
||||
/* sentinel current-epoch is a global state valid for all the masters. */
|
||||
@ -1875,7 +1947,8 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
ri->master->auth_pass;
|
||||
|
||||
if (auth_pass) {
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "AUTH %s",
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
}
|
||||
@ -1891,7 +1964,9 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
|
||||
|
||||
snprintf(name,sizeof(name),"sentinel-%.8s-%s",sentinel.myid,type);
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri,
|
||||
"CLIENT SETNAME %s", name) == C_OK)
|
||||
"%s SETNAME %s",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"),
|
||||
name) == C_OK)
|
||||
{
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
@ -1953,8 +2028,9 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
sentinelSetClientName(ri,link->pc,"pubsub");
|
||||
/* Now we subscribe to the Sentinels "Hello" channel. */
|
||||
retval = redisAsyncCommand(link->pc,
|
||||
sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
|
||||
SENTINEL_HELLO_CHANNEL);
|
||||
sentinelReceiveHelloMessages, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"SUBSCRIBE"),
|
||||
SENTINEL_HELLO_CHANNEL);
|
||||
if (retval != C_OK) {
|
||||
/* If we can't subscribe, the Pub/Sub connection is useless
|
||||
* and we can simply disconnect it and try again. */
|
||||
@ -2288,8 +2364,11 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
{
|
||||
if (redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri,
|
||||
"SCRIPT KILL") == C_OK)
|
||||
"%s KILL",
|
||||
sentinelInstanceMapCommand(ri,"SCRIPT")) == C_OK)
|
||||
{
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
ri->flags |= SRI_SCRIPT_KILL_SENT;
|
||||
}
|
||||
}
|
||||
@ -2495,8 +2574,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
master->name,master_addr->ip,master_addr->port,
|
||||
(unsigned long long) master->config_epoch);
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelPublishReplyCallback, ri, "PUBLISH %s %s",
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
sentinelPublishReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"PUBLISH"),
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
if (retval != C_OK) return C_ERR;
|
||||
ri->link->pending_commands++;
|
||||
return C_OK;
|
||||
@ -2541,7 +2621,8 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) {
|
||||
* queued in the connection. */
|
||||
int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
int retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelPingReplyCallback, ri, "PING");
|
||||
sentinelPingReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"PING"));
|
||||
if (retval == C_OK) {
|
||||
ri->link->pending_commands++;
|
||||
ri->link->last_ping_time = mstime();
|
||||
@ -2605,7 +2686,8 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
(now - ri->info_refresh) > info_period))
|
||||
{
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelInfoReplyCallback, ri, "INFO");
|
||||
sentinelInfoReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"INFO"));
|
||||
if (retval == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
|
||||
@ -3099,7 +3181,7 @@ void sentinelCommand(client *c) {
|
||||
addReplySds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
|
||||
if (c->argc < 3) goto numargserr;
|
||||
sentinelSetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
|
||||
/* SENTINEL INFO-CACHE <name> */
|
||||
@ -3298,39 +3380,58 @@ void sentinelRoleCommand(client *c) {
|
||||
void sentinelSetCommand(client *c) {
|
||||
sentinelRedisInstance *ri;
|
||||
int j, changes = 0;
|
||||
char *option, *value;
|
||||
int badarg = 0; /* Bad argument position for error reporting. */
|
||||
char *option;
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
|
||||
/* Process option - value pairs. */
|
||||
for (j = 3; j < c->argc; j += 2) {
|
||||
for (j = 3; j < c->argc; j++) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
option = c->argv[j]->ptr;
|
||||
value = c->argv[j+1]->ptr;
|
||||
robj *o = c->argv[j+1];
|
||||
long long ll;
|
||||
int old_j = j; /* Used to know what to log as an event. */
|
||||
|
||||
if (!strcasecmp(option,"down-after-milliseconds")) {
|
||||
if (!strcasecmp(option,"down-after-milliseconds") && moreargs > 0) {
|
||||
/* down-after-millisecodns <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->down_after_period = ll;
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"failover-timeout")) {
|
||||
} else if (!strcasecmp(option,"failover-timeout") && moreargs > 0) {
|
||||
/* failover-timeout <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->failover_timeout = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"parallel-syncs")) {
|
||||
} else if (!strcasecmp(option,"parallel-syncs") && moreargs > 0) {
|
||||
/* parallel-syncs <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->parallel_syncs = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"notification-script")) {
|
||||
} else if (!strcasecmp(option,"notification-script") && moreargs > 0) {
|
||||
/* notification-script <path> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
"security reasons. Check the deny-scripts-reconfig "
|
||||
"configuration directive in your Sentinel configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(value) && access(value,X_OK) == -1) {
|
||||
addReplyError(c,
|
||||
"Notification script seems non existing or non executable");
|
||||
@ -3340,8 +3441,17 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->notification_script);
|
||||
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"client-reconfig-script")) {
|
||||
} else if (!strcasecmp(option,"client-reconfig-script") && moreargs > 0) {
|
||||
/* client-reconfig-script <path> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
"security reasons. Check the deny-scripts-reconfig "
|
||||
"configuration directive in your Sentinel configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(value) && access(value,X_OK) == -1) {
|
||||
addReplyError(c,
|
||||
"Client reconfiguration script seems non existing or "
|
||||
@ -3352,24 +3462,65 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->client_reconfig_script);
|
||||
ri->client_reconfig_script = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"auth-pass")) {
|
||||
} else if (!strcasecmp(option,"auth-pass") && moreargs > 0) {
|
||||
/* auth-pass <password> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum")) {
|
||||
} else if (!strcasecmp(option,"quorum") && moreargs > 0) {
|
||||
/* quorum <count> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->quorum = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"rename-command") && moreargs > 1) {
|
||||
/* rename-command <oldname> <newname> */
|
||||
sds oldname = c->argv[++j]->ptr;
|
||||
sds newname = c->argv[++j]->ptr;
|
||||
|
||||
if ((sdslen(oldname) == 0) || (sdslen(newname) == 0)) {
|
||||
badarg = sdslen(newname) ? j-1 : j;
|
||||
goto badfmt;
|
||||
}
|
||||
|
||||
/* Remove any older renaming for this command. */
|
||||
dictDelete(ri->renamed_commands,oldname);
|
||||
|
||||
/* If the target name is the same as the source name there
|
||||
* is no need to add an entry mapping to itself. */
|
||||
if (!dictSdsKeyCaseCompare(NULL,oldname,newname)) {
|
||||
oldname = sdsdup(oldname);
|
||||
newname = sdsdup(newname);
|
||||
dictAdd(ri->renamed_commands,oldname,newname);
|
||||
}
|
||||
changes++;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
|
||||
option);
|
||||
addReplyErrorFormat(c,"Unknown option or number of arguments for "
|
||||
"SENTINEL SET '%s'", option);
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
|
||||
/* Log the event. */
|
||||
int numargs = j-old_j+1;
|
||||
switch(numargs) {
|
||||
case 2:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr);
|
||||
break;
|
||||
case 3:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr,
|
||||
c->argv[old_j+2]->ptr);
|
||||
break;
|
||||
default:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@ -3379,7 +3530,7 @@ void sentinelSetCommand(client *c) {
|
||||
badfmt: /* Bad format errors */
|
||||
if (changes) sentinelFlushConfig();
|
||||
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
|
||||
value, option);
|
||||
(char*)c->argv[badarg]->ptr,option);
|
||||
}
|
||||
|
||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||
@ -3585,7 +3736,8 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
ll2string(port,sizeof(port),master->addr->port);
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelReceiveIsMasterDownReply, ri,
|
||||
"SENTINEL is-master-down-by-addr %s %s %llu %s",
|
||||
"%s is-master-down-by-addr %s %s %llu %s",
|
||||
sentinelInstanceMapCommand(ri,"SENTINEL"),
|
||||
master->addr->ip, port,
|
||||
sentinel.current_epoch,
|
||||
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
|
||||
@ -3764,17 +3916,21 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
* Note that we don't check the replies returned by commands, since we
|
||||
* will observe instead the effects in the next INFO output. */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "MULTI");
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"MULTI"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "SLAVEOF %s %s", host, portstr);
|
||||
sentinelDiscardReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"SLAVEOF"),
|
||||
host, portstr);
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "CONFIG REWRITE");
|
||||
sentinelDiscardReplyCallback, ri, "%s REWRITE",
|
||||
sentinelInstanceMapCommand(ri,"CONFIG"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
@ -3784,12 +3940,14 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
* recognized as a syntax error, and the transaction will not fail (but
|
||||
* only the unsupported command will fail). */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "CLIENT KILL TYPE normal");
|
||||
sentinelDiscardReplyCallback, ri, "%s KILL TYPE normal",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "EXEC");
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"EXEC"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
|
46
src/server.c
46
src/server.c
@ -198,8 +198,8 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,-1,1,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,-1,1,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
@ -845,13 +845,14 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
|
||||
/* There are two conditions to resize the query buffer:
|
||||
* 1) Query buffer is > BIG_ARG and too big for latest peak.
|
||||
* 2) Client is inactive and the buffer is bigger than 1k. */
|
||||
if (((querybuf_size > PROTO_MBULK_BIG_ARG) &&
|
||||
(querybuf_size/(c->querybuf_peak+1)) > 2) ||
|
||||
(querybuf_size > 1024 && idletime > 2))
|
||||
* 2) Query buffer is > BIG_ARG and client is idle. */
|
||||
if (querybuf_size > PROTO_MBULK_BIG_ARG &&
|
||||
((querybuf_size/(c->querybuf_peak+1)) > 2 ||
|
||||
idletime > 2))
|
||||
{
|
||||
/* Only resize the query buffer if it is actually wasting space. */
|
||||
if (sdsavail(c->querybuf) > 1024) {
|
||||
/* Only resize the query buffer if it is actually wasting
|
||||
* at least a few kbytes. */
|
||||
if (sdsavail(c->querybuf) > 1024*4) {
|
||||
c->querybuf = sdsRemoveFreeSpace(c->querybuf);
|
||||
}
|
||||
}
|
||||
@ -1467,6 +1468,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;
|
||||
@ -1496,6 +1498,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;
|
||||
@ -1897,6 +1901,7 @@ void initServer(void) {
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
server.slaves = listCreate();
|
||||
server.monitors = listCreate();
|
||||
@ -2646,7 +2651,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. */
|
||||
@ -2835,9 +2840,9 @@ void commandCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"(no subcommand) -- Return details about all Redis commands.",
|
||||
"count -- Return the total number of commands in this Redis server.",
|
||||
"getkeys <full-command> -- Return the keys from a full Redis command.",
|
||||
"info [command-name ...] -- Return details about multiple Redis commands.",
|
||||
"COUNT -- Return the total number of commands in this Redis server.",
|
||||
"GETKEYS <full-command> -- Return the keys from a full Redis command.",
|
||||
"INFO [command-name ...] -- Return details about multiple Redis commands.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -2861,7 +2866,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))
|
||||
@ -2871,11 +2879,15 @@ 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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
32
src/server.h
32
src/server.h
@ -142,6 +142,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE 1
|
||||
#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,8 @@ 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 */
|
||||
|
||||
#define LIMIT_PENDING_QUERYBUF (4*1024*1024) /* 4mb */
|
||||
|
||||
/* When configuring the server eventloop, we setup it so that the total number
|
||||
@ -340,7 +342,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 */
|
||||
@ -349,12 +351,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
|
||||
@ -781,7 +785,7 @@ typedef struct zskiplistNode {
|
||||
struct zskiplistNode *backward;
|
||||
struct zskiplistLevel {
|
||||
struct zskiplistNode *forward;
|
||||
unsigned int span;
|
||||
unsigned long span;
|
||||
} level[];
|
||||
} zskiplistNode;
|
||||
|
||||
@ -880,13 +884,13 @@ typedef struct rdbSaveInfo {
|
||||
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1}
|
||||
|
||||
typedef struct malloc_stats {
|
||||
struct malloc_stats {
|
||||
size_t zmalloc_used;
|
||||
size_t process_rss;
|
||||
size_t allocator_allocated;
|
||||
size_t allocator_active;
|
||||
size_t allocator_resident;
|
||||
} malloc_stats;
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Global server state
|
||||
@ -950,6 +954,7 @@ struct redisServer {
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client, only used on crash report */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
@ -993,7 +998,7 @@ struct redisServer {
|
||||
long long slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
||||
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
||||
long long stat_net_input_bytes; /* Bytes read from network. */
|
||||
long long stat_net_output_bytes; /* Bytes written to network. */
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
@ -1045,7 +1050,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. */
|
||||
@ -1178,6 +1184,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;
|
||||
@ -1407,6 +1415,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 addReplySubcommandSyntaxError(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
@ -1415,7 +1424,7 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
char *getClientPeerId(client *client);
|
||||
sds catClientInfoString(sds s, client *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
sds getAllClientsInfoString(int type);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
@ -1496,6 +1505,7 @@ robj *tryObjectEncoding(robj *o);
|
||||
robj *getDecodedObject(robj *o);
|
||||
size_t stringObjectLen(robj *o);
|
||||
robj *createStringObjectFromLongLong(long long value);
|
||||
robj *createStringObjectFromLongLongForValue(long long value);
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
|
||||
robj *createQuicklistObject(void);
|
||||
robj *createZiplistObject(void);
|
||||
@ -1625,7 +1635,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);
|
||||
@ -1766,6 +1776,8 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock);
|
||||
#define LOOKUP_NONE 0
|
||||
#define LOOKUP_NOTOUCH (1<<0)
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -142,11 +142,11 @@ void slowlogReset(void) {
|
||||
void slowlogCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"get [count] -- Return top entries from the slowlog (default: 10)."
|
||||
"GET [count] -- Return top entries from the slowlog (default: 10)."
|
||||
" Entries are made of:",
|
||||
" id, timestamp, time in microseconds, arguments array, client IP and port, client name",
|
||||
"len -- Return the length of the slowlog.",
|
||||
"reset -- Reset the slowlog.",
|
||||
"LEN -- Return the length of the slowlog.",
|
||||
"RESET -- Reset the slowlog.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -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);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
@ -447,7 +447,7 @@ void sortCommand(client *c) {
|
||||
serverAssertWithInfo(c,sortval,j == vectorlen);
|
||||
|
||||
/* Now it's time to load the right scores in the sorting vector */
|
||||
if (dontsort == 0) {
|
||||
if (!dontsort) {
|
||||
for (j = 0; j < vectorlen; j++) {
|
||||
robj *byval;
|
||||
if (sortby) {
|
||||
@ -487,9 +487,7 @@ void sortCommand(client *c) {
|
||||
decrRefCount(byval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dontsort == 0) {
|
||||
server.sort_desc = desc;
|
||||
server.sort_alpha = alpha;
|
||||
server.sort_bypattern = sortby ? 1 : 0;
|
||||
|
182
src/t_stream.c
182
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);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
@ -171,7 +172,7 @@ int streamCompareID(streamID *a, streamID *b) {
|
||||
* if the ID was generated by the function. However the function may return
|
||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. */
|
||||
int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, streamID *use_id) {
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
/* If an ID was given, check that it's greater than the last entry ID
|
||||
* or return an error. */
|
||||
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
|
||||
@ -221,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.
|
||||
*
|
||||
@ -234,10 +235,24 @@ 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
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes > server.stream_node_max_bytes)
|
||||
{
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
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. */
|
||||
@ -245,7 +260,7 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
|
||||
lp = lpAppendInteger(lp,1); /* One item, the one we are adding. */
|
||||
lp = lpAppendInteger(lp,0); /* Zero deleted so far. */
|
||||
lp = lpAppendInteger(lp,numfields);
|
||||
for (int i = 0; i < numfields; i++) {
|
||||
for (int64_t i = 0; i < numfields; i++) {
|
||||
sds field = argv[i*2]->ptr;
|
||||
lp = lpAppend(lp,(unsigned char*)field,sdslen(field));
|
||||
}
|
||||
@ -270,10 +285,10 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
|
||||
|
||||
/* Check if the entry we are adding, have the same fields
|
||||
* as the master entry. */
|
||||
int master_fields_count = lpGetInteger(lp_ele);
|
||||
int64_t master_fields_count = lpGetInteger(lp_ele);
|
||||
lp_ele = lpNext(lp,lp_ele);
|
||||
if (numfields == master_fields_count) {
|
||||
int i;
|
||||
int64_t i;
|
||||
for (i = 0; i < master_fields_count; i++) {
|
||||
sds field = argv[i*2]->ptr;
|
||||
int64_t e_len;
|
||||
@ -317,14 +332,14 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
|
||||
lp = lpAppendInteger(lp,id.seq - master_id.seq);
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))
|
||||
lp = lpAppendInteger(lp,numfields);
|
||||
for (int i = 0; i < numfields; i++) {
|
||||
for (int64_t i = 0; i < numfields; i++) {
|
||||
sds field = argv[i*2]->ptr, value = argv[i*2+1]->ptr;
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))
|
||||
lp = lpAppend(lp,(unsigned char*)field,sdslen(field));
|
||||
lp = lpAppend(lp,(unsigned char*)value,sdslen(value));
|
||||
}
|
||||
/* Compute and store the lp-count field. */
|
||||
int lp_count = numfields;
|
||||
int64_t lp_count = numfields;
|
||||
lp_count += 3; /* Add the 3 fixed fields flags + ms-diff + seq-diff. */
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) {
|
||||
/* If the item is not compressed, it also has the fields other than
|
||||
@ -564,7 +579,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
/* If we are going backward, read the number of elements this
|
||||
* entry is composed of, and jump backward N times to seek
|
||||
* its start. */
|
||||
int lp_count = lpGetInteger(si->lp_ele);
|
||||
int64_t lp_count = lpGetInteger(si->lp_ele);
|
||||
if (lp_count == 0) { /* We reached the master entry. */
|
||||
si->lp = NULL;
|
||||
si->lp_ele = NULL;
|
||||
@ -627,12 +642,17 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
* forward, or seek the previous entry if we are going
|
||||
* backward. */
|
||||
if (!si->rev) {
|
||||
int to_discard = (flags & STREAM_ITEM_FLAG_SAMEFIELDS) ?
|
||||
*numfields : *numfields*2;
|
||||
int64_t to_discard = (flags & STREAM_ITEM_FLAG_SAMEFIELDS) ?
|
||||
*numfields : *numfields*2;
|
||||
for (int64_t i = 0; i < to_discard; i++)
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele);
|
||||
} else {
|
||||
int prev_times = 4; /* flag + id ms/seq diff + numfields. */
|
||||
int64_t prev_times = 4; /* flag + id ms + id seq + one more to
|
||||
go back to the previous entry "count"
|
||||
field. */
|
||||
/* If the entry was not flagged SAMEFIELD we also read the
|
||||
* number of fields, so go back one more. */
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) prev_times++;
|
||||
while(prev_times--) si->lp_ele = lpPrev(si->lp,si->lp_ele);
|
||||
}
|
||||
}
|
||||
@ -690,6 +710,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) {
|
||||
@ -814,7 +837,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.
|
||||
@ -867,18 +890,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) {
|
||||
@ -899,7 +945,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
/* This is an helper function for streamReplyWithRange() when called with
|
||||
* group and consumer arguments, but with a range that is referring to already
|
||||
* delivered messages. In this case we just emit messages that are already
|
||||
* in the history of the conusmer, fetching the IDs from its PEL.
|
||||
* in the history of the consumer, fetching the IDs from its PEL.
|
||||
*
|
||||
* Note that this function does not have a 'rev' argument because it's not
|
||||
* possible to iterate in reverse using a group. Basically this function
|
||||
@ -1035,7 +1081,7 @@ invalid:
|
||||
void xaddCommand(client *c) {
|
||||
streamID id;
|
||||
int id_given = 0; /* Was an ID different than "*" specified? */
|
||||
long long maxlen = 0; /* 0 means no maximum length. */
|
||||
long long maxlen = -1; /* If left to -1 no trimming is performed. */
|
||||
int approx_maxlen = 0; /* If 1 only delete whole radix tree nodes, so
|
||||
the maxium length is not applied verbatim. */
|
||||
int maxlen_arg_idx = 0; /* Index of the count in MAXLEN, for rewriting. */
|
||||
@ -1059,6 +1105,11 @@ void xaddCommand(client *c) {
|
||||
}
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[i+1],&maxlen,NULL)
|
||||
!= C_OK) return;
|
||||
|
||||
if (maxlen < 0) {
|
||||
addReplyError(c,"The MAXLEN argument must be >= 0.");
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
maxlen_arg_idx = i;
|
||||
} else {
|
||||
@ -1098,7 +1149,7 @@ void xaddCommand(client *c) {
|
||||
server.dirty++;
|
||||
|
||||
/* Remove older elements if MAXLEN was specified. */
|
||||
if (maxlen) {
|
||||
if (maxlen >= 0) {
|
||||
if (!streamTrimByLength(s,maxlen,approx_maxlen)) {
|
||||
/* If no trimming was performed, for instance because approximated
|
||||
* trimming length was specified, rewrite the MAXLEN argument
|
||||
@ -1269,14 +1320,13 @@ void xreadCommand(client *c) {
|
||||
* starting from now. */
|
||||
int id_idx = i - streams_arg - streams_count;
|
||||
robj *key = c->argv[i-streams_count];
|
||||
robj *o;
|
||||
robj *o = lookupKeyRead(c->db,key);
|
||||
if (o && checkType(c,o,OBJ_STREAM)) goto cleanup;
|
||||
streamCG *group = NULL;
|
||||
|
||||
/* If a group was specified, than we need to be sure that the
|
||||
* key and group actually exist. */
|
||||
if (groupname) {
|
||||
o = lookupKeyRead(c->db,key);
|
||||
if (o && checkType(c,o,OBJ_STREAM)) goto cleanup;
|
||||
if (o == NULL ||
|
||||
(group = streamLookupCG(o->ptr,groupname->ptr)) == NULL)
|
||||
{
|
||||
@ -1290,8 +1340,6 @@ void xreadCommand(client *c) {
|
||||
}
|
||||
|
||||
if (strcmp(c->argv[i]->ptr,"$") == 0) {
|
||||
o = lookupKeyRead(c->db,key);
|
||||
if (o && checkType(c,o,OBJ_STREAM)) goto cleanup;
|
||||
if (o) {
|
||||
stream *s = o->ptr;
|
||||
ids[id_idx] = s->last_id;
|
||||
@ -1336,7 +1384,7 @@ void xreadCommand(client *c) {
|
||||
/* Emit the two elements sub-array consisting of the name
|
||||
* of the stream and the data we extracted from it. */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyBulk(c,c->argv[i+streams_arg]);
|
||||
addReplyBulk(c,c->argv[streams_arg+i]);
|
||||
streamConsumer *consumer = NULL;
|
||||
if (groups) consumer = streamLookupConsumer(groups[i],
|
||||
consumername->ptr,1);
|
||||
@ -1516,14 +1564,14 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
|
||||
|
||||
/* XGROUP CREATE <key> <groupname> <id or $>
|
||||
* XGROUP SETID <key> <id or $>
|
||||
* XGROUP DELGROUP <key> <groupname>
|
||||
* XGROUP DESTROY <key> <groupname>
|
||||
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
|
||||
void xgroupCommand(client *c) {
|
||||
const char *help[] = {
|
||||
"CREATE <key> <groupname> <id or $> -- Create a new consumer group.",
|
||||
"SETID <key> <groupname> <id or $> -- Set the current group ID.",
|
||||
"DELGROUP <key> <groupname> -- Remove the specified group.",
|
||||
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified conusmer.",
|
||||
"DESTROY <key> <groupname> -- Remove the specified group.",
|
||||
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer.",
|
||||
"HELP -- Prints this help.",
|
||||
NULL
|
||||
};
|
||||
@ -1535,14 +1583,13 @@ NULL
|
||||
/* Lookup the key now, this is common for all the subcommands but HELP. */
|
||||
if (c->argc >= 4) {
|
||||
robj *o = lookupKeyWriteOrReply(c,c->argv[2],shared.nokeyerr);
|
||||
if (o == NULL) return;
|
||||
if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
|
||||
s = o->ptr;
|
||||
grpname = c->argv[3]->ptr;
|
||||
|
||||
/* 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' "
|
||||
@ -1564,22 +1611,46 @@ NULL
|
||||
if (cg) {
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-create",
|
||||
c->argv[2],c->db->id);
|
||||
} else {
|
||||
addReplySds(c,
|
||||
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) {
|
||||
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);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-setid",c->argv[2],c->db->id);
|
||||
} 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);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
|
||||
c->argv[2],c->db->id);
|
||||
} 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. */
|
||||
long long pending = streamDelConsumer(cg,c->argv[4]->ptr);
|
||||
addReplyLongLong(c,pending);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer",
|
||||
c->argv[2],c->db->id);
|
||||
} else if (!strcasecmp(opt,"HELP")) {
|
||||
addReplyHelp(c, help);
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1728,8 +1799,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)];
|
||||
@ -1785,7 +1858,7 @@ void xpendingCommand(client *c) {
|
||||
* becomes the specified <consumer>. 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.
|
||||
@ -1970,7 +2043,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) {
|
||||
@ -1999,7 +2072,7 @@ void xclaimCommand(client *c) {
|
||||
void xdelCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL
|
||||
|| checkType(c,o,OBJ_STREAM)) return;
|
||||
stream *s = o->ptr;
|
||||
|
||||
@ -2040,7 +2113,7 @@ void xtrimCommand(client *c) {
|
||||
|
||||
/* If the key does not exist, we are ok returning zero, that is, the
|
||||
* number of elements removed from the stream. */
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL
|
||||
|| checkType(c,o,OBJ_STREAM)) return;
|
||||
stream *s = o->ptr;
|
||||
|
||||
@ -2093,14 +2166,12 @@ void xtrimCommand(client *c) {
|
||||
/* XINFO CONSUMERS key group
|
||||
* XINFO GROUPS <key>
|
||||
* XINFO STREAM <key>
|
||||
* XINFO <key> (alias of XINFO STREAM key)
|
||||
* XINFO HELP. */
|
||||
void xinfoCommand(client *c) {
|
||||
const char *help[] = {
|
||||
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
|
||||
"GROUPS <key> -- Show the stream consumer groups.",
|
||||
"STREAM <key> -- Show information about the stream.",
|
||||
"<key> -- Alias for STREAM <key>.",
|
||||
"HELP -- Print this help.",
|
||||
NULL
|
||||
};
|
||||
@ -2112,20 +2183,19 @@ 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 "<subcommand> <key>". */
|
||||
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);
|
||||
if (o == NULL) return;
|
||||
if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
|
||||
s = o->ptr;
|
||||
|
||||
/* Dispatch the different subcommands. */
|
||||
@ -2180,9 +2250,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 <key> (or the alias XINFO <key>). */
|
||||
addReplyMultiBulkLen(c,12);
|
||||
addReplyStatus(c,"length");
|
||||
@ -2209,7 +2277,7 @@ NULL
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
addReplyError(c,"syntax error, try 'XINFO HELP'");
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +361,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
new = o;
|
||||
o->ptr = (void*)((long)value);
|
||||
} else {
|
||||
new = createStringObjectFromLongLong(value);
|
||||
new = createStringObjectFromLongLongForValue(value);
|
||||
if (o) {
|
||||
dbOverwrite(c->db,c->argv[1],new);
|
||||
} else {
|
||||
|
28
src/t_zset.c
28
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) {
|
||||
@ -3163,8 +3167,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;
|
||||
|
||||
@ -3216,9 +3220,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]);
|
||||
|
@ -27,7 +27,7 @@
|
||||
* traversal.
|
||||
*
|
||||
* <uint16_t zllen> 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.
|
||||
*
|
||||
* <uint8_t zlend> 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. */
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* This function provide us access to the original libc free(). This is useful
|
||||
* for instance to free results obtained by backtrace_symbols(). We need
|
||||
@ -164,7 +165,7 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
|
||||
*((size_t*)newptr) = size;
|
||||
update_zmalloc_stat_free(oldsize);
|
||||
update_zmalloc_stat_alloc(size);
|
||||
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
|
||||
return (char*)newptr+PREFIX_SIZE;
|
||||
#endif
|
||||
}
|
||||
@ -418,7 +419,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 */
|
||||
|
@ -63,6 +63,11 @@
|
||||
|
||||
#ifndef ZMALLOC_LIB
|
||||
#define ZMALLOC_LIB "libc"
|
||||
#ifdef __GLIBC__
|
||||
#include <malloc.h>
|
||||
#define HAVE_MALLOC_SIZE 1
|
||||
#define zmalloc_size(p) malloc_usable_size(p)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* We can enable the Redis defrag capabilities only if we are using Jemalloc
|
||||
|
@ -39,6 +39,25 @@ start_server [list overrides [list "dir" $server_path]] {
|
||||
} {0000000000000000000000000000000000000000}
|
||||
}
|
||||
|
||||
start_server [list overrides [list "dir" $server_path]] {
|
||||
test {Test RDB stream encoding} {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
if {rand() < 0.9} {
|
||||
r xadd stream * foo $j
|
||||
} else {
|
||||
r xadd stream * bar $j
|
||||
}
|
||||
}
|
||||
r xgroup create stream mygroup 0
|
||||
r xreadgroup GROUP mygroup Alice COUNT 1 STREAMS stream >
|
||||
set digest [r debug digest]
|
||||
r debug reload
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r del stream
|
||||
}
|
||||
}
|
||||
|
||||
# Helper function to start a server and kill it, just to check the error
|
||||
# logged.
|
||||
set defaults {}
|
||||
|
@ -66,3 +66,13 @@ test "SDOWN is triggered by misconfigured instance repling with errors" {
|
||||
R 0 bgsave
|
||||
ensure_master_up
|
||||
}
|
||||
|
||||
# We use this test setup to also test command renaming, as a side
|
||||
# effect of the master going down if we send PONG instead of PING
|
||||
test "SDOWN is triggered if we rename PING to PONG" {
|
||||
ensure_master_up
|
||||
S 4 SENTINEL SET mymaster rename-command PING PONG
|
||||
ensure_master_down
|
||||
S 4 SENTINEL SET mymaster rename-command PING PING
|
||||
ensure_master_up
|
||||
}
|
||||
|
@ -276,6 +276,12 @@ proc start_server {options {code undefined}} {
|
||||
error_and_quit $config_file $line
|
||||
}
|
||||
|
||||
if {$::wait_server} {
|
||||
set msg "server started PID: [dict get $srv "pid"]. press any key to continue..."
|
||||
puts $msg
|
||||
read stdin 1
|
||||
}
|
||||
|
||||
while 1 {
|
||||
# check that the server actually started and is ready for connections
|
||||
if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
|
||||
|
@ -83,6 +83,8 @@ set ::force_failure 0
|
||||
set ::timeout 600; # 10 minutes without progresses will quit the test.
|
||||
set ::last_progress [clock seconds]
|
||||
set ::active_servers {} ; # Pids of active Redis instances.
|
||||
set ::dont_clean 0
|
||||
set ::wait_server 0
|
||||
|
||||
# Set to 1 when we are running in client mode. The Redis test uses a
|
||||
# server-client model to run tests simultaneously. The server instance
|
||||
@ -176,6 +178,9 @@ proc s {args} {
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
if {$::dont_clean} {
|
||||
return
|
||||
}
|
||||
if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
|
||||
flush stdout
|
||||
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
|
||||
@ -225,6 +230,7 @@ proc test_server_cron {} {
|
||||
if {$elapsed > $::timeout} {
|
||||
set err "\[[colorstr red TIMEOUT]\]: clients state report follows."
|
||||
puts $err
|
||||
lappend ::failed_tests $err
|
||||
show_clients_state
|
||||
kill_clients
|
||||
force_kill_all_servers
|
||||
@ -411,6 +417,8 @@ proc print_help_screen {} {
|
||||
"--clients <num> Number of test clients (default 16)."
|
||||
"--timeout <sec> Test timeout in seconds (default 10 min)."
|
||||
"--force-failure Force the execution of a test that always fails."
|
||||
"--dont-clean don't delete redis log files after the run"
|
||||
"--wait-server wait after server is started (so that you can attach a debugger)"
|
||||
"--help Print this help screen."
|
||||
} "\n"]
|
||||
}
|
||||
@ -464,6 +472,10 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--clients}} {
|
||||
set ::numclients $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--dont-clean}} {
|
||||
set ::dont_clean 1
|
||||
} elseif {$opt eq {--wait-server}} {
|
||||
set ::wait_server 1
|
||||
} elseif {$opt eq {--timeout}} {
|
||||
set ::timeout $arg
|
||||
incr j
|
||||
|
@ -25,6 +25,39 @@ start_server {tags {"dump"}} {
|
||||
assert {$ttl >= (2569591501-3000) && $ttl <= 2569591501}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE can set an absolute expire} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
r del foo
|
||||
set now [clock milliseconds]
|
||||
r restore foo [expr $now+3000] $encoded absttl
|
||||
set ttl [r pttl foo]
|
||||
assert {$ttl >= 2998 && $ttl <= 3000}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE can set LRU} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
r del foo
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r restore foo 0 $encoded idletime 1000
|
||||
set idle [r object idletime foo]
|
||||
assert {$idle >= 1000 && $idle <= 1002}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE can set LFU} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
r del foo
|
||||
r config set maxmemory-policy allkeys-lfu
|
||||
r restore foo 0 $encoded freq 100
|
||||
set freq [r object freq foo]
|
||||
assert {$freq == 100}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE returns an error of the key already exists} {
|
||||
r set foo bar
|
||||
|
@ -121,7 +121,7 @@ start_server {tags {"expire"}} {
|
||||
list $a $b
|
||||
} {somevalue {}}
|
||||
|
||||
test {TTL returns tiem to live in seconds} {
|
||||
test {TTL returns time to live in seconds} {
|
||||
r del x
|
||||
r setex x 10 somevalue
|
||||
set ttl [r ttl x]
|
||||
|
@ -97,10 +97,15 @@ start_server {tags {"defrag"}} {
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 0
|
||||
r config set list-max-ziplist-size 5 ;# list of 10k items will have 2000 quicklist nodes
|
||||
r config set stream-node-max-entries 5
|
||||
r hmset hash h1 v1 h2 v2 h3 v3
|
||||
r lpush list a b c d
|
||||
r zadd zset 0 a 1 b 2 c 3 d
|
||||
r sadd set a b c d
|
||||
r xadd stream * item 1 value a
|
||||
r xadd stream * item 2 value b
|
||||
r xgroup create stream mygroup 0
|
||||
r xreadgroup GROUP mygroup Alice COUNT 1 STREAMS stream >
|
||||
|
||||
# create big keys with 10k items
|
||||
set rd [redis_deferring_client]
|
||||
@ -109,8 +114,9 @@ start_server {tags {"defrag"}} {
|
||||
$rd lpush biglist [concat "asdfasdfasdf" $j]
|
||||
$rd zadd bigzset $j [concat "asdfasdfasdf" $j]
|
||||
$rd sadd bigset [concat "asdfasdfasdf" $j]
|
||||
$rd xadd bigstream * item 1 value a
|
||||
}
|
||||
for {set j 0} {$j < 40000} {incr j} {
|
||||
for {set j 0} {$j < 50000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
|
||||
@ -134,7 +140,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 500008}
|
||||
assert {[r dbsize] == 500010}
|
||||
|
||||
# create some fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
@ -143,7 +149,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 250008}
|
||||
assert {[r dbsize] == 250010}
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
@ -155,6 +161,7 @@ start_server {tags {"defrag"}} {
|
||||
r config set latency-monitor-threshold 5
|
||||
r latency reset
|
||||
|
||||
set digest [r debug digest]
|
||||
catch {r config set activedefrag yes} e
|
||||
if {![string match {DISABLED*} $e]} {
|
||||
# wait for the active defrag to start working (decision once a second)
|
||||
@ -193,9 +200,11 @@ start_server {tags {"defrag"}} {
|
||||
# due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 75ms
|
||||
assert {$max_latency <= 80}
|
||||
} else {
|
||||
set _ ""
|
||||
}
|
||||
} {}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
} {OK}
|
||||
}
|
||||
}
|
||||
|
@ -236,4 +236,50 @@ start_server {tags {"scan"}} {
|
||||
set first_score [lindex $res 1]
|
||||
assert {$first_score != 0}
|
||||
}
|
||||
|
||||
test "SCAN regression test for issue #4906" {
|
||||
for {set k 0} {$k < 100} {incr k} {
|
||||
r del set
|
||||
r sadd set x; # Make sure it's not intset encoded
|
||||
set toremove {}
|
||||
unset -nocomplain found
|
||||
array set found {}
|
||||
|
||||
# Populate the set
|
||||
set numele [expr {101+[randomInt 1000]}]
|
||||
for {set j 0} {$j < $numele} {incr j} {
|
||||
r sadd set $j
|
||||
if {$j >= 100} {
|
||||
lappend toremove $j
|
||||
}
|
||||
}
|
||||
|
||||
# Start scanning
|
||||
set cursor 0
|
||||
set iteration 0
|
||||
set del_iteration [randomInt 10]
|
||||
while {!($cursor == 0 && $iteration != 0)} {
|
||||
lassign [r sscan set $cursor] cursor items
|
||||
|
||||
# Mark found items. We expect to find from 0 to 99 at the end
|
||||
# since those elements will never be removed during the scanning.
|
||||
foreach i $items {
|
||||
set found($i) 1
|
||||
}
|
||||
incr iteration
|
||||
# At some point remove most of the items to trigger the
|
||||
# rehashing to a smaller hash table.
|
||||
if {$iteration == $del_iteration} {
|
||||
r srem set {*}$toremove
|
||||
}
|
||||
}
|
||||
|
||||
# Verify that SSCAN reported everything from 0 to 99
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
if {![info exists found($j)]} {
|
||||
fail "SSCAN element missing $j"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,4 +253,20 @@ start_server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {XREVRANGE regression test for issue #5006} {
|
||||
# Add non compressed entries
|
||||
r xadd teststream 1234567891230 key1 value1
|
||||
r xadd teststream 1234567891240 key2 value2
|
||||
r xadd teststream 1234567891250 key3 value3
|
||||
|
||||
# Add SAMEFIELD compressed entries
|
||||
r xadd teststream2 1234567891230 key1 value1
|
||||
r xadd teststream2 1234567891240 key1 value2
|
||||
r xadd teststream2 1234567891250 key1 value3
|
||||
|
||||
assert_equal [r xrevrange teststream 1234567891245 -] {{1234567891240-0 {key2 value2}} {1234567891230-0 {key1 value1}}}
|
||||
|
||||
assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,8 @@ GROUPS = [
|
||||
"scripting",
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo"
|
||||
"geo",
|
||||
"stream"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
Loading…
Reference in New Issue
Block a user