Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7209749a6 | ||
|
|
5171a3ff8e | ||
|
|
af7069627e | ||
|
|
3ad68a07ff | ||
|
|
68382091a0 | ||
|
|
6b0cdbd903 | ||
|
|
574017b7d2 | ||
|
|
cd2b5a79ff | ||
|
|
8246a38e83 | ||
|
|
b80e4b6925 | ||
|
|
5e0d9841d4 | ||
|
|
e27a040146 | ||
|
|
c5dfff465e | ||
|
|
86aade7475 | ||
|
|
071e235a52 | ||
|
|
6acd8a31a4 | ||
|
|
20bc3786c0 | ||
|
|
2cadef46f7 | ||
|
|
093ec57d06 | ||
|
|
d769ce0a20 | ||
|
|
280b2dc1e4 | ||
|
|
260b32edff | ||
|
|
28e0ca5a8c | ||
|
|
9d2b1f294d | ||
|
|
3d4ad250cd | ||
|
|
5a3d490c50 | ||
|
|
24fb4e3894 | ||
|
|
a67ecb91f9 | ||
|
|
5cdd475066 | ||
|
|
2cd6b001da | ||
|
|
b2a5a70e5a | ||
|
|
eb26a8174a | ||
|
|
f2d65c380d | ||
|
|
b16e5432b6 | ||
|
|
d01af7abe4 | ||
|
|
77acc63eef | ||
|
|
3ddc537605 | ||
|
|
b85086c573 | ||
|
|
4a9670f517 | ||
|
|
d5712d2151 | ||
|
|
3ca02fb939 | ||
|
|
59e51f92d2 | ||
|
|
3502d1f16b | ||
|
|
1317bab0d1 | ||
|
|
63ed0f7593 | ||
|
|
8b9b02ade5 | ||
|
|
d4ed462b39 | ||
|
|
525a7b399b | ||
|
|
ae9687de6f | ||
|
|
9bb7c4b2e5 | ||
|
|
07142f034a | ||
|
|
733348bbb4 | ||
|
|
4223c41587 | ||
|
|
ad4e5ca925 | ||
|
|
d55c5e8caf | ||
|
|
d2aa10d029 | ||
|
|
2ee4a1c980 |
+229
-11
@@ -1,16 +1,234 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Redis, the place where all the development happens.
|
||||
Redis 5.0 release notes
|
||||
=======================
|
||||
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development environments
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
http://download.redis.io/releases/redis-stable.tar.gz
|
||||
================================================================================
|
||||
Redis 5.0 RC2 Released Wed Jun 13 12:49:13 CEST 2018
|
||||
================================================================================
|
||||
|
||||
More information is available at http://redis.io
|
||||
Upgrade urgency CRITICAL: This release fixes important security issues.
|
||||
HIGH: This release fixes a SCAN commands family bug.
|
||||
MODERATE: This release fixes a PSYNC2 edge case with expires.
|
||||
MODERATE: Sentinel related fixes.
|
||||
LOW: All the other issues
|
||||
|
||||
Happy hacking!
|
||||
NOTE: This release breaks API compatibility with certain APIs that were
|
||||
introduced in Redis 5.0 RC1. Notably ZPOP* now returns score/element in reverse
|
||||
order. XINFO <key> special form was removed, now XINFO STREAM <key> must be
|
||||
used to obtain general information about the stream.
|
||||
|
||||
Redis 5.0 RC2 ixes a number of important issues:
|
||||
|
||||
* Important security issues related to the Lua scripting engine.
|
||||
Please check https://github.com/antirez/redis/issues/5017
|
||||
for more information.
|
||||
|
||||
* A bug with SCAN, SSCAN, HSCAN and ZSCAN, that may not return all the elements.
|
||||
We also add a regression test that can trigger the issue often when present, and
|
||||
may in theory be able to find unrelated regressions.
|
||||
|
||||
* A PSYNC2 bug is fixed: Redis should not expire keys when saving RDB files
|
||||
because otherwise it is no longer possible to use such RDB file as a base
|
||||
for partial resynchronization. It no longer represents the right state.
|
||||
|
||||
* Compatibility of AOF with RDB preamble when the RDB checksum is disabled.
|
||||
|
||||
* Sentinel bug that in some cases prevented Sentinel to detect that the master
|
||||
was down immediately. A delay was added to the detection.
|
||||
|
||||
* XREADGROUP would crash when the master had attacked slaves.
|
||||
|
||||
* Replication and events generation of several streams command were fixed.
|
||||
|
||||
* XREVRANGE fixed, in some cases it could not return elements, or crash the
|
||||
server, or in general not behave correctly.
|
||||
|
||||
* ZPOP can now unblock multiple clients in a sane way.
|
||||
|
||||
* Other minor issues.
|
||||
|
||||
Moreover this release adds new features:
|
||||
|
||||
* XGROUP DESTROY and XGROUP SETID.
|
||||
|
||||
* RDB loading speedup.
|
||||
|
||||
* Configurable stream macro node limits (number of elements / bytes).
|
||||
|
||||
* More smaller improvements.
|
||||
|
||||
The following is the list of commits composing the release, please check
|
||||
the commit messages and authors for credits.
|
||||
|
||||
antirez in commit 9fdcc159:
|
||||
Security: fix redis-cli buffer overflow.
|
||||
1 file changed, 16 insertions(+), 11 deletions(-)
|
||||
|
||||
antirez in commit cf760071:
|
||||
Security: fix Lua struct package offset handling.
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit a57595ca:
|
||||
Security: more cmsgpack fixes by @soloestoy.
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
antirez in commit 8783fb94:
|
||||
Security: update Lua struct package for security.
|
||||
1 file changed, 23 insertions(+), 23 deletions(-)
|
||||
|
||||
antirez in commit 8cb9344b:
|
||||
Security: fix Lua cmsgpack library stack overflow.
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
赵磊 in commit 59080f60:
|
||||
Fix dictScan(): It can't scan all buckets when dict is shrinking.
|
||||
1 file changed, 14 insertions(+), 11 deletions(-)
|
||||
|
||||
dejun.xdj in commit ac2a824a:
|
||||
Fix redis-cli memory leak when sending set preference command.
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
dejun.xdj in commit c7197ff5:
|
||||
Check if the repeat value is positive in while loop of cliSendCommand().
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
dejun.xdj in commit 3f77777f:
|
||||
Change the type of repeat argument to long for function cliSendCommand.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
dejun.xdj in commit 7a565d72:
|
||||
Fix negtive repeat command value issue.
|
||||
1 file changed, 11 insertions(+), 3 deletions(-)
|
||||
|
||||
dejun.xdj in commit 64bf60fb:
|
||||
Detect and stop saving history for auth command with repeat option.
|
||||
1 file changed, 17 insertions(+), 10 deletions(-)
|
||||
|
||||
dejun.xdj in commit 5bed12aa:
|
||||
Change the warning message a little bit to avoid trademark issuses.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
dejun.xdj in commit d71c4961:
|
||||
Stop saving auth command in redis-cli history.
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
dejun.xdj in commit fca99e41:
|
||||
Add warning message when using password on command line
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit 01407a3a:
|
||||
Don't expire keys while loading RDB from AOF preamble.
|
||||
3 files changed, 5 insertions(+), 5 deletions(-)
|
||||
|
||||
WuYunlong in commit fb5408cf:
|
||||
Fix rdb save by allowing dumping of expire keys, so that when we add a new slave, and do a failover, eighter by manual or not, other local slaves will delete the expired keys properly.
|
||||
2 files changed, 3 insertions(+), 7 deletions(-)
|
||||
|
||||
antirez in commit 0b8b6df4:
|
||||
Backport hiredis issue 525 fix to compile on FreeBSD.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit e98627c5:
|
||||
Add INIT INFO to the provided init script.
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
antirez in commit 17f5de89:
|
||||
Fix ae.c when a timer finalizerProc adds an event.
|
||||
2 files changed, 10 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit 266e6423:
|
||||
Sentinel: fix delay in detecting ODOWN.
|
||||
1 file changed, 9 insertions(+), 5 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit eafaf172:
|
||||
AOF & RDB: be compatible with rdbchecksum no
|
||||
1 file changed, 9 insertions(+), 7 deletions(-)
|
||||
|
||||
huijing.whj in commit 4630da37:
|
||||
fix int overflow problem in freeMemoryIfNeeded
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0 RC1 Released Tue May 29 14:14:11 CEST 2018
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the first RC of Redis 5.
|
||||
|
||||
Introduction to the Redis 5 release
|
||||
===================================
|
||||
|
||||
Redis 5 is a release focused on a few important features. While Redis 4
|
||||
was very very focused on operations, Redis 5 changes are mostly user-facing,
|
||||
with the implementation of new data types and operations on top of existing
|
||||
types. The following are the major features of this release:
|
||||
|
||||
1. The new Stream data type. https://redis.io/topics/streams-intro
|
||||
2. New Redis modules APIs: Timers and Cluster API.
|
||||
3. RDB now store LFU and LRU information.
|
||||
4. The cluster manager was ported from Ruby (redis-trib.rb) to C code
|
||||
inside redis-cli. Check `redis-cli --cluster help` for more info.
|
||||
5. New sorted set commands: ZPOPMIN/MAX and blocking variants.
|
||||
6. Active defragmentation version 2.
|
||||
7. Improvemenets in HyperLogLog implementations.
|
||||
8. Better memory reporting capabilities.
|
||||
9. Many commands with sub-commands now have an HELP subcommand.
|
||||
10. Better performances when clients connect and disconnect often.
|
||||
11. Many bug fixes and other random improvements.
|
||||
12. Jemalloc was upgraded to version 5.1
|
||||
|
||||
Thanks to all the users and developers who made this release possible.
|
||||
We'll follow up with more RC releases, until the code looks production ready
|
||||
and we don't get reports of serious issues for a while.
|
||||
|
||||
A special thank you for the amount of work put into this release
|
||||
(in decreasing number of commits) by:
|
||||
|
||||
Fabio Nicotra, <artix2@gmail.com>
|
||||
Soloestoy <zhaozhao.zz@alibaba-inc.com>
|
||||
Itamar Haber <itamar@redislabs.com>
|
||||
Oran Agra <oran@redislabs.com>
|
||||
Dvir Volk <dvirsky@gmail.com>
|
||||
dejun.xdj <dejun.xdj@alibaba-inc.com>
|
||||
Guy Benoish <guy.benoish@redislabs.com>
|
||||
Charsyam <charsyam@gmail.com>
|
||||
Otmar Ertl <otmar.ertl@gmail.com>
|
||||
Jan-Erik Rediger <janerik@fnordig.de>
|
||||
Spinlock <wnzheng@gmail.com>
|
||||
|
||||
Migrating from 4.0 to 5.0
|
||||
=========================
|
||||
|
||||
Redis 4.0 is mostly a strict subset of 5.0, you should not have any problem
|
||||
upgrading your application from 4.0 to 5.0. However this is a list of small
|
||||
non-backward compatible changes introduced in the 5.0 release:
|
||||
|
||||
* redis-cli now implements the cluster management tool. We still ship the
|
||||
old redis-trib, but new fixes will be implemented only in redis-cli.
|
||||
See `redis-cli --cluster help` for more info.
|
||||
|
||||
* The RDB format changed. Redis 5.0 is still able to read 4.0 (and all the
|
||||
past versions) files, but not the other way around.
|
||||
|
||||
* Certain log formats and sentences are different in Redis 5.0.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Credits: For each release, a list of changes with the relative author is
|
||||
provided. Where not specified the implementation and design is done by
|
||||
Salvatore Sanfilippo. Thanks to Redis Labs for making all this possible.
|
||||
Also many thanks to all the other contributors and the amazing community
|
||||
we have.
|
||||
|
||||
Commit messages may contain additional credits.
|
||||
|
||||
Enjoy,
|
||||
Salvatore
|
||||
|
||||
+2
-4
@@ -8,9 +8,7 @@ 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:
|
||||
|
||||
https://groups.google.com/forum/m/#!forum/Redis-db
|
||||
requests. Everything else belongs to the Redis Google Group.
|
||||
|
||||
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
|
||||
@@ -32,7 +30,7 @@ each source file that you contribute.
|
||||
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
|
||||
@@ -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 the Redis source code
|
||||
There are tons of commands implementations inside th 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.
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -22,7 +22,7 @@ just following tose steps:
|
||||
|
||||
1. Remove the jemalloc directory.
|
||||
2. Substitute it with the new jemalloc source tree.
|
||||
3. Edit the Makefile localted in the same directory as the README you are
|
||||
3. Edit the Makefile localted in the same directoy as the README you are
|
||||
reading, and change the --with-version in the Jemalloc configure script
|
||||
options with the version you are using. This is required because otherwise
|
||||
Jemalloc configuration script is broken and will not work nested in another
|
||||
@@ -50,7 +50,7 @@ This is never upgraded since it's part of the Redis project. If there are change
|
||||
Hiredis
|
||||
---
|
||||
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is adviced to take a lot of care:
|
||||
|
||||
1. Check with diff if hiredis API changed and what impact it could have in Redis.
|
||||
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible.
|
||||
@@ -83,6 +83,6 @@ and our version:
|
||||
|
||||
1. Makefile is modified to allow a different compiler than GCC.
|
||||
2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode execution.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode exectuion.
|
||||
|
||||
|
||||
|
||||
@@ -215,32 +215,4 @@ 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 = ((long long)bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((long long)(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,7 +120,3 @@
|
||||
# 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
|
||||
|
||||
Vendored
-11
@@ -3324,14 +3324,3 @@ 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);
|
||||
}
|
||||
|
||||
+1
-43
@@ -602,26 +602,6 @@ slave-priority 100
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
|
||||
# Starting from Redis 5, by default a slave will ignore its maxmemory setting
|
||||
# (unless it is promoted to master after a failover or manually). It means
|
||||
# that the eviction of keys will be just handled by the master, sending the
|
||||
# DEL commands to the slave as keys evict in the master side.
|
||||
#
|
||||
# This behavior ensures that masters and slaves stay consistent, and is usually
|
||||
# what you want, however if your slave is writable, or you want the slave to have
|
||||
# a different memory setting, and you are sure all the writes performed to the
|
||||
# slave are idempotent, then you may change this default (but be sure to understand
|
||||
# what you are doing).
|
||||
#
|
||||
# Note that since the slave by default does not evict, it may end using more
|
||||
# memory than the one set via maxmemory (there are certain buffers that may
|
||||
# be larger on the slave, or data structures may sometimes take more memory and so
|
||||
# forth). So make sure you monitor your slaves and make sure they have enough
|
||||
# memory to never hit a real out-of-memory condition before the master hits
|
||||
# the configured maxmemory setting.
|
||||
#
|
||||
# slave-ingore-maxmemory yes
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@@ -659,7 +639,7 @@ slave-priority 100
|
||||
# it with the specified string.
|
||||
# 4) During replication, when a slave performs a full resynchronization with
|
||||
# its master, the content of the whole database is removed in order to
|
||||
# load the RDB file just transferred.
|
||||
# load the RDB file just transfered.
|
||||
#
|
||||
# In all the above cases the default is to delete objects in a blocking way,
|
||||
# like if DEL was called. However you can configure each case specifically
|
||||
@@ -1225,34 +1205,12 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
# Normally it is useful to have an HZ value which is proportional to the
|
||||
# number of clients connected. This is useful in order, for instance, to
|
||||
# avoid too many clients are processed for each background task invocation
|
||||
# in order to avoid latency spikes.
|
||||
#
|
||||
# Since the default HZ value by default is conservatively set to 10, Redis
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
#
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# as a baseline, but multiples of the configured HZ value will be actually
|
||||
# used as needed once more clients are connected. In this way an idle
|
||||
# instance will use very little CPU time while a busy instance will be
|
||||
# more responsive.
|
||||
dynamic-hz yes
|
||||
|
||||
# When a child rewrites the AOF 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.
|
||||
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
|
||||
|
||||
@@ -11,4 +11,4 @@ then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/test_helper.tcl "${@}"
|
||||
$TCLSH tests/test_helper.tcl $*
|
||||
|
||||
@@ -20,21 +20,6 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
|
||||
# daemonized.
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis Sentinel writes a pid file in
|
||||
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
|
||||
# location here.
|
||||
pidfile /var/run/redis-sentinel.pid
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Sentinel to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
@@ -209,31 +194,3 @@ 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.
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
|
||||
@@ -433,7 +433,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
* before replying to a client. */
|
||||
int invert = fe->mask & AE_BARRIER;
|
||||
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
* processed event removed an element that fired and we still
|
||||
* didn't processed, so we check if the event is still valid.
|
||||
*
|
||||
@@ -485,7 +485,7 @@ int aeWait(int fd, int mask, long long milliseconds) {
|
||||
if ((retval = poll(&pfd, 1, milliseconds))== 1) {
|
||||
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
|
||||
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
|
||||
return retmask;
|
||||
} else {
|
||||
|
||||
@@ -228,7 +228,7 @@ static void killAppendOnlyChild(void) {
|
||||
void stopAppendOnly(void) {
|
||||
serverAssert(server.aof_state != AOF_OFF);
|
||||
flushAppendOnlyFile(1);
|
||||
redis_fsync(server.aof_fd);
|
||||
aof_fsync(server.aof_fd);
|
||||
close(server.aof_fd);
|
||||
|
||||
server.aof_fd = -1;
|
||||
@@ -261,7 +261,7 @@ int startAppendOnly(void) {
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
* start a new one: the old one cannot be reused becuase it is not
|
||||
* accumulating the AOF buffer. */
|
||||
if (server.aof_child_pid != -1) {
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||
@@ -476,10 +476,10 @@ void flushAppendOnlyFile(int force) {
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* redis_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
/* aof_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
* flushing metadata. */
|
||||
latencyStartMonitor(latency);
|
||||
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
aof_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;
|
||||
@@ -645,7 +645,7 @@ struct client *createFakeClient(void) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -683,7 +683,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Handle a zero-length AOF file as a special case. An empty AOF file
|
||||
/* Handle a zero-length AOF file as a special case. An emtpy AOF file
|
||||
* is a valid AOF because an empty server with AOF enabled will create
|
||||
* a zero length file at startup, that will remain like that if no write
|
||||
* operation is received. */
|
||||
@@ -798,9 +798,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, handle it as it was
|
||||
* a short read, even if technically the protocol is correct: we want
|
||||
* to remove the unprocessed tail and continue. */
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
@@ -1223,6 +1221,7 @@ 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++) {
|
||||
@@ -1248,6 +1247,9 @@ 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 */
|
||||
@@ -1320,7 +1322,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
rioInitWithFile(&aof,fp);
|
||||
|
||||
if (server.aof_rewrite_incremental_fsync)
|
||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||
rioSetAutoSync(&aof,AOF_AUTOSYNC_BYTES);
|
||||
|
||||
if (server.aof_use_rdb_preamble) {
|
||||
int error;
|
||||
@@ -1688,7 +1690,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
oldfd = server.aof_fd;
|
||||
server.aof_fd = newfd;
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
redis_fsync(newfd);
|
||||
aof_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 */
|
||||
@@ -1715,7 +1717,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
* tirggering an error conditon. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* pthread_mutex_t myvar_mutex;
|
||||
* atomicSet(myvar,12345);
|
||||
*
|
||||
* If atomic primitives are available (tested in config.h) the mutex
|
||||
* If atomic primitives are availble (tested in config.h) the mutex
|
||||
* is not used.
|
||||
*
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
|
||||
@@ -187,7 +187,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
if (type == BIO_CLOSE_FILE) {
|
||||
close((long)job->arg1);
|
||||
} else if (type == BIO_AOF_FSYNC) {
|
||||
redis_fsync((long)job->arg1);
|
||||
aof_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.
|
||||
|
||||
+4
-4
@@ -918,7 +918,7 @@ void bitfieldCommand(client *c) {
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
size_t highest_write_offset = 0;
|
||||
size_t higest_write_offset = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
int remargs = c->argc-j-1; /* Remaining args other than current. */
|
||||
@@ -968,8 +968,8 @@ void bitfieldCommand(client *c) {
|
||||
|
||||
if (opcode != BITFIELDOP_GET) {
|
||||
readonly = 0;
|
||||
if (highest_write_offset < bitoffset + bits - 1)
|
||||
highest_write_offset = bitoffset + bits - 1;
|
||||
if (higest_write_offset < bitoffset + bits - 1)
|
||||
higest_write_offset = bitoffset + bits - 1;
|
||||
/* INCRBY and SET require another argument. */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){
|
||||
zfree(ops);
|
||||
@@ -999,7 +999,7 @@ void bitfieldCommand(client *c) {
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
highest_write_offset)) == NULL) return;
|
||||
higest_write_offset)) == NULL) return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
|
||||
+21
-35
@@ -269,7 +269,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
@@ -285,7 +285,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
@@ -370,46 +370,32 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyError(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
if (s->last_id.ms > gt->ms ||
|
||||
(s->last_id.ms == gt->ms &&
|
||||
s->last_id.seq > gt->seq))
|
||||
{
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and
|
||||
* consumer here. */
|
||||
streamCG *group = NULL;
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* In theory if the group is not found we
|
||||
* just perform the read without the group,
|
||||
* but actually when the group, or the key
|
||||
* itself is deleted (triggering the removal
|
||||
* of the group), we check for blocked clients
|
||||
* and send them an error. */
|
||||
}
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
@@ -426,7 +412,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
0, group, consumer, 0, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
|
||||
+35
-83
@@ -3100,7 +3100,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
(unsigned long long) myself->configEpoch);
|
||||
}
|
||||
|
||||
/* Take responsibility for the cluster slots. */
|
||||
/* Take responsability for the cluster slots. */
|
||||
clusterFailoverReplaceYourMaster();
|
||||
} else {
|
||||
clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES);
|
||||
@@ -3151,11 +3151,11 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
!nodeTimedOut(mymaster->slaves[j])) okslaves++;
|
||||
if (okslaves <= server.cluster_migration_barrier) return;
|
||||
|
||||
/* Step 3: Identify a candidate for migration, and check if among the
|
||||
/* Step 3: Idenitfy a candidate for migration, and check if among the
|
||||
* masters with the greatest number of ok slaves, I'm the one with the
|
||||
* smallest node ID (the "candidate slave").
|
||||
*
|
||||
* Note: this means that eventually a replica migration will occur
|
||||
* Note: this means that eventually a replica migration will occurr
|
||||
* since slaves that are reachable again always have their FAIL flag
|
||||
* cleared, so eventually there must be a candidate. At the same time
|
||||
* this does not mean that there are no race conditions possible (two
|
||||
@@ -3321,18 +3321,14 @@ void clusterCron(void) {
|
||||
int changed = 0;
|
||||
|
||||
if (prev_ip == NULL && curr_ip != NULL) changed = 1;
|
||||
else if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
|
||||
if (changed) {
|
||||
if (prev_ip) zfree(prev_ip);
|
||||
prev_ip = curr_ip;
|
||||
if (prev_ip) prev_ip = zstrdup(prev_ip);
|
||||
|
||||
if (curr_ip) {
|
||||
/* We always take a copy of the previous IP address, by
|
||||
* duplicating the string. This way later we can check if
|
||||
* the address really changed. */
|
||||
prev_ip = zstrdup(prev_ip);
|
||||
strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN);
|
||||
myself->ip[NET_IP_STR_LEN-1] = '\0';
|
||||
} else {
|
||||
@@ -3740,7 +3736,7 @@ void clusterCloseAllSlots(void) {
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* The following are defines that are only used in the evaluation function
|
||||
* and are based on heuristics. Actually the main point about the rejoin and
|
||||
* and are based on heuristics. Actaully the main point about the rejoin and
|
||||
* writable delay is that they should be a few orders of magnitude larger
|
||||
* than the network latency. */
|
||||
#define CLUSTER_MAX_REJOIN_DELAY 5000
|
||||
@@ -4182,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
|
||||
};
|
||||
@@ -4750,7 +4746,8 @@ NULL
|
||||
clusterReset(hard);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLUSTER HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4838,39 +4835,15 @@ void dumpCommand(client *c) {
|
||||
|
||||
/* RESTORE key ttl serialized-value [REPLACE] */
|
||||
void restoreCommand(client *c) {
|
||||
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
|
||||
long long ttl;
|
||||
rio payload;
|
||||
int j, type, replace = 0, absttl = 0;
|
||||
int j, type, replace = 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;
|
||||
@@ -4911,11 +4884,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
if (ttl) setExpire(c,c->db,c->argv[1],mstime()+ttl);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
@@ -5150,11 +5119,6 @@ try_again:
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
|
||||
}
|
||||
|
||||
int expired = 0; /* Number of keys that we'll find already expired.
|
||||
Note that serializing large keys may take some time
|
||||
so certain keys that were found non expired by the
|
||||
lookupKey() function, may be expired later. */
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
long long ttl = 0;
|
||||
@@ -5162,10 +5126,6 @@ try_again:
|
||||
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 0) {
|
||||
expired++;
|
||||
continue;
|
||||
}
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
serverAssertWithInfo(c,NULL,
|
||||
@@ -5230,13 +5190,9 @@ try_again:
|
||||
int socket_error = 0;
|
||||
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
|
||||
|
||||
/* Allocate the new argument vector that will replace the current command,
|
||||
* to propagate the MIGRATE as a DEL command (if no COPY option was given).
|
||||
* We allocate num_keys+1 because the additional argument is for "DEL"
|
||||
* command name itself. */
|
||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||
|
||||
for (j = 0; j < num_keys-expired; j++) {
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
|
||||
socket_error = 1;
|
||||
break;
|
||||
@@ -5601,7 +5557,7 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
||||
/* The request spawns multiple keys in the same slot,
|
||||
/* The request spawns mutliple keys in the same slot,
|
||||
* but the slot is not "stable" currently as there is
|
||||
* a migration or import in progress. */
|
||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
||||
@@ -5633,11 +5589,7 @@ 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 ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM))
|
||||
{
|
||||
if (c->flags & CLIENT_BLOCKED && c->btype == BLOCKED_LIST) {
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
|
||||
|
||||
+1
-1
@@ -243,7 +243,7 @@ union clusterMsgData {
|
||||
#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* TCP base port number. */
|
||||
|
||||
+44
-78
@@ -390,7 +390,7 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
} 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;
|
||||
@@ -399,10 +399,6 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slave-ignore-maxmemory") && argc == 2) {
|
||||
if ((server.repl_slave_ignore_maxmemory = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
|
||||
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -435,23 +431,14 @@ 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;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"dynamic-hz") && argc == 2) {
|
||||
if ((server.dynamic_hz = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
|
||||
server.config_hz = atoi(argv[1]);
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
|
||||
server.hz = atoi(argv[1]);
|
||||
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
|
||||
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
|
||||
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
|
||||
int yes;
|
||||
|
||||
@@ -496,13 +483,6 @@ 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;
|
||||
@@ -516,7 +496,7 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||
goto loaderr;
|
||||
}
|
||||
server.requirepass = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
server.requirepass = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
|
||||
zfree(server.pidfile);
|
||||
server.pidfile = zstrdup(argv[1]);
|
||||
@@ -1041,8 +1021,6 @@ 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(
|
||||
@@ -1051,8 +1029,6 @@ void configSetCommand(client *c) {
|
||||
"slave-serve-stale-data",server.repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"slave-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"slave-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",server.activerehashing) {
|
||||
} config_set_bool_field(
|
||||
@@ -1082,21 +1058,19 @@ void configSetCommand(client *c) {
|
||||
"slave-lazy-flush",server.repl_slave_lazy_flush) {
|
||||
} config_set_bool_field(
|
||||
"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
|
||||
} config_set_bool_field(
|
||||
"dynamic-hz",server.dynamic_hz) {
|
||||
|
||||
/* Numerical fields.
|
||||
* config_set_numerical_field(name,var,min,max) */
|
||||
} config_set_numerical_field(
|
||||
"tcp-keepalive",server.tcpkeepalive,0,INT_MAX) {
|
||||
"tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"maxmemory-samples",server.maxmemory_samples,1,INT_MAX) {
|
||||
"maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lfu-log-factor",server.lfu_log_factor,0,INT_MAX) {
|
||||
"lfu-log-factor",server.lfu_log_factor,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lfu-decay-time",server.lfu_decay_time,0,INT_MAX) {
|
||||
"lfu-decay-time",server.lfu_decay_time,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"timeout",server.maxidletime,0,INT_MAX) {
|
||||
"timeout",server.maxidletime,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-threshold-lower",server.active_defrag_threshold_lower,0,1000) {
|
||||
} config_set_numerical_field(
|
||||
@@ -1108,15 +1082,15 @@ 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,LONG_MAX) {
|
||||
"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,INT_MAX){
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LONG_MAX) {
|
||||
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LONG_MAX) {
|
||||
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"stream-node-max-bytes",server.stream_node_max_bytes,0,LONG_MAX) {
|
||||
"stream-node-max-bytes",server.stream_node_max_bytes,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"stream-node-max-entries",server.stream_node_max_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
@@ -1124,40 +1098,40 @@ void configSetCommand(client *c) {
|
||||
} 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,LONG_MAX) {
|
||||
"set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LONG_MAX) {
|
||||
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LONG_MAX) {
|
||||
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LONG_MAX) {
|
||||
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lua-time-limit",server.lua_time_limit,0,LONG_MAX) {
|
||||
"lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,-1,LLONG_MAX) {
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-max-len",ll,0,LONG_MAX) {
|
||||
"slowlog-max-len",ll,0,LLONG_MAX) {
|
||||
/* Cast to unsigned. */
|
||||
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,INT_MAX) {
|
||||
"repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-timeout",server.repl_timeout,1,INT_MAX) {
|
||||
"repl-timeout",server.repl_timeout,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LONG_MAX) {
|
||||
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) {
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-priority",server.slave_priority,0,INT_MAX) {
|
||||
"slave-priority",server.slave_priority,0,LLONG_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,INT_MAX) {
|
||||
"min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
|
||||
@@ -1166,17 +1140,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,INT_MAX){
|
||||
"cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hz",server.config_hz,0,INT_MAX) {
|
||||
"hz",server.hz,0,LLONG_MAX) {
|
||||
/* Hz is more an hint from the user, so we accept values out of range
|
||||
* but cap them to reasonable values. */
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
|
||||
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,INT_MAX) {
|
||||
"watchdog-period",ll,0,LLONG_MAX) {
|
||||
if (ll)
|
||||
enableWatchdog(ll);
|
||||
else
|
||||
@@ -1187,7 +1161,7 @@ void configSetCommand(client *c) {
|
||||
} config_set_memory_field("maxmemory",server.maxmemory) {
|
||||
if (server.maxmemory) {
|
||||
if (server.maxmemory < zmalloc_used_memory()) {
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
@@ -1341,7 +1315,7 @@ void configGetCommand(client *c) {
|
||||
config_get_numerical_field("slave-announce-port",server.slave_announce_port);
|
||||
config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
|
||||
config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
|
||||
config_get_numerical_field("hz",server.config_hz);
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
@@ -1359,8 +1333,6 @@ void configGetCommand(client *c) {
|
||||
server.repl_serve_stale_data);
|
||||
config_get_bool_field("slave-read-only",
|
||||
server.repl_slave_ro);
|
||||
config_get_bool_field("slave-ignore-maxmemory",
|
||||
server.repl_slave_ignore_maxmemory);
|
||||
config_get_bool_field("stop-writes-on-bgsave-error",
|
||||
server.stop_writes_on_bgsave_err);
|
||||
config_get_bool_field("daemonize", server.daemonize);
|
||||
@@ -1375,8 +1347,6 @@ 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",
|
||||
@@ -1389,8 +1359,6 @@ void configGetCommand(client *c) {
|
||||
server.lazyfree_lazy_server_del);
|
||||
config_get_bool_field("slave-lazy-flush",
|
||||
server.repl_slave_lazy_flush);
|
||||
config_get_bool_field("dynamic-hz",
|
||||
server.dynamic_hz);
|
||||
|
||||
/* Enum values */
|
||||
config_get_enum_field("maxmemory-policy",
|
||||
@@ -2057,7 +2025,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
|
||||
rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
|
||||
rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
|
||||
rewriteConfigYesNoOption(state,"slave-ignore-maxmemory",server.repl_slave_ignore_maxmemory,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY);
|
||||
rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
|
||||
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
|
||||
@@ -2115,9 +2082,8 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
|
||||
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
|
||||
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);
|
||||
@@ -2125,7 +2091,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
|
||||
rewriteConfigYesNoOption(state,"slave-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
|
||||
rewriteConfigYesNoOption(state,"dynamic-hz",server.dynamic_hz,CONFIG_DEFAULT_DYNAMIC_HZ);
|
||||
|
||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
@@ -2158,10 +2123,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);
|
||||
@@ -2186,7 +2151,8 @@ NULL
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CONFIG HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -87,11 +87,11 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Define redis_fsync to fdatasync() in Linux and fsync() for all the rest */
|
||||
/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */
|
||||
#ifdef __linux__
|
||||
#define redis_fsync fdatasync
|
||||
#define aof_fsync fdatasync
|
||||
#else
|
||||
#define redis_fsync fsync
|
||||
#define aof_fsync fsync
|
||||
#endif
|
||||
|
||||
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
|
||||
|
||||
@@ -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 if the key is logically expired
|
||||
* Note: this function also returns NULL is 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
|
||||
@@ -113,7 +113,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
* safety measure, the command invoked is a read-only command, we can
|
||||
* safely return NULL here, and provide a more consistent behavior
|
||||
* to clients accessign expired values in a read-only fashion, that
|
||||
* will say the key as non existing.
|
||||
* will say the key as non exisitng.
|
||||
*
|
||||
* Notably this covers GETs when slaves are used to scale reads. */
|
||||
if (server.current_client &&
|
||||
@@ -184,19 +184,14 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
dictEntry auxentry = *de;
|
||||
robj *old = dictGetVal(de);
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
val->lru = old->lru;
|
||||
robj *old = dictGetVal(de);
|
||||
int saved_lru = old->lru;
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = saved_lru;
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
}
|
||||
dictSetVal(db->dict, de, val);
|
||||
|
||||
if (server.lazyfree_lazy_server_del) {
|
||||
freeObjAsync(old);
|
||||
dictSetVal(db->dict, &auxentry, NULL);
|
||||
}
|
||||
|
||||
dictFreeVal(db->dict, &auxentry);
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
@@ -228,8 +223,6 @@ 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;
|
||||
@@ -241,17 +234,6 @@ 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. */
|
||||
@@ -323,7 +305,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* If callback is given the function is called from time to time to
|
||||
* signal that work is in progress.
|
||||
*
|
||||
* The dbnum can be -1 if all the DBs should be flushed, or the specified
|
||||
* The dbnum can be -1 if all teh DBs should be flushed, or the specified
|
||||
* DB number if we want to flush only a single Redis database number.
|
||||
*
|
||||
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
|
||||
@@ -334,7 +316,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
int j, async = (flags & EMPTYDB_ASYNC);
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= server.dbnum) {
|
||||
@@ -342,15 +324,8 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int startdb, enddb;
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
enddb = server.dbnum-1;
|
||||
} else {
|
||||
startdb = enddb = dbnum;
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
if (dbnum != -1 && dbnum != j) continue;
|
||||
removed += dictSize(server.db[j].dict);
|
||||
if (async) {
|
||||
emptyDbAsync(&server.db[j]);
|
||||
@@ -492,7 +467,8 @@ void existsCommand(client *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (lookupKeyRead(c->db,c->argv[j])) count++;
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbExists(c->db,c->argv[j])) count++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
@@ -1197,7 +1173,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
* of arguments (negative arity parameter) do not have dispatch
|
||||
* of arugments (negative arity parameter) do not have dispatch
|
||||
* time arity checks, so we need to handle the case where the user
|
||||
* passed an invalid number of arguments here. In this case we
|
||||
* return no keys and expect the command implementation to report
|
||||
@@ -1252,7 +1228,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 < 1 || num > (argc-3)) {
|
||||
if (num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
@@ -1281,7 +1257,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 <= 0 || num > (argc-3)) {
|
||||
if (num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
@@ -1410,37 +1386,23 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
}
|
||||
|
||||
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
|
||||
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
|
||||
* [RETRY <milliseconds> <ttl>] 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 = 0, *keys;
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* 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. */
|
||||
/* We need to seek the last argument that contains "STREAMS", because other
|
||||
* arguments before may contain it (for example the group name). */
|
||||
int streams_pos = -1;
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i]->ptr;
|
||||
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 (!strcasecmp(arg, "streams")) streams_pos = i;
|
||||
}
|
||||
if (streams_pos != -1) num = argc - streams_pos - 1;
|
||||
|
||||
/* Syntax error. */
|
||||
if (streams_pos == -1 || num == 0 || num % 2 != 0) {
|
||||
if (streams_pos == -1 || num % 2 != 0) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
@@ -1448,7 +1410,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-num; i++) keys[i-streams_pos-1] = i;
|
||||
for (i = streams_pos+1; i < argc; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
+24
-27
@@ -285,27 +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-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.",
|
||||
"LOG <message> -- write message to the server log.",
|
||||
"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.",
|
||||
"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 -- Outputs an hex signature representing the current DB content.",
|
||||
"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.",
|
||||
"error <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -334,9 +333,6 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
|
||||
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
serverAssertWithInfo(c,c->argv[0],1 == 2);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
@@ -352,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_OFF) flushAppendOnlyFile(1);
|
||||
if (server.aof_state == AOF_ON) flushAppendOnlyFile(1);
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
if (loadAppendOnlyFile(server.aof_filename) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
@@ -586,7 +582,8 @@ NULL
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try DEBUG HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1080,7 +1077,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(-1);
|
||||
clients = getAllClientsInfoString();
|
||||
serverLogRaw(LL_WARNING|LL_RAW, clients);
|
||||
sdsfree(infostring);
|
||||
sdsfree(clients);
|
||||
|
||||
+21
-194
@@ -592,171 +592,6 @@ 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. */
|
||||
@@ -825,8 +660,6 @@ 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. */
|
||||
@@ -847,7 +680,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
/* Defrag scan callback for 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 */
|
||||
@@ -867,8 +700,9 @@ void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t resident, active, allocated;
|
||||
zmalloc_get_allocator_info(&allocated, &active, &resident);
|
||||
size_t resident = server.cron_malloc_stats.allocator_resident;
|
||||
size_t active = server.cron_malloc_stats.allocator_active;
|
||||
size_t allocated = server.cron_malloc_stats.allocator_allocated;
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
@@ -894,29 +728,27 @@ long defragOtherGlobals() {
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
unsigned long defragLaterItem(dictEntry *de, unsigned long cursor) {
|
||||
long defragged = 0;
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
defragged += scanLaterList(ob);
|
||||
cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
defragged += scanLaterSet(ob, &cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
server.stat_active_defrag_hits += scanLaterZset(ob, cursor);
|
||||
defragged += scanLaterZset(ob, &cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
server.stat_active_defrag_hits += scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStraemListpacks(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
defragged += scanLaterHash(ob, &cursor);
|
||||
} 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 */
|
||||
}
|
||||
return 0;
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
@@ -956,22 +788,17 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
dictEntry *de = dictFind(db->dict, current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
do {
|
||||
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;
|
||||
cursor = defragLaterItem(de, cursor);
|
||||
|
||||
/* 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. */
|
||||
if (quit || (++iterations > 16 ||
|
||||
* 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 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
if (quit || ustime() > endtime) {
|
||||
if (!cursor || ustime() > endtime) {
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
|
||||
+3
-5
@@ -327,7 +327,7 @@ int dictReplace(dict *d, void *key, void *val)
|
||||
dictEntry *entry, *existing, auxentry;
|
||||
|
||||
/* Try to add the element. If the key
|
||||
* does not exists dictAdd will succeed. */
|
||||
* does not exists dictAdd will suceed. */
|
||||
entry = dictAddRaw(d,key,&existing);
|
||||
if (entry) {
|
||||
dictSetVal(d, entry, val);
|
||||
@@ -705,10 +705,8 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
* table, there will be no elements in both tables up to
|
||||
* the current rehashing index, so we jump if possible.
|
||||
* (this happens when going from big to small table). */
|
||||
if (i >= d->ht[1].size)
|
||||
i = d->rehashidx;
|
||||
else
|
||||
continue;
|
||||
if (i >= d->ht[1].size) i = d->rehashidx;
|
||||
continue;
|
||||
}
|
||||
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
|
||||
+4
-4
@@ -43,12 +43,12 @@ uint16_t intrev16(uint16_t v);
|
||||
uint32_t intrev32(uint32_t v);
|
||||
uint64_t intrev64(uint64_t v);
|
||||
|
||||
/* variants of the function doing the actual conversion only if the target
|
||||
/* variants of the function doing the actual convertion only if the target
|
||||
* host is big endian */
|
||||
#if (BYTE_ORDER == LITTLE_ENDIAN)
|
||||
#define memrev16ifbe(p) ((void)(0))
|
||||
#define memrev32ifbe(p) ((void)(0))
|
||||
#define memrev64ifbe(p) ((void)(0))
|
||||
#define memrev16ifbe(p)
|
||||
#define memrev32ifbe(p)
|
||||
#define memrev64ifbe(p)
|
||||
#define intrev16ifbe(v) (v)
|
||||
#define intrev32ifbe(v) (v)
|
||||
#define intrev64ifbe(v) (v)
|
||||
|
||||
@@ -444,10 +444,6 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
/* By default slaves should ignore maxmemory and just be masters excat
|
||||
* copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
|
||||
+1
-1
@@ -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 limit. Also don't repeat a fast cycle for the same period
|
||||
* for time limt. 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;
|
||||
|
||||
@@ -145,7 +145,7 @@ double extractUnitOrReply(client *c, robj *unit) {
|
||||
/* Input Argument Helper.
|
||||
* Extract the dinstance from the specified two arguments starting at 'argv'
|
||||
* that shouldbe in the form: <number> <unit> and return the dinstance in the
|
||||
* specified unit on success. *conversions is populated with the coefficient
|
||||
* specified unit on success. *conversino is populated with the coefficient
|
||||
* to use in order to convert meters to the unit.
|
||||
*
|
||||
* On error a value less than zero is returned. */
|
||||
|
||||
+2
-2
@@ -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 *= (1ULL << step);
|
||||
long_offset *= (1ULL << step);
|
||||
lat_offset *= (1 << step);
|
||||
long_offset *= (1 << step);
|
||||
hash->bits = interleave64(lat_offset, long_offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -975,7 +975,7 @@ struct commandHelp {
|
||||
"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.",
|
||||
"Return information and entries from a stream conusmer group pending entries list, that are messages fetched but never acknowledged.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XRANGE",
|
||||
|
||||
+3
-3
@@ -673,7 +673,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
end = p + sdslen(o->ptr) - HLL_HDR_SIZE;
|
||||
|
||||
first = 0;
|
||||
prev = NULL; /* Points to previous opcode at the end of the loop. */
|
||||
prev = NULL; /* Points to previos opcode at the end of the loop. */
|
||||
next = NULL; /* Points to the next opcode at the end of the loop. */
|
||||
span = 0;
|
||||
while(p < end) {
|
||||
@@ -764,7 +764,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
* and is either currently represented by a VAL opcode with len > 1,
|
||||
* by a ZERO opcode with len > 1, or by an XZERO opcode.
|
||||
*
|
||||
* In those cases the original opcode must be split into multiple
|
||||
* In those cases the original opcode must be split into muliple
|
||||
* opcodes. The worst case is an XZERO split in the middle resuling into
|
||||
* XZERO - VAL - XZERO, so the resulting sequence max length is
|
||||
* 5 bytes.
|
||||
@@ -887,7 +887,7 @@ promote: /* Promote to dense representation. */
|
||||
*
|
||||
* Note that this in turn means that PFADD will make sure the command
|
||||
* is propagated to slaves / AOF, so if there is a sparse -> dense
|
||||
* conversion, it will be performed in all the slaves as well. */
|
||||
* convertion, it will be performed in all the slaves as well. */
|
||||
int dense_retval = hllDenseSet(hdr->registers,index,count);
|
||||
serverAssert(dense_retval == 1);
|
||||
return dense_retval;
|
||||
|
||||
+2
-2
@@ -152,7 +152,7 @@ int latencyResetEvent(char *event_to_reset) {
|
||||
|
||||
/* ------------------------ Latency reporting (doctor) ---------------------- */
|
||||
|
||||
/* Analyze the samples available for a given event and return a structure
|
||||
/* Analyze the samples avaialble for a given event and return a structure
|
||||
* populate with different metrics, average, MAD, min, max, and so forth.
|
||||
* Check latency.h definition of struct latenctStat for more info.
|
||||
* If the specified event has no elements the structure is populate with
|
||||
@@ -294,7 +294,7 @@ sds createLatencyReport(void) {
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than < 0) {
|
||||
if (server.slowlog_log_slower_than == 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
|
||||
+2
-13
@@ -23,10 +23,10 @@ size_t lazyfreeGetPendingObjectsCount(void) {
|
||||
* the function just returns the number of elements the object is composed of.
|
||||
*
|
||||
* Objects composed of single allocations are always reported as having a
|
||||
* single item even if they are actually logical composed of multiple
|
||||
* single item even if they are actaully logical composed of multiple
|
||||
* elements.
|
||||
*
|
||||
* For lists the function returns the number of elements in the quicklist
|
||||
* For lists the funciton returns the number of elements in the quicklist
|
||||
* representing the list. */
|
||||
size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
if (obj->type == OBJ_LIST) {
|
||||
@@ -90,17 +90,6 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Free an object, if the object is huge enough, free it in async way. */
|
||||
void freeObjAsync(robj *o) {
|
||||
size_t free_effort = lazyfreeGetFreeEffort(o);
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->refcount == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty a Redis DB asynchronously. What the function does actually is to
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
|
||||
+3
-3
@@ -291,7 +291,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
/* Store a reverse-encoded variable length field, representing the length
|
||||
* of the previous element of size 'l', in the target buffer 'buf'.
|
||||
* The function returns the number of bytes used to encode it, from
|
||||
* 1 to 5. If 'buf' is NULL the function just returns the number of bytes
|
||||
* 1 to 5. If 'buf' is NULL the funciton just returns the number of bytes
|
||||
* needed in order to encode the backlen. */
|
||||
unsigned long lpEncodeBacklen(unsigned char *buf, uint64_t l) {
|
||||
if (l <= 127) {
|
||||
@@ -568,7 +568,7 @@ unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert, delete or replace the specified element 'ele' of length 'len' at
|
||||
/* Insert, delete or replace the specified element 'ele' of lenght 'len' at
|
||||
* the specified position 'p', with 'p' being a listpack element pointer
|
||||
* obtained with lpFirst(), lpLast(), lpIndex(), lpNext(), lpPrev() or
|
||||
* lpSeek().
|
||||
@@ -710,7 +710,7 @@ unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, un
|
||||
return lp;
|
||||
}
|
||||
|
||||
/* Append the specified element 'ele' of length 'len' at the end of the
|
||||
/* Append the specified element 'ele' of lenght 'len' at the end of the
|
||||
* listpack. It is implemented in terms of lpInsert(), so the return value is
|
||||
* the same as lpInsert(). */
|
||||
unsigned char *lpAppend(unsigned char *lp, unsigned char *ele, uint32_t size) {
|
||||
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/* This is a safe version of localtime() which contains no locks and is
|
||||
* fork() friendly. Even the _r version of localtime() cannot be used safely
|
||||
* in Redis. Another thread may be calling localtime() while the main thread
|
||||
* forks(). Later when the child process calls localtime() again, for instance
|
||||
* in order to log something to the Redis log, it may deadlock: in the copy
|
||||
* of the address space of the forked process the lock will never be released.
|
||||
*
|
||||
* This function takes the timezone 'tz' as argument, and the 'dst' flag is
|
||||
* used to check if daylight saving time is currently in effect. The caller
|
||||
* of this function should obtain such information calling tzset() ASAP in the
|
||||
* main() function to obtain the timezone offset from the 'timezone' global
|
||||
* variable. To obtain the daylight information, if it is currently active or not,
|
||||
* one trick is to call localtime() in main() ASAP as well, and get the
|
||||
* information from the tm_isdst field of the tm structure. However the daylight
|
||||
* time may switch in the future for long running processes, so this information
|
||||
* should be refreshed at safe times.
|
||||
*
|
||||
* Note that this function does not work for dates < 1/1/1970, it is solely
|
||||
* designed to work with what time(NULL) may return, and to support Redis
|
||||
* logging of the dates, it's not really a complete implementation. */
|
||||
static int is_leap_year(time_t year) {
|
||||
if (year % 4) return 0; /* A year not divisible by 4 is not leap. */
|
||||
else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */
|
||||
else if (year % 400) return 0; /* If div by 100 *and* 400 is not leap. */
|
||||
else return 1; /* If div by 100 and not by 400 is leap. */
|
||||
}
|
||||
|
||||
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
|
||||
const time_t secs_min = 60;
|
||||
const time_t secs_hour = 3600;
|
||||
const time_t secs_day = 3600*24;
|
||||
|
||||
t -= tz; /* Adjust for timezone. */
|
||||
t += 3600*dst; /* Adjust for daylight time. */
|
||||
time_t days = t / secs_day; /* Days passed since epoch. */
|
||||
time_t seconds = t % secs_day; /* Remaining seconds. */
|
||||
|
||||
tmp->tm_isdst = dst;
|
||||
tmp->tm_hour = seconds / secs_hour;
|
||||
tmp->tm_min = (seconds % secs_hour) / secs_min;
|
||||
tmp->tm_sec = (seconds % secs_hour) % secs_min;
|
||||
|
||||
/* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure
|
||||
* where sunday = 0, so to calculate the day of the week we have to add 4
|
||||
* and take the modulo by 7. */
|
||||
tmp->tm_wday = (days+4)%7;
|
||||
|
||||
/* Calculate the current year. */
|
||||
tmp->tm_year = 1970;
|
||||
while(1) {
|
||||
/* Leap years have one day more. */
|
||||
time_t days_this_year = 365 + is_leap_year(tmp->tm_year);
|
||||
if (days_this_year > days) break;
|
||||
days -= days_this_year;
|
||||
tmp->tm_year++;
|
||||
}
|
||||
tmp->tm_yday = days; /* Number of day of the current year. */
|
||||
|
||||
/* We need to calculate in which month and day of the month we are. To do
|
||||
* so we need to skip days according to how many days there are in each
|
||||
* month, and adjust for the leap year that has one more day in February. */
|
||||
int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
mdays[1] += is_leap_year(tmp->tm_year);
|
||||
|
||||
tmp->tm_mon = 0;
|
||||
while(days >= mdays[tmp->tm_mon]) {
|
||||
days -= mdays[tmp->tm_mon];
|
||||
tmp->tm_mon++;
|
||||
}
|
||||
|
||||
tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */
|
||||
tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */
|
||||
}
|
||||
|
||||
#ifdef LOCALTIME_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
/* Obtain timezone and daylight info. */
|
||||
tzset(); /* Now 'timezome' global is populated. */
|
||||
time_t t = time(NULL);
|
||||
struct tm *aux = localtime(&t);
|
||||
int daylight_active = aux->tm_isdst;
|
||||
|
||||
struct tm tm;
|
||||
char buf[1024];
|
||||
|
||||
nolocks_localtime(&tm,t,timezone,daylight_active);
|
||||
strftime(buf,sizeof(buf),"%d %b %H:%M:%S",&tm);
|
||||
printf("[timezone: %d, dl: %d] %s\n", (int)timezone, (int)daylight_active, buf);
|
||||
}
|
||||
#endif
|
||||
@@ -2,9 +2,6 @@
|
||||
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
||||
fi
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
|
||||
|
||||
+21
-33
@@ -548,7 +548,7 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
|
||||
ctx->keys_pos[ctx->keys_count++] = pos;
|
||||
}
|
||||
|
||||
/* Helper for RM_CreateCommand(). Turns a string representing command
|
||||
/* Helper for RM_CreateCommand(). Truns a string representing command
|
||||
* flags into the command flags used by the Redis core.
|
||||
*
|
||||
* It returns the set of flags, or -1 if unknown flags are found. */
|
||||
@@ -595,7 +595,7 @@ int commandFlagsFromString(char *s) {
|
||||
* And is supposed to always return REDISMODULE_OK.
|
||||
*
|
||||
* The set of flags 'strflags' specify the behavior of the command, and should
|
||||
* be passed as a C string composed of space separated words, like for
|
||||
* be passed as a C string compoesd of space separated words, like for
|
||||
* example "write deny-oom". The set of flags are:
|
||||
*
|
||||
* * **"write"**: The command may modify the data set (it may also read
|
||||
@@ -616,7 +616,7 @@ int commandFlagsFromString(char *s) {
|
||||
* * **"allow-stale"**: The command is allowed to run on slaves that don't
|
||||
* serve stale data. Don't use if you don't know what
|
||||
* this means.
|
||||
* * **"no-monitor"**: Don't propagate the command on monitor. Use this if
|
||||
* * **"no-monitor"**: Don't propoagate the command on monitor. Use this if
|
||||
* the command has sensible data among the arguments.
|
||||
* * **"fast"**: The command time complexity is not greater
|
||||
* than O(log(N)) where N is the size of the collection or
|
||||
@@ -956,9 +956,9 @@ RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
/* Append the specified buffer to the string 'str'. The string must be a
|
||||
/* Append the specified buffere to the string 'str'. The string must be a
|
||||
* string created by the user that is referenced only a single time, otherwise
|
||||
* REDISMODULE_ERR is returned and the operation is not performed. */
|
||||
* REDISMODULE_ERR is returend and the operation is not performed. */
|
||||
int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) {
|
||||
UNUSED(ctx);
|
||||
str = moduleAssertUnsharedString(str);
|
||||
@@ -1118,7 +1118,7 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
*
|
||||
* Note that in the above example there is no reason to postpone the array
|
||||
* length, since we produce a fixed number of elements, but in the practice
|
||||
* the code may use an iterator or other ways of creating the output so
|
||||
* the code may use an interator or other ways of creating the output so
|
||||
* that is not easy to calculate in advance the number of elements.
|
||||
*/
|
||||
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
@@ -1410,7 +1410,7 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) {
|
||||
* to call other APIs with the key handle as argument to perform
|
||||
* operations on the key.
|
||||
*
|
||||
* The return value is the handle representing the key, that must be
|
||||
* The return value is the handle repesenting the key, that must be
|
||||
* closed with RM_CloseKey().
|
||||
*
|
||||
* If the key does not exist and WRITE mode is requested, the handle
|
||||
@@ -1664,7 +1664,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
* Key API for List type
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Push an element into a list, on head or tail depending on 'where' argument.
|
||||
/* Push an element into a list, on head or tail depending on 'where' argumnet.
|
||||
* If the key pointer is about an empty key opened for writing, the key
|
||||
* is created. On error (key opened for read-only operations or of the wrong
|
||||
* type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */
|
||||
@@ -1769,7 +1769,7 @@ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *f
|
||||
* The input and output flags, and the return value, have the same exact
|
||||
* meaning, with the only difference that this function will return
|
||||
* REDISMODULE_ERR even when 'score' is a valid double number, but adding it
|
||||
* to the existing score results into a NaN (not a number) condition.
|
||||
* to the existing score resuts into a NaN (not a number) condition.
|
||||
*
|
||||
* This function has an additional field 'newscore', if not NULL is filled
|
||||
* with the new score of the element after the increment, if no error
|
||||
@@ -2239,9 +2239,6 @@ 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(),
|
||||
@@ -2712,9 +2709,9 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
sds proto = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
sds o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
proto = sdscatlen(proto,o->buf,o->used);
|
||||
proto = sdscatsds(proto,o);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto);
|
||||
@@ -3399,7 +3396,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 limit is not specified but is guaranteed to be more than
|
||||
* to emit, this limti 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, ...) {
|
||||
@@ -3793,11 +3790,11 @@ void moduleReleaseGIL(void) {
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Subscribe to keyspace notifications. This is a low-level version of the
|
||||
* keyspace-notifications API. A module can register callbacks to be notified
|
||||
* keyspace-notifications API. A module cand register callbacks to be notified
|
||||
* when keyspce events occur.
|
||||
*
|
||||
* Notification events are filtered by their type (string events, set events,
|
||||
* etc), and the subscriber callback receives only events that match a specific
|
||||
* etc), and the subsriber callback receives only events that match a specific
|
||||
* mask of event types.
|
||||
*
|
||||
* When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents
|
||||
@@ -3830,9 +3827,9 @@ void moduleReleaseGIL(void) {
|
||||
*
|
||||
* Notification callback gets executed with a redis context that can not be
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occurred as its selected db number.
|
||||
* occured as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable notifications in redis.conf for
|
||||
* Notice that it is not necessary to enable norifications in redis.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
@@ -3876,7 +3873,7 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
|
||||
ctx.client = moduleKeyspaceSubscribersClient;
|
||||
selectDb(ctx.client, dbid);
|
||||
|
||||
/* mark the handler as active to avoid reentrant loops.
|
||||
/* mark the handler as activer to avoid reentrant loops.
|
||||
* If the subscriber performs an action triggering itself,
|
||||
* it will not be notified about it. */
|
||||
sub->active = 1;
|
||||
@@ -3887,7 +3884,7 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
|
||||
}
|
||||
}
|
||||
|
||||
/* Unsubscribe any notification subscribers this module has upon unloading */
|
||||
/* Unsubscribe any notification subscirbers this module has upon unloading */
|
||||
void moduleUnsubscribeNotifications(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
@@ -4365,7 +4362,7 @@ void moduleInitModulesSystem(void) {
|
||||
* because the server must be fully initialized before loading modules.
|
||||
*
|
||||
* The function aborts the server on errors, since to start with missing
|
||||
* modules is not considered sane: clients may rely on the existence of
|
||||
* modules is not considered sane: clients may rely on the existance of
|
||||
* given commands, loading AOF also may need some modules to exist, and
|
||||
* if this instance is a slave, it must understand commands from master. */
|
||||
void moduleLoadFromQueue(void) {
|
||||
@@ -4502,15 +4499,7 @@ 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;
|
||||
@@ -4559,8 +4548,7 @@ NULL
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
addReply(c,shared.syntaxerr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# gendoc.rb -- Converts the top-comments inside module.c to modules API
|
||||
# reference documentation in markdown format.
|
||||
# reference documentaiton in markdown format.
|
||||
|
||||
# Convert the C comment to markdown
|
||||
def markdown(s)
|
||||
|
||||
+116
-246
@@ -33,7 +33,7 @@
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static void setProtocolError(const char *errstr, client *c);
|
||||
static void setProtocolError(const char *errstr, client *c, long pos);
|
||||
|
||||
/* Return the size consumed from the allocator, for the specified SDS string,
|
||||
* including internal fragmentation. This function is used in order to compute
|
||||
@@ -56,14 +56,11 @@ size_t getStringObjectSdsUsedMemory(robj *o) {
|
||||
|
||||
/* Client.reply list dup and free methods. */
|
||||
void *dupClientReplyValue(void *o) {
|
||||
clientReplyBlock *old = o;
|
||||
clientReplyBlock *buf = zmalloc(sizeof(clientReplyBlock) + old->size);
|
||||
memcpy(buf, o, sizeof(clientReplyBlock) + old->size);
|
||||
return buf;
|
||||
return sdsdup(o);
|
||||
}
|
||||
|
||||
void freeClientReplyValue(void *o) {
|
||||
zfree(o);
|
||||
sdsfree(o);
|
||||
}
|
||||
|
||||
int listMatchObjects(void *a, void *b) {
|
||||
@@ -78,8 +75,6 @@ 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) {
|
||||
@@ -110,7 +105,6 @@ client *createClient(int fd) {
|
||||
c->fd = fd;
|
||||
c->name = NULL;
|
||||
c->bufpos = 0;
|
||||
c->qb_pos = 0;
|
||||
c->querybuf = sdsempty();
|
||||
c->pending_querybuf = sdsempty();
|
||||
c->querybuf_peak = 0;
|
||||
@@ -144,7 +138,6 @@ client *createClient(int fd) {
|
||||
c->bpop.target = NULL;
|
||||
c->bpop.xread_group = NULL;
|
||||
c->bpop.xread_consumer = NULL;
|
||||
c->bpop.xread_group_noack = 0;
|
||||
c->bpop.numreplicas = 0;
|
||||
c->bpop.reploffset = 0;
|
||||
c->woff = 0;
|
||||
@@ -244,35 +237,25 @@ int _addReplyToBuffer(client *c, const char *s, size_t len) {
|
||||
void _addReplyStringToList(client *c, const char *s, size_t len) {
|
||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
|
||||
|
||||
listNode *ln = listLast(c->reply);
|
||||
clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
|
||||
if (listLength(c->reply) == 0) {
|
||||
sds node = sdsnewlen(s,len);
|
||||
listAddNodeTail(c->reply,node);
|
||||
c->reply_bytes += len;
|
||||
} else {
|
||||
listNode *ln = listLast(c->reply);
|
||||
sds tail = listNodeValue(ln);
|
||||
|
||||
/* Note that 'tail' may be NULL even if we have a tail node, becuase when
|
||||
* addDeferredMultiBulkLength() is used, it sets a dummy node to NULL just
|
||||
* fo fill it later, when the size of the bulk length is set. */
|
||||
|
||||
/* Append to tail string when possible. */
|
||||
if (tail) {
|
||||
/* Copy the part we can fit into the tail, and leave the rest for a
|
||||
* new node */
|
||||
size_t avail = tail->size - tail->used;
|
||||
size_t copy = avail >= len? len: avail;
|
||||
memcpy(tail->buf + tail->used, s, copy);
|
||||
tail->used += copy;
|
||||
s += copy;
|
||||
len -= copy;
|
||||
}
|
||||
if (len) {
|
||||
/* Create a new node, make sure it is allocated to at
|
||||
* least PROTO_REPLY_CHUNK_BYTES */
|
||||
size_t size = len < PROTO_REPLY_CHUNK_BYTES? PROTO_REPLY_CHUNK_BYTES: len;
|
||||
tail = zmalloc(size + sizeof(clientReplyBlock));
|
||||
/* take over the allocation's internal fragmentation */
|
||||
tail->size = zmalloc_usable(tail) - sizeof(clientReplyBlock);
|
||||
tail->used = len;
|
||||
memcpy(tail->buf, s, len);
|
||||
listAddNodeTail(c->reply, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
/* Append to this object when possible. If tail == NULL it was
|
||||
* set via addDeferredMultiBulkLength(). */
|
||||
if (tail && sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES) {
|
||||
tail = sdscatlen(tail,s,len);
|
||||
listNodeValue(ln) = tail;
|
||||
c->reply_bytes += len;
|
||||
} else {
|
||||
sds node = sdsnewlen(s,len);
|
||||
listAddNodeTail(c->reply,node);
|
||||
c->reply_bytes += len;
|
||||
}
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
@@ -343,30 +326,11 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
if (!len || s[0] != '-') addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
|
||||
/* Sometimes it could be normal that a slave replies to a master with
|
||||
* an error and this function gets called. Actually the error will never
|
||||
* be sent because addReply*() against master clients has no effect...
|
||||
* A notable example is:
|
||||
*
|
||||
* EVAL 'redis.call("incr",KEYS[1]); redis.call("nonexisting")' 1 x
|
||||
*
|
||||
* Where the master must propagate the first change even if the second
|
||||
* will produce an error. However it is useful to log such events since
|
||||
* they are rare and may hint at errors in a script or a bug in Redis. */
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE)) {
|
||||
char* to = c->flags & CLIENT_MASTER? "master": "slave";
|
||||
char* from = c->flags & CLIENT_MASTER? "slave": "master";
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
/* Here we want to panic because when a master is sending an
|
||||
* error to some slave in the context of replication, this can
|
||||
* only create some kind of offset or data desynchronization. Better
|
||||
* to catch it ASAP and crash instead of continuing. */
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
serverPanic("Continuing is unsafe: replication protocol violation.");
|
||||
serverLog(LL_WARNING,"== CRITICAL == This slave is sending an error "
|
||||
"to its master: '%s' after processing the command "
|
||||
"'%s'", s, cmdname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,41 +387,26 @@ void *addDeferredMultiBulkLength(client *c) {
|
||||
/* Populate the length object and try gluing it to the next chunk. */
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length) {
|
||||
listNode *ln = (listNode*)node;
|
||||
clientReplyBlock *next;
|
||||
char lenstr[128];
|
||||
size_t lenstr_len = sprintf(lenstr, "*%ld\r\n", length);
|
||||
sds len, next;
|
||||
|
||||
/* Abort when *node is NULL: when the client should not accept writes
|
||||
* we return NULL in addDeferredMultiBulkLength() */
|
||||
if (node == NULL) return;
|
||||
serverAssert(!listNodeValue(ln));
|
||||
|
||||
/* Normally we fill this dummy NULL node, added by addDeferredMultiBulkLength(),
|
||||
* with a new buffer structure containing the protocol needed to specify
|
||||
* the length of the array following. However sometimes when there is
|
||||
* little memory to move, we may instead remove this NULL node, and prefix
|
||||
* our protocol in the node immediately after to it, in order to save a
|
||||
* write(2) syscall later. Conditions needed to do it:
|
||||
*
|
||||
* - The next node is non-NULL,
|
||||
* - It has enough room already allocated
|
||||
* - And not too large (avoid large memmove) */
|
||||
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
|
||||
next->size - next->used >= lenstr_len &&
|
||||
next->used < PROTO_REPLY_CHUNK_BYTES * 4) {
|
||||
memmove(next->buf + lenstr_len, next->buf, next->used);
|
||||
memcpy(next->buf, lenstr, lenstr_len);
|
||||
next->used += lenstr_len;
|
||||
listDelNode(c->reply,ln);
|
||||
} else {
|
||||
/* Create a new node */
|
||||
clientReplyBlock *buf = zmalloc(lenstr_len + sizeof(clientReplyBlock));
|
||||
/* Take over the allocation's internal fragmentation */
|
||||
buf->size = zmalloc_usable(buf) - sizeof(clientReplyBlock);
|
||||
buf->used = lenstr_len;
|
||||
memcpy(buf->buf, lenstr, lenstr_len);
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
len = sdscatprintf(sdsnewlen("*",1),"%ld\r\n",length);
|
||||
listNodeValue(ln) = len;
|
||||
c->reply_bytes += sdslen(len);
|
||||
if (ln->next != NULL) {
|
||||
next = listNodeValue(ln->next);
|
||||
|
||||
/* Only glue when the next node is non-NULL (an sds in this case) */
|
||||
if (next != NULL) {
|
||||
len = sdscatsds(len,next);
|
||||
listDelNode(c->reply,ln->next);
|
||||
listNodeValue(ln) = len;
|
||||
/* No need to update c->reply_bytes: we are just moving the same
|
||||
* amount of bytes from one node to another. */
|
||||
}
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
@@ -611,24 +560,11 @@ 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.",
|
||||
(char*)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. */
|
||||
void copyClientOutputBuffer(client *dst, client *src) {
|
||||
listRelease(dst->reply);
|
||||
dst->sentlen = 0;
|
||||
dst->reply = listDup(src->reply);
|
||||
memcpy(dst->buf,src->buf,src->bufpos);
|
||||
dst->bufpos = src->bufpos;
|
||||
@@ -784,8 +720,6 @@ 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;
|
||||
}
|
||||
@@ -827,7 +761,8 @@ void freeClient(client *c) {
|
||||
serverLog(LL_WARNING,"Connection with master lost.");
|
||||
if (!(c->flags & (CLIENT_CLOSE_AFTER_REPLY|
|
||||
CLIENT_CLOSE_ASAP|
|
||||
CLIENT_BLOCKED)))
|
||||
CLIENT_BLOCKED|
|
||||
CLIENT_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
@@ -929,21 +864,12 @@ 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) {
|
||||
ssize_t nwritten = 0, totwritten = 0;
|
||||
size_t objlen;
|
||||
clientReplyBlock *o;
|
||||
sds o;
|
||||
|
||||
while(clientHasPendingReplies(c)) {
|
||||
if (c->bufpos > 0) {
|
||||
@@ -960,24 +886,23 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
}
|
||||
} else {
|
||||
o = listNodeValue(listFirst(c->reply));
|
||||
objlen = o->used;
|
||||
objlen = sdslen(o);
|
||||
|
||||
if (objlen == 0) {
|
||||
c->reply_bytes -= o->size;
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
continue;
|
||||
}
|
||||
|
||||
nwritten = write(fd, o->buf + c->sentlen, objlen - c->sentlen);
|
||||
nwritten = write(fd, o + c->sentlen, objlen - c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
/* If we fully sent the object on head go to the next one */
|
||||
if (c->sentlen == objlen) {
|
||||
c->reply_bytes -= o->size;
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes -= objlen;
|
||||
/* If there are no longer objects in the list, we expect
|
||||
* the count of reply bytes to be exactly zero. */
|
||||
if (listLength(c->reply) == 0)
|
||||
@@ -1114,34 +1039,34 @@ void resetClient(client *c) {
|
||||
* with the error and close the connection. */
|
||||
int processInlineBuffer(client *c) {
|
||||
char *newline;
|
||||
int argc, j, linefeed_chars = 1;
|
||||
int argc, j;
|
||||
sds *argv, aux;
|
||||
size_t querylen;
|
||||
|
||||
/* Search for end of line */
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\n');
|
||||
newline = strchr(c->querybuf,'\n');
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,"Protocol error: too big inline request");
|
||||
setProtocolError("too big inline request",c);
|
||||
setProtocolError("too big inline request",c,0);
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Handle the \r\n case. */
|
||||
if (newline && newline != c->querybuf+c->qb_pos && *(newline-1) == '\r')
|
||||
newline--, linefeed_chars++;
|
||||
if (newline && newline != c->querybuf && *(newline-1) == '\r')
|
||||
newline--;
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf+c->qb_pos);
|
||||
aux = sdsnewlen(c->querybuf+c->qb_pos,querylen);
|
||||
querylen = newline-(c->querybuf);
|
||||
aux = sdsnewlen(c->querybuf,querylen);
|
||||
argv = sdssplitargs(aux,&argc);
|
||||
sdsfree(aux);
|
||||
if (argv == NULL) {
|
||||
addReplyError(c,"Protocol error: unbalanced quotes in request");
|
||||
setProtocolError("unbalanced quotes in inline request",c);
|
||||
setProtocolError("unbalanced quotes in inline request",c,0);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -1151,8 +1076,8 @@ int processInlineBuffer(client *c) {
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Move querybuffer position to the next query in the buffer. */
|
||||
c->qb_pos += querylen+linefeed_chars;
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (argc) {
|
||||
@@ -1173,19 +1098,19 @@ int processInlineBuffer(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Helper function. Record protocol erro details in server log,
|
||||
* and set the client as CLIENT_CLOSE_AFTER_REPLY. */
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
#define PROTO_DUMP_LEN 128
|
||||
static void setProtocolError(const char *errstr, client *c) {
|
||||
static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
if (server.verbosity <= LL_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
/* Sample some protocol to given an idea about what was inside. */
|
||||
char buf[256];
|
||||
if (sdslen(c->querybuf)-c->qb_pos < PROTO_DUMP_LEN) {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf+c->qb_pos);
|
||||
if (sdslen(c->querybuf) < PROTO_DUMP_LEN) {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf);
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf+c->qb_pos, sdslen(c->querybuf)-c->qb_pos-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf, sdslen(c->querybuf)-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
|
||||
}
|
||||
|
||||
/* Remove non printable chars. */
|
||||
@@ -1201,6 +1126,7 @@ static void setProtocolError(const char *errstr, client *c) {
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
/* Process the query buffer for client 'c', setting up the client argument
|
||||
@@ -1216,6 +1142,7 @@ static void setProtocolError(const char *errstr, client *c) {
|
||||
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
|
||||
int processMultibulkBuffer(client *c) {
|
||||
char *newline = NULL;
|
||||
long pos = 0;
|
||||
int ok;
|
||||
long long ll;
|
||||
|
||||
@@ -1224,32 +1151,34 @@ int processMultibulkBuffer(client *c) {
|
||||
serverAssertWithInfo(c,NULL,c->argc == 0);
|
||||
|
||||
/* Multi bulk length cannot be read without a \r\n */
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\r');
|
||||
newline = strchr(c->querybuf,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,"Protocol error: too big mbulk count string");
|
||||
setProtocolError("too big mbulk count string",c);
|
||||
setProtocolError("too big mbulk count string",c,0);
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Buffer should also contain \n */
|
||||
if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
|
||||
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
|
||||
return C_ERR;
|
||||
|
||||
/* We know for sure there is a whole line since newline != NULL,
|
||||
* so go ahead and find out the multi bulk length. */
|
||||
serverAssertWithInfo(c,NULL,c->querybuf[c->qb_pos] == '*');
|
||||
ok = string2ll(c->querybuf+1+c->qb_pos,newline-(c->querybuf+1+c->qb_pos),&ll);
|
||||
serverAssertWithInfo(c,NULL,c->querybuf[0] == '*');
|
||||
ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
|
||||
if (!ok || ll > 1024*1024) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError("invalid mbulk count",c);
|
||||
setProtocolError("invalid mbulk count",c,pos);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = (newline-c->querybuf)+2;
|
||||
|
||||
if (ll <= 0) return C_OK;
|
||||
pos = (newline-c->querybuf)+2;
|
||||
if (ll <= 0) {
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
c->multibulklen = ll;
|
||||
|
||||
@@ -1262,37 +1191,37 @@ int processMultibulkBuffer(client *c) {
|
||||
while(c->multibulklen) {
|
||||
/* Read bulk length if unknown */
|
||||
if (c->bulklen == -1) {
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\r');
|
||||
newline = strchr(c->querybuf+pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,
|
||||
"Protocol error: too big bulk count string");
|
||||
setProtocolError("too big bulk count string",c);
|
||||
setProtocolError("too big bulk count string",c,0);
|
||||
return C_ERR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Buffer should also contain \n */
|
||||
if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
|
||||
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
|
||||
break;
|
||||
|
||||
if (c->querybuf[c->qb_pos] != '$') {
|
||||
if (c->querybuf[pos] != '$') {
|
||||
addReplyErrorFormat(c,
|
||||
"Protocol error: expected '$', got '%c'",
|
||||
c->querybuf[c->qb_pos]);
|
||||
setProtocolError("expected $ but got something else",c);
|
||||
c->querybuf[pos]);
|
||||
setProtocolError("expected $ but got something else",c,pos);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll);
|
||||
ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
|
||||
if (!ok || ll < 0 || ll > server.proto_max_bulk_len) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c);
|
||||
setProtocolError("invalid bulk length",c,pos);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = newline-c->querybuf+2;
|
||||
pos += newline-(c->querybuf+pos)+2;
|
||||
if (ll >= PROTO_MBULK_BIG_ARG) {
|
||||
size_t qblen;
|
||||
|
||||
@@ -1300,8 +1229,8 @@ int processMultibulkBuffer(client *c) {
|
||||
* try to make it likely that it will start at c->querybuf
|
||||
* boundary so that we can optimize object creation
|
||||
* avoiding a large copy of data. */
|
||||
sdsrange(c->querybuf,c->qb_pos,-1);
|
||||
c->qb_pos = 0;
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
pos = 0;
|
||||
qblen = sdslen(c->querybuf);
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
@@ -1312,14 +1241,14 @@ int processMultibulkBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
if (sdslen(c->querybuf)-c->qb_pos < (size_t)(c->bulklen+2)) {
|
||||
if (sdslen(c->querybuf)-pos < (size_t)(c->bulklen+2)) {
|
||||
/* Not enough data (+2 == trailing \r\n) */
|
||||
break;
|
||||
} else {
|
||||
/* Optimization: if the buffer contains JUST our bulk element
|
||||
* instead of creating a new object by *copying* the sds we
|
||||
* just use the current sds string. */
|
||||
if (c->qb_pos == 0 &&
|
||||
if (pos == 0 &&
|
||||
c->bulklen >= PROTO_MBULK_BIG_ARG &&
|
||||
sdslen(c->querybuf) == (size_t)(c->bulklen+2))
|
||||
{
|
||||
@@ -1329,16 +1258,20 @@ int processMultibulkBuffer(client *c) {
|
||||
* likely... */
|
||||
c->querybuf = sdsnewlen(SDS_NOINIT,c->bulklen+2);
|
||||
sdsclear(c->querybuf);
|
||||
pos = 0;
|
||||
} else {
|
||||
c->argv[c->argc++] =
|
||||
createStringObject(c->querybuf+c->qb_pos,c->bulklen);
|
||||
c->qb_pos += c->bulklen+2;
|
||||
createStringObject(c->querybuf+pos,c->bulklen);
|
||||
pos += c->bulklen+2;
|
||||
}
|
||||
c->bulklen = -1;
|
||||
c->multibulklen--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (pos) sdsrange(c->querybuf,pos,-1);
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) return C_OK;
|
||||
|
||||
@@ -1352,21 +1285,14 @@ int processMultibulkBuffer(client *c) {
|
||||
* pending query buffer, already representing a full command, to process. */
|
||||
void processInputBuffer(client *c) {
|
||||
server.current_client = c;
|
||||
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
while(c->qb_pos < sdslen(c->querybuf)) {
|
||||
while(sdslen(c->querybuf)) {
|
||||
/* Return if clients are paused. */
|
||||
if (!(c->flags & CLIENT_SLAVE) && clientsArePaused()) break;
|
||||
|
||||
/* Immediately abort if the client is in the middle of something. */
|
||||
if (c->flags & CLIENT_BLOCKED) break;
|
||||
|
||||
/* Don't process input from the master while there is a busy script
|
||||
* condition on the slave. We want just to accumulate the replication
|
||||
* stream (instead of replying -BUSY like we do with other clients) and
|
||||
* later resume the processing. */
|
||||
if (server.lua_timedout && c->flags & CLIENT_MASTER) break;
|
||||
|
||||
/* CLIENT_CLOSE_AFTER_REPLY closes the connection once the reply is
|
||||
* written to the client. Make sure to not let the reply grow after
|
||||
* this flag has been set (i.e. don't process more commands).
|
||||
@@ -1376,7 +1302,7 @@ void processInputBuffer(client *c) {
|
||||
|
||||
/* Determine request type when unknown. */
|
||||
if (!c->reqtype) {
|
||||
if (c->querybuf[c->qb_pos] == '*') {
|
||||
if (c->querybuf[0] == '*') {
|
||||
c->reqtype = PROTO_REQ_MULTIBULK;
|
||||
} else {
|
||||
c->reqtype = PROTO_REQ_INLINE;
|
||||
@@ -1399,7 +1325,7 @@ void processInputBuffer(client *c) {
|
||||
if (processCommand(c) == C_OK) {
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf);
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
@@ -1415,13 +1341,6 @@ void processInputBuffer(client *c) {
|
||||
if (server.current_client == NULL) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (c->qb_pos) {
|
||||
sdsrange(c->querybuf,c->qb_pos,-1);
|
||||
c->qb_pos = 0;
|
||||
}
|
||||
|
||||
server.current_client = NULL;
|
||||
}
|
||||
|
||||
@@ -1574,7 +1493,6 @@ 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';
|
||||
@@ -1613,7 +1531,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
client->lastcmd ? client->lastcmd->name : "NULL");
|
||||
}
|
||||
|
||||
sds getAllClientsInfoString(int type) {
|
||||
sds getAllClientsInfoString(void) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
client *client;
|
||||
@@ -1622,7 +1540,6 @@ sds getAllClientsInfoString(int type) {
|
||||
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);
|
||||
}
|
||||
@@ -1636,40 +1553,22 @@ void clientCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"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>.",
|
||||
"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 [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 <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
||||
" 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.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} 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")) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) {
|
||||
/* CLIENT LIST */
|
||||
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);
|
||||
sds o = getAllClientsInfoString();
|
||||
addReplyBulkCBuffer(c,o,sdslen(o));
|
||||
sdsfree(o);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
||||
@@ -1772,38 +1671,6 @@ 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;
|
||||
@@ -1954,7 +1821,10 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
* the caller wishes. The main usage of this function currently is
|
||||
* enforcing the client output length limits. */
|
||||
unsigned long getClientOutputBufferMemoryUsage(client *c) {
|
||||
unsigned long list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
|
||||
unsigned long list_item_size = sizeof(listNode)+5;
|
||||
/* The +5 above means we assume an sds16 hdr, may not be true
|
||||
* but is not going to be a problem. */
|
||||
|
||||
return c->reply_bytes + (list_item_size*listLength(c->reply));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* This file implements keyspace events notification via Pub/Sub and
|
||||
* described at https://redis.io/topics/notifications. */
|
||||
/* This file implements keyspace events notification via Pub/Sub ad
|
||||
* described at http://redis.io/topics/keyspace-events. */
|
||||
|
||||
/* Turn a string representing notification classes into an integer
|
||||
* representing notification classes flags xored.
|
||||
|
||||
+6
-88
@@ -123,25 +123,9 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
return createRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* 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 *createStringObjectFromLongLong(long long value) {
|
||||
robj *o;
|
||||
|
||||
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) {
|
||||
if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
} else {
|
||||
@@ -156,20 +140,6 @@ robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
|
||||
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
|
||||
@@ -745,7 +715,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 16 bytes of data overhead for each
|
||||
* approximate it considering 128 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
|
||||
@@ -904,7 +874,6 @@ 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);
|
||||
@@ -1017,18 +986,6 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->aof_buffer = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
|
||||
dictSlots(server.lua_scripts) * sizeof(dictEntry*);
|
||||
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
|
||||
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
|
||||
if (listLength(server.repl_scriptcache_fifo) > 0) {
|
||||
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
|
||||
sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
|
||||
}
|
||||
mh->lua_caches = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
long long keyscount = dictSize(db->dict);
|
||||
@@ -1086,7 +1043,6 @@ sds getMemoryDoctorReport(void) {
|
||||
int high_alloc_rss = 0; /* High rss overhead. */
|
||||
int big_slave_buf = 0; /* Slave buffers are too big. */
|
||||
int big_client_buf = 0; /* Client buffers are too big. */
|
||||
int many_scripts = 0; /* Script cache has too many scripts. */
|
||||
int num_reports = 0;
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
@@ -1137,12 +1093,6 @@ sds getMemoryDoctorReport(void) {
|
||||
big_slave_buf = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Too many scripts are cached? */
|
||||
if (dictSize(server.lua_scripts) > 1000) {
|
||||
many_scripts = 1;
|
||||
num_reports++;
|
||||
}
|
||||
}
|
||||
|
||||
sds s;
|
||||
@@ -1172,7 +1122,7 @@ sds getMemoryDoctorReport(void) {
|
||||
s = sdscatprintf(s," * High allocator RSS overhead: This instance has an RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the allocator is much larger than the sum what the allocator actually holds). This problem is usually due to a large peak memory (check if there is a peak memory entry above in the report), you can try the MEMORY PURGE command to reclaim it.\n\n");
|
||||
}
|
||||
if (high_proc_rss) {
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to Lua scripts or Modules.\n\n");
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to LUA scripts or Modules.\n\n");
|
||||
}
|
||||
if (big_slave_buf) {
|
||||
s = sdscat(s," * Big slave buffers: The slave output buffers in this instance are greater than 10MB for each slave (on average). This likely means that there is some slave instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what slave is not receiving data correctly and why. You can use the INFO output in order to check the slaves delays and the CLIENT LIST command to check the output buffers of each slave.\n\n");
|
||||
@@ -1180,41 +1130,12 @@ sds getMemoryDoctorReport(void) {
|
||||
if (big_client_buf) {
|
||||
s = sdscat(s," * Big client buffers: The clients output buffers in this instance are greater than 200K per client (on average). This may result from different causes, like Pub/Sub clients subscribed to channels bot not receiving data fast enough, so that data piles on the Redis instance output buffer, or clients sending commands with large replies or very large sequences of commands in the same pipeline. Please use the CLIENT LIST command in order to investigate the issue if it causes problems in your instance, or to understand better why certain clients are using a big amount of memory.\n\n");
|
||||
}
|
||||
if (many_scripts) {
|
||||
s = sdscat(s," * Many scripts: There seem to be many cached scripts in this instance (more than 1000). This may be because scripts are generated and `EVAL`ed, instead of being parameterized (with KEYS and ARGV), `SCRIPT LOAD`ed and `EVALSHA`ed. Unless `SCRIPT FLUSH` is called periodically, the scripts' caches may end up consuming most of your memory.\n\n");
|
||||
}
|
||||
s = sdscat(s,"I'm here to keep you safe, Sam. I want to help you.\n");
|
||||
}
|
||||
freeMemoryOverheadData(mh);
|
||||
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
|
||||
@@ -1276,7 +1197,7 @@ NULL
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help", (char *)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,7 +1236,7 @@ void memoryCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
addReplyMultiBulkLen(c,(25+mh->num_dbs)*2);
|
||||
addReplyMultiBulkLen(c,(24+mh->num_dbs)*2);
|
||||
|
||||
addReplyBulkCString(c,"peak.allocated");
|
||||
addReplyLongLong(c,mh->peak_allocated);
|
||||
@@ -1338,9 +1259,6 @@ void memoryCommand(client *c) {
|
||||
addReplyBulkCString(c,"aof.buffer");
|
||||
addReplyLongLong(c,mh->aof_buffer);
|
||||
|
||||
addReplyBulkCString(c,"lua.caches");
|
||||
addReplyLongLong(c,mh->lua_caches);
|
||||
|
||||
for (size_t j = 0; j < mh->num_dbs; j++) {
|
||||
char dbname[32];
|
||||
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
|
||||
|
||||
+5
-4
@@ -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,6 +372,7 @@ NULL
|
||||
/* PUBSUB NUMPAT */
|
||||
addReplyLongLong(c,listLength(server.pubsub_patterns));
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1636,7 +1636,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
TEST("add to tail of empty list") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistPushTail(ql, "hello", 6);
|
||||
/* 1 for head and 1 for tail because 1 node = head = tail */
|
||||
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
|
||||
ql_verify(ql, 1, 1, 1, 1);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@@ -1644,7 +1644,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
TEST("add to head of empty list") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistPushHead(ql, "hello", 6);
|
||||
/* 1 for head and 1 for tail because 1 node = head = tail */
|
||||
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
|
||||
ql_verify(ql, 1, 1, 1, 1);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
@@ -359,18 +359,7 @@ 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.
|
||||
*
|
||||
* 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). */
|
||||
* know where to split the node for insertion. */
|
||||
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;
|
||||
@@ -416,12 +405,10 @@ 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 (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) {
|
||||
* 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) {
|
||||
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
|
||||
@@ -439,8 +426,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
* data pointer. */
|
||||
if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) {
|
||||
debugf("### Insert: node representing key exists\n");
|
||||
/* Make space for the value pointer if needed. */
|
||||
if (!h->iskey || (h->isnull && overwrite)) {
|
||||
if (!h->iskey || h->isnull) {
|
||||
h = raxReallocForData(h,data);
|
||||
if (h) memcpy(parentlink,&h,sizeof(h));
|
||||
}
|
||||
@@ -448,17 +434,12 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Update the existing key if there is already one. */
|
||||
if (h->iskey) {
|
||||
if (old) *old = raxGetData(h);
|
||||
if (overwrite) raxSetData(h,data);
|
||||
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. */
|
||||
@@ -467,7 +448,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
/* If the node we stopped at is a compressed node, we need to
|
||||
* split it before to continue.
|
||||
*
|
||||
* Splitting a compressed node have a few possible cases.
|
||||
* Splitting a compressed node have a few possibile cases.
|
||||
* Imagine that the node 'h' we are currently at is a compressed
|
||||
* node contaning the string "ANNIBALE" (it means that it represents
|
||||
* nodes A -> N -> N -> I -> B -> A -> L -> E with the only child
|
||||
@@ -749,7 +730,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
cp = raxNodeLastChildPtr(trimmed);
|
||||
memcpy(cp,&postfix,sizeof(postfix));
|
||||
|
||||
/* Finish! We don't need to continue with the insertion
|
||||
/* Finish! We don't need to contine with the insertion
|
||||
* algorithm for ALGO 2. The key is already inserted. */
|
||||
rax->numele++;
|
||||
rax_free(h);
|
||||
@@ -812,19 +793,6 @@ 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. */
|
||||
@@ -1167,7 +1135,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -1241,10 +1208,6 @@ 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. */
|
||||
@@ -1297,10 +1260,6 @@ 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;
|
||||
@@ -1334,7 +1293,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 raxIteratorNextStep(). */
|
||||
* effect to the one of raxIteratorPrevSte(). */
|
||||
int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 1;
|
||||
@@ -1564,26 +1523,11 @@ 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. Continue iterating as this was
|
||||
* after processing all the key. Cotinue iterating as this was
|
||||
* a legitimate key we stopped at. */
|
||||
it->flags &= ~RAX_ITER_JUST_SEEKED;
|
||||
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;
|
||||
}
|
||||
if (gt && !raxIteratorNextStep(it,0)) return 0;
|
||||
if (lt && !raxIteratorPrevStep(it,0)) return 0;
|
||||
it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -94,7 +94,7 @@ typedef struct raxNode {
|
||||
*
|
||||
* If the node has an associated key (iskey=1) and is not NULL
|
||||
* (isnull=0), then after the raxNode pointers poiting to the
|
||||
* children, an additional value pointer is present (as you can see
|
||||
* childen, an additional value pointer is present (as you can see
|
||||
* in the representation above as "value-ptr" field).
|
||||
*/
|
||||
unsigned char data[];
|
||||
@@ -119,21 +119,6 @@ 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
|
||||
@@ -152,7 +137,6 @@ 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. */
|
||||
@@ -161,7 +145,6 @@ 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);
|
||||
@@ -177,8 +160,4 @@ 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
|
||||
|
||||
@@ -288,7 +288,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
memcpy(p,buf,len);
|
||||
return p;
|
||||
} else if (encode) {
|
||||
return createStringObjectFromLongLongForValue(val);
|
||||
return createStringObjectFromLongLong(val);
|
||||
} else {
|
||||
return createObject(OBJ_STRING,sdsfromlonglong(val));
|
||||
}
|
||||
@@ -1127,9 +1127,13 @@ 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. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = dictSize(db->dict);
|
||||
expires_size = dictSize(db->expires);
|
||||
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;
|
||||
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;
|
||||
@@ -1237,10 +1241,6 @@ 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;
|
||||
@@ -1658,7 +1658,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
if (first == NULL) {
|
||||
/* Serialized listpacks should never be empty, since on
|
||||
* deletion we should remove the radix tree key if the
|
||||
* resulting listpack is empty. */
|
||||
* resulting listpack is emtpy. */
|
||||
rdbExitReportCorruptRDB("Empty listpack inside stream");
|
||||
}
|
||||
|
||||
@@ -1867,9 +1867,11 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
}
|
||||
|
||||
/* Key-specific attributes, set by opcodes before the key type. */
|
||||
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
|
||||
long long expiretime = -1, now = mstime();
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
|
||||
uint64_t lru_idle = -1;
|
||||
int lfu_freq = -1;
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
|
||||
@@ -1897,9 +1899,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_IDLE) {
|
||||
/* IDLE: LRU idle time. */
|
||||
uint64_t qword;
|
||||
if ((qword = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
lru_idle = qword;
|
||||
if ((lru_idle = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_EOF) {
|
||||
/* EOF: End of file, exit the main loop. */
|
||||
@@ -2018,9 +2018,20 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
|
||||
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;
|
||||
}
|
||||
|
||||
/* Decrement the key refcount since dbAdd() will take its
|
||||
* own reference. */
|
||||
@@ -2099,7 +2110,7 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
* tirggering an error conditon. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.lastbgsave_status = C_ERR;
|
||||
}
|
||||
@@ -2136,7 +2147,7 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
* in error state.
|
||||
*
|
||||
* If the process returned an error, consider the list of slaves that
|
||||
* can continue to be empty, so that it's just a special case of the
|
||||
* can continue to be emtpy, so that it's just a special case of the
|
||||
* normal code path. */
|
||||
ok_slaves = zmalloc(sizeof(uint64_t)); /* Make space for the count. */
|
||||
ok_slaves[0] = 0;
|
||||
|
||||
@@ -129,8 +129,6 @@ 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,6 +34,7 @@
|
||||
|
||||
void createSharedObjects(void);
|
||||
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
|
||||
long long rdbLoadMillisecondTime(rio *rdb);
|
||||
int rdbCheckMode = 0;
|
||||
|
||||
struct {
|
||||
@@ -223,7 +224,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, rdbver)) == -1) goto eoferr;
|
||||
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_FREQ) {
|
||||
/* FREQ: LFU frequency. */
|
||||
@@ -286,8 +287,12 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
if (expiretime != -1) rdbstate.expires++;
|
||||
rdbstate.key = NULL;
|
||||
|
||||
+417
-478
File diff suppressed because it is too large
Load Diff
+1807
-106
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* redisassert.h -- Drop in replacements assert.h that prints the stack trace
|
||||
/* redisassert.h -- Drop in replacemnet assert.h that prints the stack trace
|
||||
* in the Redis logs.
|
||||
*
|
||||
* This file should be included instead of "assert.h" inside libraries used by
|
||||
|
||||
+13
-23
@@ -553,7 +553,7 @@ need_full_resync:
|
||||
* Side effects, other than starting a BGSAVE:
|
||||
*
|
||||
* 1) Handle the slaves in WAIT_START state, by preparing them for a full
|
||||
* sync if the BGSAVE was successfully started, or sending them an error
|
||||
* sync if the BGSAVE was succesfully started, or sending them an error
|
||||
* and dropping them from the list of slaves.
|
||||
*
|
||||
* 2) Flush the Lua scripting script cache if the BGSAVE was actually
|
||||
@@ -896,7 +896,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
/* If the preamble was already transferred, send the RDB bulk data. */
|
||||
/* If the preamble was already transfered, send the RDB bulk data. */
|
||||
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
|
||||
buflen = read(slave->repldbfd,buf,PROTO_IOBUF_LEN);
|
||||
if (buflen <= 0) {
|
||||
@@ -965,7 +965,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
replicationGetSlaveName(slave));
|
||||
/* Note: we wait for a REPLCONF ACK message from slave in
|
||||
* order to really put it online (install the write handler
|
||||
* so that the accumulated data can be transferred). However
|
||||
* so that the accumulated data can be transfered). However
|
||||
* we change the replication state ASAP, since our slave
|
||||
* is technically online now. */
|
||||
slave->replstate = SLAVE_STATE_ONLINE;
|
||||
@@ -1048,7 +1048,7 @@ int slaveIsInHandshakeState(void) {
|
||||
|
||||
/* Avoid the master to detect the slave is timing out while loading the
|
||||
* RDB file in initial synchronization. We send a single newline character
|
||||
* that is valid protocol but is guaranteed to either be sent entirely or
|
||||
* that is valid protocol but is guaranteed to either be sent entierly or
|
||||
* not, since the byte is indivisible.
|
||||
*
|
||||
* The function is called in two contexts: while we flush the current
|
||||
@@ -1279,7 +1279,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
close(server.repl_transfer_fd);
|
||||
replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
|
||||
server.repl_state = REPL_STATE_CONNECTED;
|
||||
server.repl_down_since = 0;
|
||||
/* After a full resynchroniziation we use the replication ID and
|
||||
* offset of the master. The secondary ID / offset are cleared since
|
||||
* we are starting a new history. */
|
||||
@@ -1316,31 +1315,24 @@ 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 redis binary
|
||||
* protocol to make sure correct arguments are sent. This function
|
||||
* is not safe for all binary data. */
|
||||
/* Create the command to send to the master, we use simple inline
|
||||
* protocol for simplicity as currently we only send simple strings. */
|
||||
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;
|
||||
|
||||
cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
|
||||
argslen++;
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -1397,7 +1389,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
*
|
||||
* The function returns:
|
||||
*
|
||||
* PSYNC_CONTINUE: If the PSYNC command succeeded and we can continue.
|
||||
* PSYNC_CONTINUE: If the PSYNC command succeded and we can continue.
|
||||
* PSYNC_FULLRESYNC: If PSYNC is supported but a full resync is needed.
|
||||
* In this case the master run_id and global replication
|
||||
* offset is saved.
|
||||
@@ -1951,6 +1943,7 @@ void replicationSetMaster(char *ip, int port) {
|
||||
* our own parameters, to later PSYNC with the new master. */
|
||||
if (was_master) replicationCacheMasterUsingMyself();
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
server.repl_down_since = 0;
|
||||
}
|
||||
|
||||
/* Cancel replication, setting the instance as a master itself. */
|
||||
@@ -2120,7 +2113,7 @@ void replicationSendAck(void) {
|
||||
* functions. */
|
||||
|
||||
/* This function is called by freeClient() in order to cache the master
|
||||
* client structure instead of destroying it. freeClient() will return
|
||||
* client structure instead of destryoing it. freeClient() will return
|
||||
* ASAP after this function returns, so every action needed to avoid problems
|
||||
* with a client that is really "suspended" has to be done by this function.
|
||||
*
|
||||
@@ -2148,8 +2141,6 @@ void replicationCacheMaster(client *c) {
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
if (c->flags & CLIENT_MULTI) discardTransaction(c);
|
||||
listEmpty(c->reply);
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes = 0;
|
||||
c->bufpos = 0;
|
||||
resetClient(c);
|
||||
|
||||
@@ -2219,7 +2210,6 @@ void replicationResurrectCachedMaster(int newfd) {
|
||||
server.master->authenticated = 1;
|
||||
server.master->lastinteraction = server.unixtime;
|
||||
server.repl_state = REPL_STATE_CONNECTED;
|
||||
server.repl_down_since = 0;
|
||||
|
||||
/* Re-add to the list of clients. */
|
||||
linkClient(server.master);
|
||||
|
||||
@@ -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);
|
||||
redis_fsync(fileno(r->io.file.fp));
|
||||
aof_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
return retval;
|
||||
|
||||
+19
-33
@@ -483,7 +483,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* command marked as non-deterministic was already called in the context
|
||||
* of this script. */
|
||||
if (cmd->flags & CMD_WRITE) {
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (server.lua_random_dirty && !server.lua_replicate_commands) {
|
||||
luaPushError(lua,
|
||||
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
|
||||
@@ -494,16 +493,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
{
|
||||
luaPushError(lua, shared.roslaveerr->ptr);
|
||||
goto cleanup;
|
||||
} else if (deny_write_type != DISK_ERROR_TYPE_NONE) {
|
||||
if (deny_write_type == DISK_ERROR_TYPE_RDB) {
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
} else {
|
||||
sds aof_write_err = sdscatfmt(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno));
|
||||
luaPushError(lua, aof_write_err);
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
} else if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR)
|
||||
{
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@@ -581,9 +575,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
sds o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatlen(reply,o->buf,o->used);
|
||||
reply = sdscatsds(reply,o);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
}
|
||||
@@ -925,7 +919,6 @@ void scriptingInit(int setup) {
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
* as EVAL, so we need to remember the associated script. */
|
||||
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
|
||||
server.lua_scripts_mem = 0;
|
||||
|
||||
/* Register the redis commands table and fields */
|
||||
lua_newtable(lua);
|
||||
@@ -1080,7 +1073,6 @@ void scriptingInit(int setup) {
|
||||
* This function is used in order to reset the scripting environment. */
|
||||
void scriptingRelease(void) {
|
||||
dictRelease(server.lua_scripts);
|
||||
server.lua_scripts_mem = 0;
|
||||
lua_close(server.lua);
|
||||
}
|
||||
|
||||
@@ -1215,19 +1207,17 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
server.lua_scripts_mem += sdsZmallocSize(sha) + sdsZmallocSize(body->ptr);
|
||||
incrRefCount(body);
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
long long elapsed = mstime() - server.lua_time_start;
|
||||
long long elapsed;
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
/* Set the timeout condition if not already set and the maximum
|
||||
* execution time was reached. */
|
||||
elapsed = mstime() - server.lua_time_start;
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
@@ -1346,7 +1336,9 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL &&
|
||||
ldb.active == 0)
|
||||
{
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
} else if (ldb.active) {
|
||||
@@ -1367,10 +1359,6 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* script timeout was detected. */
|
||||
aeCreateFileEvent(server.el,c->fd,AE_READABLE,
|
||||
readQueryFromClient,c);
|
||||
if (server.masterhost && server.master) {
|
||||
server.master->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,server.master);
|
||||
}
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
|
||||
@@ -1469,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);
|
||||
@@ -1500,8 +1488,6 @@ NULL
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
|
||||
} else if (server.lua_write_dirty) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
} else {
|
||||
@@ -1528,7 +1514,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP", (char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1730,7 +1716,7 @@ int ldbRemoveChild(pid_t pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the number of children we still did not receive termination
|
||||
/* Return the number of children we still did not received termination
|
||||
* acknowledge via wait() in the parent process. */
|
||||
int ldbPendingChildren(void) {
|
||||
return listLength(ldb.children);
|
||||
|
||||
@@ -67,10 +67,8 @@ static inline char sdsReqType(size_t string_size) {
|
||||
#if (LONG_MAX == LLONG_MAX)
|
||||
if (string_size < 1ll<<32)
|
||||
return SDS_TYPE_32;
|
||||
return SDS_TYPE_64;
|
||||
#else
|
||||
return SDS_TYPE_32;
|
||||
#endif
|
||||
return SDS_TYPE_64;
|
||||
}
|
||||
|
||||
/* Create a new sds string with the content specified by the 'init' pointer
|
||||
@@ -285,7 +283,7 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return the total size of the allocation of the specified sds string,
|
||||
/* Return the total size of the allocation of the specifed sds string,
|
||||
* including:
|
||||
* 1) The sds header before the pointer.
|
||||
* 2) The string.
|
||||
|
||||
+44
-202
@@ -84,7 +84,6 @@ 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. */
|
||||
@@ -178,10 +177,6 @@ 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
|
||||
@@ -246,8 +241,6 @@ 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. */
|
||||
@@ -387,9 +380,7 @@ 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) {
|
||||
@@ -423,16 +414,6 @@ 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);
|
||||
@@ -487,7 +468,6 @@ 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));
|
||||
}
|
||||
|
||||
@@ -514,7 +494,7 @@ void sentinelIsRunning(void) {
|
||||
if (sentinel.myid[j] != 0) break;
|
||||
|
||||
if (j == CONFIG_RUN_ID_SIZE) {
|
||||
/* Pick ID and persist the config. */
|
||||
/* Pick ID and presist the config. */
|
||||
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
@@ -1227,7 +1207,6 @@ 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;
|
||||
@@ -1275,7 +1254,6 @@ 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)
|
||||
@@ -1590,21 +1568,6 @@ 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;
|
||||
@@ -1714,17 +1677,6 @@ 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]))
|
||||
@@ -1732,12 +1684,6 @@ 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.";
|
||||
}
|
||||
@@ -1758,12 +1704,6 @@ 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) {
|
||||
@@ -1871,18 +1811,6 @@ 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. */
|
||||
@@ -1947,8 +1875,7 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
ri->master->auth_pass;
|
||||
|
||||
if (auth_pass) {
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "AUTH %s",
|
||||
auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
}
|
||||
@@ -1964,9 +1891,7 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
|
||||
|
||||
snprintf(name,sizeof(name),"sentinel-%.8s-%s",sentinel.myid,type);
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri,
|
||||
"%s SETNAME %s",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"),
|
||||
name) == C_OK)
|
||||
"CLIENT SETNAME %s", name) == C_OK)
|
||||
{
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
@@ -2028,9 +1953,8 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
sentinelSetClientName(ri,link->pc,"pubsub");
|
||||
/* Now we subscribe to the Sentinels "Hello" channel. */
|
||||
retval = redisAsyncCommand(link->pc,
|
||||
sentinelReceiveHelloMessages, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"SUBSCRIBE"),
|
||||
SENTINEL_HELLO_CHANNEL);
|
||||
sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
|
||||
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. */
|
||||
@@ -2364,11 +2288,8 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
{
|
||||
if (redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri,
|
||||
"%s KILL",
|
||||
sentinelInstanceMapCommand(ri,"SCRIPT")) == C_OK)
|
||||
{
|
||||
"SCRIPT KILL") == C_OK)
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
ri->flags |= SRI_SCRIPT_KILL_SENT;
|
||||
}
|
||||
}
|
||||
@@ -2531,7 +2452,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
}
|
||||
|
||||
/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
|
||||
* instance in order to broadcast the current configuration for this
|
||||
* instance in order to broadcast the current configuraiton for this
|
||||
* master, and to advertise the existence of this Sentinel at the same time.
|
||||
*
|
||||
* The message has the following format:
|
||||
@@ -2574,9 +2495,8 @@ 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, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"PUBLISH"),
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
sentinelPublishReplyCallback, ri, "PUBLISH %s %s",
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
if (retval != C_OK) return C_ERR;
|
||||
ri->link->pending_commands++;
|
||||
return C_OK;
|
||||
@@ -2621,14 +2541,13 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) {
|
||||
* queued in the connection. */
|
||||
int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
int retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelPingReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"PING"));
|
||||
sentinelPingReplyCallback, ri, "PING");
|
||||
if (retval == C_OK) {
|
||||
ri->link->pending_commands++;
|
||||
ri->link->last_ping_time = mstime();
|
||||
/* We update the active ping time only if we received the pong for
|
||||
* the previous ping, otherwise we are technically waiting since the
|
||||
* first ping that did not receive a reply. */
|
||||
* first ping that did not received a reply. */
|
||||
if (ri->link->act_ping_time == 0)
|
||||
ri->link->act_ping_time = ri->link->last_ping_time;
|
||||
return 1;
|
||||
@@ -2686,8 +2605,7 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
(now - ri->info_refresh) > info_period))
|
||||
{
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelInfoReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"INFO"));
|
||||
sentinelInfoReplyCallback, ri, "INFO");
|
||||
if (retval == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
|
||||
@@ -3181,7 +3099,7 @@ void sentinelCommand(client *c) {
|
||||
addReplySds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3) goto numargserr;
|
||||
if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
|
||||
sentinelSetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
|
||||
/* SENTINEL INFO-CACHE <name> */
|
||||
@@ -3357,7 +3275,7 @@ void sentinelInfoCommand(client *c) {
|
||||
addReplyBulkSds(c, info);
|
||||
}
|
||||
|
||||
/* Implements Sentinel version of the ROLE command. The output is
|
||||
/* Implements Sentinel verison of the ROLE command. The output is
|
||||
* "sentinel" and the list of currently monitored master names. */
|
||||
void sentinelRoleCommand(client *c) {
|
||||
dictIterator *di;
|
||||
@@ -3380,58 +3298,39 @@ void sentinelRoleCommand(client *c) {
|
||||
void sentinelSetCommand(client *c) {
|
||||
sentinelRedisInstance *ri;
|
||||
int j, changes = 0;
|
||||
int badarg = 0; /* Bad argument position for error reporting. */
|
||||
char *option;
|
||||
char *option, *value;
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
|
||||
/* Process option - value pairs. */
|
||||
for (j = 3; j < c->argc; j++) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
for (j = 3; j < c->argc; j += 2) {
|
||||
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") && moreargs > 0) {
|
||||
if (!strcasecmp(option,"down-after-milliseconds")) {
|
||||
/* down-after-millisecodns <milliseconds> */
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
}
|
||||
ri->down_after_period = ll;
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"failover-timeout") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"failover-timeout")) {
|
||||
/* failover-timeout <milliseconds> */
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
}
|
||||
ri->failover_timeout = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"parallel-syncs") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"parallel-syncs")) {
|
||||
/* parallel-syncs <milliseconds> */
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
}
|
||||
ri->parallel_syncs = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"notification-script") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"notification-script")) {
|
||||
/* 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");
|
||||
@@ -3441,17 +3340,8 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->notification_script);
|
||||
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"client-reconfig-script") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"client-reconfig-script")) {
|
||||
/* 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 "
|
||||
@@ -3462,65 +3352,24 @@ 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") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"auth-pass")) {
|
||||
/* 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") && moreargs > 0) {
|
||||
} else if (!strcasecmp(option,"quorum")) {
|
||||
/* quorum <count> */
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
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 or number of arguments for "
|
||||
"SENTINEL SET '%s'", option);
|
||||
addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
|
||||
option);
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@@ -3530,7 +3379,7 @@ void sentinelSetCommand(client *c) {
|
||||
badfmt: /* Bad format errors */
|
||||
if (changes) sentinelFlushConfig();
|
||||
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
|
||||
(char*)c->argv[badarg]->ptr,option);
|
||||
value, option);
|
||||
}
|
||||
|
||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||
@@ -3568,8 +3417,8 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
if (ri->link->cc &&
|
||||
(mstime() - ri->link->cc_conn_time) >
|
||||
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
|
||||
ri->link->act_ping_time != 0 && /* There is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not receive
|
||||
ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not received
|
||||
* error replies as well. */
|
||||
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
|
||||
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
|
||||
@@ -3725,7 +3574,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
*
|
||||
* 1) We believe it is down, or there is a failover in progress.
|
||||
* 2) Sentinel is connected.
|
||||
* 3) We did not receive the info within SENTINEL_ASK_PERIOD ms. */
|
||||
* 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */
|
||||
if ((master->flags & SRI_S_DOWN) == 0) continue;
|
||||
if (ri->link->disconnected) continue;
|
||||
if (!(flags & SENTINEL_ASK_FORCED) &&
|
||||
@@ -3736,8 +3585,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
ll2string(port,sizeof(port),master->addr->port);
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelReceiveIsMasterDownReply, ri,
|
||||
"%s is-master-down-by-addr %s %s %llu %s",
|
||||
sentinelInstanceMapCommand(ri,"SENTINEL"),
|
||||
"SENTINEL is-master-down-by-addr %s %s %llu %s",
|
||||
master->addr->ip, port,
|
||||
sentinel.current_epoch,
|
||||
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
|
||||
@@ -3757,7 +3605,7 @@ void sentinelSimFailureCrash(void) {
|
||||
}
|
||||
|
||||
/* Vote for the sentinel with 'req_runid' or return the old vote if already
|
||||
* voted for the specified 'req_epoch' or one greater.
|
||||
* voted for the specifed 'req_epoch' or one greater.
|
||||
*
|
||||
* If a vote is not available returns NULL, otherwise return the Sentinel
|
||||
* runid and populate the leader_epoch with the epoch of the vote. */
|
||||
@@ -3908,7 +3756,7 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
/* In order to send SLAVEOF in a safe way, we send a transaction performing
|
||||
* the following tasks:
|
||||
* 1) Reconfigure the instance according to the specified host/port params.
|
||||
* 2) Rewrite the configuration.
|
||||
* 2) Rewrite the configuraiton.
|
||||
* 3) Disconnect all clients (but this one sending the commnad) in order
|
||||
* to trigger the ask-master-on-reconnection protocol for connected
|
||||
* clients.
|
||||
@@ -3916,21 +3764,17 @@ 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, "%s",
|
||||
sentinelInstanceMapCommand(ri,"MULTI"));
|
||||
sentinelDiscardReplyCallback, ri, "MULTI");
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"SLAVEOF"),
|
||||
host, portstr);
|
||||
sentinelDiscardReplyCallback, ri, "SLAVEOF %s %s", host, portstr);
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s REWRITE",
|
||||
sentinelInstanceMapCommand(ri,"CONFIG"));
|
||||
sentinelDiscardReplyCallback, ri, "CONFIG REWRITE");
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
@@ -3940,14 +3784,12 @@ 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, "%s KILL TYPE normal",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"));
|
||||
sentinelDiscardReplyCallback, ri, "CLIENT KILL TYPE normal");
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"EXEC"));
|
||||
sentinelDiscardReplyCallback, ri, "EXEC");
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
|
||||
+73
-254
@@ -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},
|
||||
@@ -214,7 +214,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
@@ -240,9 +240,9 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"a",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"a",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"alt",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
|
||||
@@ -254,16 +254,16 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"flushdb",flushdbCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"flushall",flushallCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
|
||||
{"info",infoCommand,-1,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"info",infoCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"ttl",ttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"touch",touchCommand,-2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"last",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"lat",0,NULL,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
@@ -275,24 +275,24 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"wR",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"rR",0,NULL,2,2,1,0,0},
|
||||
{"memory",memoryCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,1,0,0},
|
||||
{"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"aR",0,NULL,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"a",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"lt",0,NULL,0,0,0,0,0},
|
||||
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
@@ -310,14 +310,14 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"xread",xreadCommand,-4,"rs",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xreadgroup",xreadCommand,-7,"ws",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xread",xreadCommand,-3,"rs",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xreadgroup",xreadCommand,-3,"ws",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xgroup",xgroupCommand,-2,"wm",0,NULL,2,2,1,0,0},
|
||||
{"xack",xackCommand,-4,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xack",xackCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xpending",xpendingCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"xclaim",xclaimCommand,-6,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xclaim",xclaimCommand,-5,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xinfo",xinfoCommand,-2,"r",0,NULL,2,2,1,0,0},
|
||||
{"xdel",xdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xdel",xdelCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xtrim",xtrimCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
@@ -326,10 +326,6 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
|
||||
/* We use a private localtime implementation which is fork-safe. The logging
|
||||
* function of Redis may be called from other threads. */
|
||||
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
|
||||
|
||||
/* Low level logging. To use only for very big messages, otherwise
|
||||
* serverLog() is to prefer. */
|
||||
void serverLogRaw(int level, const char *msg) {
|
||||
@@ -355,9 +351,7 @@ void serverLogRaw(int level, const char *msg) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
struct tm tm;
|
||||
nolocks_localtime(&tm,tv.tv_sec,server.timezone,server.daylight_active);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %Y %H:%M:%S.",&tm);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec));
|
||||
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
|
||||
if (server.sentinel_mode) {
|
||||
role_char = 'X'; /* Sentinel. */
|
||||
@@ -865,102 +859,15 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
/* Reset the peak again to capture the peak memory usage in the next
|
||||
* cycle. */
|
||||
c->querybuf_peak = 0;
|
||||
|
||||
/* Clients representing masters also use a "pending query buffer" that
|
||||
* is the yet not applied part of the stream we are reading. Such buffer
|
||||
* also needs resizing from time to time, otherwise after a very large
|
||||
* transfer (a huge value or a big MIGRATE operation) it will keep using
|
||||
* a lot of memory. */
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
/* There are two conditions to resize the pending query buffer:
|
||||
* 1) Pending Query buffer is > LIMIT_PENDING_QUERYBUF.
|
||||
* 2) Used length is smaller than pending_querybuf_size/2 */
|
||||
size_t pending_querybuf_size = sdsAllocSize(c->pending_querybuf);
|
||||
if(pending_querybuf_size > LIMIT_PENDING_QUERYBUF &&
|
||||
sdslen(c->pending_querybuf) < (pending_querybuf_size/2))
|
||||
{
|
||||
c->pending_querybuf = sdsRemoveFreeSpace(c->pending_querybuf);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function is used in order to track clients using the biggest amount
|
||||
* of memory in the latest few seconds. This way we can provide such information
|
||||
* in the INFO output (clients section), without having to do an O(N) scan for
|
||||
* all the clients.
|
||||
*
|
||||
* This is how it works. We have an array of CLIENTS_PEAK_MEM_USAGE_SLOTS slots
|
||||
* where we track, for each, the biggest client output and input buffers we
|
||||
* saw in that slot. Every slot correspond to one of the latest seconds, since
|
||||
* the array is indexed by doing UNIXTIME % CLIENTS_PEAK_MEM_USAGE_SLOTS.
|
||||
*
|
||||
* When we want to know what was recently the peak memory usage, we just scan
|
||||
* such few slots searching for the maximum value. */
|
||||
#define CLIENTS_PEAK_MEM_USAGE_SLOTS 8
|
||||
size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
|
||||
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
|
||||
|
||||
int clientsCronTrackExpansiveClients(client *c) {
|
||||
size_t in_usage = sdsAllocSize(c->querybuf);
|
||||
size_t out_usage = getClientOutputBufferMemoryUsage(c);
|
||||
int i = server.unixtime % CLIENTS_PEAK_MEM_USAGE_SLOTS;
|
||||
int zeroidx = (i+1) % CLIENTS_PEAK_MEM_USAGE_SLOTS;
|
||||
|
||||
/* Always zero the next sample, so that when we switch to that second, we'll
|
||||
* only register samples that are greater in that second without considering
|
||||
* the history of such slot.
|
||||
*
|
||||
* Note: our index may jump to any random position if serverCron() is not
|
||||
* called for some reason with the normal frequency, for instance because
|
||||
* some slow command is called taking multiple seconds to execute. In that
|
||||
* case our array may end containing data which is potentially older
|
||||
* than CLIENTS_PEAK_MEM_USAGE_SLOTS seconds: however this is not a problem
|
||||
* since here we want just to track if "recently" there were very expansive
|
||||
* clients from the POV of memory usage. */
|
||||
ClientsPeakMemInput[zeroidx] = 0;
|
||||
ClientsPeakMemOutput[zeroidx] = 0;
|
||||
|
||||
/* Track the biggest values observed so far in this slot. */
|
||||
if (in_usage > ClientsPeakMemInput[i]) ClientsPeakMemInput[i] = in_usage;
|
||||
if (out_usage > ClientsPeakMemOutput[i]) ClientsPeakMemOutput[i] = out_usage;
|
||||
|
||||
return 0; /* This function never terminates the client. */
|
||||
}
|
||||
|
||||
/* Return the max samples in the memory usage of clients tracked by
|
||||
* the function clientsCronTrackExpansiveClients(). */
|
||||
void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
size_t i = 0, o = 0;
|
||||
for (int j = 0; j < CLIENTS_PEAK_MEM_USAGE_SLOTS; j++) {
|
||||
if (ClientsPeakMemInput[j] > i) i = ClientsPeakMemInput[j];
|
||||
if (ClientsPeakMemOutput[j] > o) o = ClientsPeakMemOutput[j];
|
||||
}
|
||||
*in_usage = i;
|
||||
*out_usage = o;
|
||||
}
|
||||
|
||||
/* This function is called by serverCron() and is used in order to perform
|
||||
* operations on clients that are important to perform constantly. For instance
|
||||
* we use this function in order to disconnect clients after a timeout, including
|
||||
* clients blocked in some blocking command with a non-zero timeout.
|
||||
*
|
||||
* The function makes some effort to process all the clients every second, even
|
||||
* if this cannot be strictly guaranteed, since serverCron() may be called with
|
||||
* an actual frequency lower than server.hz in case of latency events like slow
|
||||
* commands.
|
||||
*
|
||||
* It is very important for this function, and the functions it calls, to be
|
||||
* very fast: sometimes Redis has tens of hundreds of connected clients, and the
|
||||
* default server.hz value is 10, so sometimes here we need to process thousands
|
||||
* of clients per second, turning this function into a source of latency.
|
||||
*/
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* Try to process at least numclients/server.hz of clients
|
||||
* per call. Since normally (if there are no big latency events) this
|
||||
* function is called server.hz times per second, in the average case we
|
||||
* process all the clients in 1 second. */
|
||||
/* Make sure to process at least numclients/server.hz of clients
|
||||
* per call. Since this function is called server.hz times per second
|
||||
* we are sure that in the worst case we process all the clients in 1
|
||||
* second. */
|
||||
int numclients = listLength(server.clients);
|
||||
int iterations = numclients/server.hz;
|
||||
mstime_t now = mstime();
|
||||
@@ -987,7 +894,6 @@ void clientsCron(void) {
|
||||
* terminated. */
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1054,14 +960,6 @@ void updateCachedTime(void) {
|
||||
time_t unixtime = time(NULL);
|
||||
atomicSet(server.unixtime,unixtime);
|
||||
server.mstime = mstime();
|
||||
|
||||
/* To get information about daylight saving time, we need to call localtime_r
|
||||
* and cache the result. However calling localtime_r in this context is safe
|
||||
* since we will never fork() while here, in the main thread. The logging
|
||||
* function will call a thread safe version of localtime that has no locks. */
|
||||
struct tm tm;
|
||||
localtime_r(&server.unixtime,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
@@ -1096,21 +994,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
server.hz = server.config_hz;
|
||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||
* many clients, we want to call serverCron() with an higher frequency. */
|
||||
if (server.dynamic_hz) {
|
||||
while (listLength(server.clients) / server.hz >
|
||||
MAX_CLIENTS_PER_CLOCK_TICK)
|
||||
{
|
||||
server.hz *= 2;
|
||||
if (server.hz > CONFIG_MAX_HZ) {
|
||||
server.hz = CONFIG_MAX_HZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_with_period(100) {
|
||||
trackInstantaneousMetric(STATS_METRIC_COMMAND,server.stat_numcommands);
|
||||
trackInstantaneousMetric(STATS_METRIC_NET_INPUT,
|
||||
@@ -1251,7 +1134,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
} else {
|
||||
/* If there is not a background saving/rewrite in progress check if
|
||||
* we have to save/rewrite now. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
struct saveparam *sp = server.saveparams+j;
|
||||
|
||||
/* Save if we reached the given amount of changes,
|
||||
@@ -1271,23 +1154,23 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
rdbSaveBackground(server.rdb_filename,rsiptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
{
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
{
|
||||
long long base = server.aof_rewrite_base_size ?
|
||||
server.aof_rewrite_base_size : 1;
|
||||
server.aof_rewrite_base_size : 1;
|
||||
long long growth = (server.aof_current_size*100/base) - 100;
|
||||
if (growth >= server.aof_rewrite_perc) {
|
||||
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
|
||||
rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1491,7 +1374,6 @@ void createSharedObjects(void) {
|
||||
shared.rpop = createStringObject("RPOP",4);
|
||||
shared.lpop = createStringObject("LPOP",4);
|
||||
shared.lpush = createStringObject("LPUSH",5);
|
||||
shared.rpoplpush = createStringObject("RPOPLPUSH",9);
|
||||
shared.zpopmin = createStringObject("ZPOPMIN",7);
|
||||
shared.zpopmax = createStringObject("ZPOPMAX",7);
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
@@ -1520,16 +1402,13 @@ void initServerConfig(void) {
|
||||
pthread_mutex_init(&server.lruclock_mutex,NULL);
|
||||
pthread_mutex_init(&server.unixtime_mutex,NULL);
|
||||
|
||||
updateCachedTime();
|
||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
server.timezone = timezone; /* Initialized by tzset(). */
|
||||
server.configfile = NULL;
|
||||
server.executable = NULL;
|
||||
server.config_hz = CONFIG_DEFAULT_HZ;
|
||||
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
|
||||
server.hz = CONFIG_DEFAULT_HZ;
|
||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||
server.port = CONFIG_DEFAULT_SERVER_PORT;
|
||||
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
|
||||
@@ -1578,7 +1457,6 @@ 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;
|
||||
@@ -1649,7 +1527,6 @@ void initServerConfig(void) {
|
||||
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
||||
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
|
||||
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
|
||||
server.repl_slave_ignore_maxmemory = CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY;
|
||||
server.repl_slave_lazy_flush = CONFIG_DEFAULT_SLAVE_LAZY_FLUSH;
|
||||
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY;
|
||||
@@ -2009,11 +1886,9 @@ void initServer(void) {
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
server.hz = server.config_hz;
|
||||
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();
|
||||
@@ -2106,6 +1981,7 @@ void initServer(void) {
|
||||
server.aof_last_write_status = C_OK;
|
||||
server.aof_last_write_errno = 0;
|
||||
server.repl_good_slaves_count = 0;
|
||||
updateCachedTime();
|
||||
|
||||
/* Create the timer callback, this is our way to process many background
|
||||
* operations incrementally, like clients timeout, eviction of unaccessed
|
||||
@@ -2469,7 +2345,7 @@ void call(client *c, int flags) {
|
||||
if (c->flags & CLIENT_FORCE_AOF) propagate_flags |= PROPAGATE_AOF;
|
||||
|
||||
/* However prevent AOF / replication propagation if the command
|
||||
* implementations called preventCommandPropagation() or similar,
|
||||
* implementatino called preventCommandPropagation() or similar,
|
||||
* or if we don't have the call() flags to do so. */
|
||||
if (c->flags & CLIENT_PREVENT_REPL_PROP ||
|
||||
!(flags & CMD_CALL_PROPAGATE_REPL))
|
||||
@@ -2539,13 +2415,8 @@ int processCommand(client *c) {
|
||||
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
|
||||
if (!c->cmd) {
|
||||
flagTransaction(c);
|
||||
sds args = sdsempty();
|
||||
int i;
|
||||
for (i=1; i < c->argc && sdslen(args) < 128; i++)
|
||||
args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
|
||||
addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s",
|
||||
(char*)c->argv[0]->ptr, args);
|
||||
sdsfree(args);
|
||||
addReplyErrorFormat(c,"unknown command '%s'",
|
||||
(char*)c->argv[0]->ptr);
|
||||
return C_OK;
|
||||
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
|
||||
(c->argc < -c->cmd->arity)) {
|
||||
@@ -2595,14 +2466,14 @@ int processCommand(client *c) {
|
||||
* keys in the dataset). If there are not the only thing we can do
|
||||
* is returning an error. */
|
||||
if (server.maxmemory) {
|
||||
int out_of_memory = freeMemoryIfNeeded() == C_ERR;
|
||||
int retval = freeMemoryIfNeeded();
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return C_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
* is trying to execute is denied during OOM conditions? Error. */
|
||||
if ((c->cmd->flags & CMD_DENYOOM) && out_of_memory) {
|
||||
if ((c->cmd->flags & CMD_DENYOOM) && retval == C_ERR) {
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
@@ -2611,14 +2482,16 @@ int processCommand(client *c) {
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk
|
||||
* and if this is a master instance. */
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (deny_write_type != DISK_ERROR_TYPE_NONE &&
|
||||
if (((server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR) ||
|
||||
server.aof_last_write_status == C_ERR) &&
|
||||
server.masterhost == NULL &&
|
||||
(c->cmd->flags & CMD_WRITE ||
|
||||
c->cmd->proc == pingCommand))
|
||||
{
|
||||
flagTransaction(c);
|
||||
if (deny_write_type == DISK_ERROR_TYPE_RDB)
|
||||
if (server.aof_last_write_status == C_OK)
|
||||
addReply(c, shared.bgsaveerr);
|
||||
else
|
||||
addReplySds(c,
|
||||
@@ -2765,7 +2638,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);
|
||||
redis_fsync(server.aof_fd);
|
||||
aof_fsync(server.aof_fd);
|
||||
}
|
||||
|
||||
/* Create a new RDB file before exiting. */
|
||||
@@ -2804,32 +2677,6 @@ int prepareForShutdown(int flags) {
|
||||
|
||||
/*================================== Commands =============================== */
|
||||
|
||||
/* Sometimes Redis cannot accept write commands because there is a perstence
|
||||
* error with the RDB or AOF file, and Redis is configured in order to stop
|
||||
* accepting writes in such situation. This function returns if such a
|
||||
* condition is active, and the type of the condition.
|
||||
*
|
||||
* Function return values:
|
||||
*
|
||||
* DISK_ERROR_TYPE_NONE: No problems, we can accept writes.
|
||||
* DISK_ERROR_TYPE_AOF: Don't accept writes: AOF errors.
|
||||
* DISK_ERROR_TYPE_RDB: Don't accept writes: RDB errors.
|
||||
*/
|
||||
int writeCommandsDeniedByDiskError(void) {
|
||||
if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR)
|
||||
{
|
||||
return DISK_ERROR_TYPE_RDB;
|
||||
} else if (server.aof_state != AOF_OFF &&
|
||||
server.aof_last_write_status == C_ERR)
|
||||
{
|
||||
return DISK_ERROR_TYPE_AOF;
|
||||
} else {
|
||||
return DISK_ERROR_TYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return zero if strings are the same, non-zero if they are not.
|
||||
* The comparison is performed in a way that prevents an attacker to obtain
|
||||
* information about the nature of the strings just monitoring the execution
|
||||
@@ -2980,9 +2827,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);
|
||||
@@ -3006,10 +2853,7 @@ NULL
|
||||
int *keys, numkeys, j;
|
||||
|
||||
if (!cmd) {
|
||||
addReplyError(c,"Invalid command specified");
|
||||
return;
|
||||
} else if (cmd->getkeys_proc == NULL && cmd->firstkey == 0) {
|
||||
addReplyError(c,"The command has no key arguments");
|
||||
addReplyErrorFormat(c,"Invalid command specified");
|
||||
return;
|
||||
} else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
|
||||
((c->argc-2) < -cmd->arity))
|
||||
@@ -3019,15 +2863,11 @@ NULL
|
||||
}
|
||||
|
||||
keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys);
|
||||
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);
|
||||
}
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
|
||||
getKeysFreeResult(keys);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try COMMAND HELP", (char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3039,6 +2879,7 @@ void bytesToHuman(char *s, unsigned long long n) {
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lluB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
sprintf(s,"%.2fK",d);
|
||||
@@ -3068,6 +2909,7 @@ sds genRedisInfoString(char *section) {
|
||||
time_t uptime = server.unixtime-server.stat_starttime;
|
||||
int j;
|
||||
struct rusage self_ru, c_ru;
|
||||
unsigned long lol, bib;
|
||||
int allsections = 0, defsections = 0;
|
||||
int sections = 0;
|
||||
|
||||
@@ -3077,6 +2919,7 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
getrusage(RUSAGE_SELF, &self_ru);
|
||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||
getClientsMaxBuffers(&lol,&bib);
|
||||
|
||||
/* Server */
|
||||
if (allsections || defsections || !strcasecmp(section,"server")) {
|
||||
@@ -3116,7 +2959,6 @@ sds genRedisInfoString(char *section) {
|
||||
"uptime_in_seconds:%jd\r\n"
|
||||
"uptime_in_days:%jd\r\n"
|
||||
"hz:%d\r\n"
|
||||
"configured_hz:%d\r\n"
|
||||
"lru_clock:%ld\r\n"
|
||||
"executable:%s\r\n"
|
||||
"config_file:%s\r\n",
|
||||
@@ -3140,7 +2982,6 @@ sds genRedisInfoString(char *section) {
|
||||
(intmax_t)uptime,
|
||||
(intmax_t)(uptime/(3600*24)),
|
||||
server.hz,
|
||||
server.config_hz,
|
||||
(unsigned long) lruclock,
|
||||
server.executable ? server.executable : "",
|
||||
server.configfile ? server.configfile : "");
|
||||
@@ -3148,17 +2989,15 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
/* Clients */
|
||||
if (allsections || defsections || !strcasecmp(section,"clients")) {
|
||||
size_t maxin, maxout;
|
||||
getExpansiveClientsInfo(&maxin,&maxout);
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
"# Clients\r\n"
|
||||
"connected_clients:%lu\r\n"
|
||||
"client_recent_max_input_buffer:%zu\r\n"
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"client_longest_output_list:%lu\r\n"
|
||||
"client_biggest_input_buf:%lu\r\n"
|
||||
"blocked_clients:%d\r\n",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
maxin, maxout,
|
||||
lol, bib,
|
||||
server.blocked_clients);
|
||||
}
|
||||
|
||||
@@ -3168,7 +3007,6 @@ sds genRedisInfoString(char *section) {
|
||||
char peak_hmem[64];
|
||||
char total_system_hmem[64];
|
||||
char used_memory_lua_hmem[64];
|
||||
char used_memory_scripts_hmem[64];
|
||||
char used_memory_rss_hmem[64];
|
||||
char maxmemory_hmem[64];
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
@@ -3188,7 +3026,6 @@ sds genRedisInfoString(char *section) {
|
||||
bytesToHuman(peak_hmem,server.stat_peak_memory);
|
||||
bytesToHuman(total_system_hmem,total_system_mem);
|
||||
bytesToHuman(used_memory_lua_hmem,memory_lua);
|
||||
bytesToHuman(used_memory_scripts_hmem,server.lua_scripts_mem);
|
||||
bytesToHuman(used_memory_rss_hmem,server.cron_malloc_stats.process_rss);
|
||||
bytesToHuman(maxmemory_hmem,server.maxmemory);
|
||||
|
||||
@@ -3213,9 +3050,6 @@ sds genRedisInfoString(char *section) {
|
||||
"total_system_memory_human:%s\r\n"
|
||||
"used_memory_lua:%lld\r\n"
|
||||
"used_memory_lua_human:%s\r\n"
|
||||
"used_memory_scripts:%lld\r\n"
|
||||
"used_memory_scripts_human:%s\r\n"
|
||||
"number_of_cached_scripts:%lu\r\n"
|
||||
"maxmemory:%lld\r\n"
|
||||
"maxmemory_human:%s\r\n"
|
||||
"maxmemory_policy:%s\r\n"
|
||||
@@ -3227,11 +3061,6 @@ sds genRedisInfoString(char *section) {
|
||||
"rss_overhead_bytes:%zu\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_fragmentation_bytes:%zu\r\n"
|
||||
"mem_not_counted_for_evict:%zu\r\n"
|
||||
"mem_replication_backlog:%zu\r\n"
|
||||
"mem_clients_slaves:%zu\r\n"
|
||||
"mem_clients_normal:%zu\r\n"
|
||||
"mem_aof_buffer:%zu\r\n"
|
||||
"mem_allocator:%s\r\n"
|
||||
"active_defrag_running:%d\r\n"
|
||||
"lazyfree_pending_objects:%zu\r\n",
|
||||
@@ -3253,9 +3082,6 @@ sds genRedisInfoString(char *section) {
|
||||
total_system_hmem,
|
||||
memory_lua,
|
||||
used_memory_lua_hmem,
|
||||
server.lua_scripts_mem,
|
||||
used_memory_scripts_hmem,
|
||||
dictSize(server.lua_scripts),
|
||||
server.maxmemory,
|
||||
maxmemory_hmem,
|
||||
evict_policy,
|
||||
@@ -3267,11 +3093,6 @@ sds genRedisInfoString(char *section) {
|
||||
mh->rss_extra_bytes,
|
||||
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */
|
||||
mh->total_frag_bytes, /* named so for backwards compatibility */
|
||||
freeMemoryGetNotCountedMemory(),
|
||||
mh->repl_backlog,
|
||||
mh->clients_slaves,
|
||||
mh->clients_normal,
|
||||
mh->aof_buffer,
|
||||
ZMALLOC_LIB,
|
||||
server.active_defrag_running,
|
||||
lazyfreeGetPendingObjectsCount()
|
||||
@@ -3561,14 +3382,14 @@ sds genRedisInfoString(char *section) {
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
"# CPU\r\n"
|
||||
"used_cpu_sys:%ld.%06ld\r\n"
|
||||
"used_cpu_user:%ld.%06ld\r\n"
|
||||
"used_cpu_sys_children:%ld.%06ld\r\n"
|
||||
"used_cpu_user_children:%ld.%06ld\r\n",
|
||||
(long)self_ru.ru_stime.tv_sec, (long)self_ru.ru_stime.tv_usec,
|
||||
(long)self_ru.ru_utime.tv_sec, (long)self_ru.ru_utime.tv_usec,
|
||||
(long)c_ru.ru_stime.tv_sec, (long)c_ru.ru_stime.tv_usec,
|
||||
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
||||
"used_cpu_sys:%.2f\r\n"
|
||||
"used_cpu_user:%.2f\r\n"
|
||||
"used_cpu_sys_children:%.2f\r\n"
|
||||
"used_cpu_user_children:%.2f\r\n",
|
||||
(float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
|
||||
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000);
|
||||
}
|
||||
|
||||
/* Command statistics */
|
||||
@@ -4014,11 +3835,9 @@ int main(int argc, char **argv) {
|
||||
spt_init(argc, argv);
|
||||
#endif
|
||||
setlocale(LC_COLLATE,"");
|
||||
tzset(); /* Populates 'timezone' global. */
|
||||
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
|
||||
srand(time(NULL)^getpid());
|
||||
gettimeofday(&tv,NULL);
|
||||
|
||||
char hashseed[16];
|
||||
getRandomHexChars(hashseed,sizeof(hashseed));
|
||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
||||
@@ -4075,7 +3894,7 @@ int main(int argc, char **argv) {
|
||||
configfile = argv[j];
|
||||
server.configfile = getAbsolutePath(configfile);
|
||||
/* Replace the config file in server.exec_argv with
|
||||
* its absolute path. */
|
||||
* its absoulte path. */
|
||||
zfree(server.exec_argv[j]);
|
||||
server.exec_argv[j] = zstrdup(server.configfile);
|
||||
j++;
|
||||
|
||||
+17
-56
@@ -78,14 +78,12 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define C_ERR -1
|
||||
|
||||
/* Static server configuration */
|
||||
#define CONFIG_DEFAULT_DYNAMIC_HZ 1 /* Adapt hz to # of clients.*/
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_MIN_HZ 1
|
||||
#define CONFIG_MAX_HZ 500
|
||||
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* default client timeout: infinite */
|
||||
#define CONFIG_DEFAULT_DBNUM 16
|
||||
#define CONFIG_MAX_LINE 1024
|
||||
#define CRON_DBS_PER_CALL 16
|
||||
@@ -93,7 +91,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define PROTO_SHARED_SELECT_CMDS 10
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define AOF_REWRITE_PERC 100
|
||||
#define AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
@@ -131,7 +129,6 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP NULL
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT 0
|
||||
#define CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
@@ -145,7 +142,6 @@ 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 */
|
||||
@@ -187,9 +183,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
|
||||
#define PROTO_MBULK_BIG_ARG (1024*32)
|
||||
#define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */
|
||||
#define REDIS_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
|
||||
#define LIMIT_PENDING_QUERYBUF (4*1024*1024) /* 4mb */
|
||||
#define AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
|
||||
/* When configuring the server eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + RESERVED_FDS +
|
||||
@@ -622,13 +616,6 @@ typedef struct redisObject {
|
||||
|
||||
struct evictionPoolEntry; /* Defined in evict.c */
|
||||
|
||||
/* This structure is used in order to represent the output buffer of a client,
|
||||
* which is actually a linked list of blocks like that, that is: client->reply. */
|
||||
typedef struct clientReplyBlock {
|
||||
size_t size, used;
|
||||
char buf[];
|
||||
} clientReplyBlock;
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
@@ -675,7 +662,6 @@ typedef struct blockingState {
|
||||
robj *xread_group; /* XREADGROUP group name. */
|
||||
robj *xread_consumer; /* XREADGROUP consumer name. */
|
||||
mstime_t xread_retry_time, xread_retry_ttl;
|
||||
int xread_group_noack;
|
||||
|
||||
/* BLOCKED_WAIT */
|
||||
int numreplicas; /* Number of replicas we are waiting for ACK. */
|
||||
@@ -711,11 +697,9 @@ typedef struct client {
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
size_t qb_pos; /* The position we have read in querybuf. */
|
||||
sds pending_querybuf; /* If this client is flagged as master, this buffer
|
||||
represents the yet not applied portion of the
|
||||
replication stream that we are receiving from
|
||||
the master. */
|
||||
sds pending_querybuf; /* If this is a master, this buffer represents the
|
||||
yet not applied replication stream that we
|
||||
are receiving from the master. */
|
||||
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
|
||||
int argc; /* Num of arguments of current command. */
|
||||
robj **argv; /* Arguments of current command. */
|
||||
@@ -783,7 +767,7 @@ struct sharedObjectsStruct {
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
|
||||
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*rpop, *lpop, *lpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -855,7 +839,6 @@ struct redisMemOverhead {
|
||||
size_t clients_slaves;
|
||||
size_t clients_normal;
|
||||
size_t aof_buffer;
|
||||
size_t lua_caches;
|
||||
size_t overhead_total;
|
||||
size_t dataset;
|
||||
size_t total_keys;
|
||||
@@ -898,13 +881,13 @@ typedef struct rdbSaveInfo {
|
||||
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1}
|
||||
|
||||
struct malloc_stats {
|
||||
typedef 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
|
||||
@@ -928,10 +911,6 @@ struct redisServer {
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
char *executable; /* Absolute executable file path. */
|
||||
char **exec_argv; /* Executable argv vector (copy). */
|
||||
int dynamic_hz; /* Change hz value depending on # of clients. */
|
||||
int config_hz; /* Configured HZ value. May be different than
|
||||
the actual 'hz' field value if dynamic-hz
|
||||
is enabled. */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
@@ -972,7 +951,6 @@ 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 */
|
||||
@@ -1016,7 +994,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 */
|
||||
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
||||
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. */
|
||||
@@ -1068,8 +1046,7 @@ 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 aof rewriting? */
|
||||
int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
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. */
|
||||
@@ -1158,7 +1135,6 @@ struct redisServer {
|
||||
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
|
||||
int repl_serve_stale_data; /* Serve stale data when link is down? */
|
||||
int repl_slave_ro; /* Slave is read only? */
|
||||
int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */
|
||||
time_t repl_down_since; /* Unix time at which link with master went down */
|
||||
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
|
||||
int slave_priority; /* Reported in INFO and used by Sentinel. */
|
||||
@@ -1210,8 +1186,6 @@ struct redisServer {
|
||||
int list_compress_depth;
|
||||
/* time cache */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||
int daylight_active; /* Currently in daylight saving time. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
@@ -1237,7 +1211,6 @@ struct redisServer {
|
||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
client *lua_caller; /* The client running EVAL right now, or NULL */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
@@ -1437,17 +1410,15 @@ 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);
|
||||
void freeClientReplyValue(void *o);
|
||||
void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
char *getClientPeerId(client *client);
|
||||
sds catClientInfoString(sds s, client *client);
|
||||
sds getAllClientsInfoString(int type);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
@@ -1528,7 +1499,6 @@ 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);
|
||||
@@ -1594,11 +1564,6 @@ void startLoading(FILE *fp);
|
||||
void loadingProgress(off_t pos);
|
||||
void stopLoading(void);
|
||||
|
||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||
#define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */
|
||||
int writeCommandsDeniedByDiskError(void);
|
||||
|
||||
/* RDB persistence */
|
||||
#include "rdb.h"
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
@@ -1628,11 +1593,11 @@ void receiveChildInfo(void);
|
||||
#define ZADD_NONE 0
|
||||
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
|
||||
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already existing. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already exisitng. */
|
||||
|
||||
/* Output flags. */
|
||||
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/
|
||||
#define ZADD_NAN (1<<4) /* Only touch elements already existing. */
|
||||
#define ZADD_NAN (1<<4) /* Only touch elements already exisitng. */
|
||||
#define ZADD_ADDED (1<<5) /* The element was new and was added. */
|
||||
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */
|
||||
|
||||
@@ -1688,7 +1653,6 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
|
||||
|
||||
/* Core functions */
|
||||
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
|
||||
size_t freeMemoryGetNotCountedMemory();
|
||||
int freeMemoryIfNeeded(void);
|
||||
int processCommand(client *c);
|
||||
void setupSignalHandlers(void);
|
||||
@@ -1805,8 +1769,6 @@ 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);
|
||||
@@ -1838,7 +1800,6 @@ int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
void slotToKeyFlushAsync(void);
|
||||
size_t lazyfreeGetPendingObjectsCount(void);
|
||||
void freeObjAsync(robj *o);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
+4
-4
@@ -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 {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SLOWLOG HELP", (char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -193,7 +193,7 @@ void sortCommand(client *c) {
|
||||
long limit_start = 0, limit_count = -1, start, end;
|
||||
int j, dontsort = 0, vectorlen;
|
||||
int getop = 0; /* GET operation counter */
|
||||
int int_conversion_error = 0;
|
||||
int int_convertion_error = 0;
|
||||
int syntax_error = 0;
|
||||
robj *sortval, *sortby = NULL, *storekey = NULL;
|
||||
redisSortObject *vector; /* Resulting vector to sort */
|
||||
@@ -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) {
|
||||
if (dontsort == 0) {
|
||||
for (j = 0; j < vectorlen; j++) {
|
||||
robj *byval;
|
||||
if (sortby) {
|
||||
@@ -469,7 +469,7 @@ void sortCommand(client *c) {
|
||||
if (eptr[0] != '\0' || errno == ERANGE ||
|
||||
isnan(vector[j].u.score))
|
||||
{
|
||||
int_conversion_error = 1;
|
||||
int_convertion_error = 1;
|
||||
}
|
||||
} else if (byval->encoding == OBJ_ENCODING_INT) {
|
||||
/* Don't need to decode the object if it's
|
||||
@@ -487,7 +487,9 @@ void sortCommand(client *c) {
|
||||
decrRefCount(byval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dontsort == 0) {
|
||||
server.sort_desc = desc;
|
||||
server.sort_alpha = alpha;
|
||||
server.sort_bypattern = sortby ? 1 : 0;
|
||||
@@ -501,7 +503,7 @@ void sortCommand(client *c) {
|
||||
/* Send command output to the output buffer, performing the specified
|
||||
* GET/DEL/INCR/DECR operations if any. */
|
||||
outputlen = getop ? getop*(end-start+1) : end-start+1;
|
||||
if (int_conversion_error) {
|
||||
if (int_convertion_error) {
|
||||
addReplyError(c,"One or more scores can't be converted into double");
|
||||
} else if (storekey == NULL) {
|
||||
/* STORE option not specified, sent the sorting result to client */
|
||||
|
||||
@@ -108,6 +108,5 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
|
||||
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
|
||||
streamNACK *streamCreateNACK(streamConsumer *consumer);
|
||||
void streamDecodeID(void *buf, streamID *id);
|
||||
int streamCompareID(streamID *a, streamID *b);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -596,9 +596,6 @@ void rpoplpushCommand(client *c) {
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
if (c->lastcmd->proc == brpoplpushCommand) {
|
||||
rewriteClientCommandVector(c,3,shared.rpoplpush,c->argv[1],c->argv[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -516,7 +516,11 @@ void spopWithCountCommand(client *c) {
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
|
||||
/* Transfer the old set to the client. */
|
||||
/* Assign the new set as the key value. */
|
||||
incrRefCount(set); /* Protect the old set value. */
|
||||
dbOverwrite(c->db,c->argv[1],newset);
|
||||
|
||||
/* Tranfer the old set to the client and release it. */
|
||||
setTypeIterator *si;
|
||||
si = setTypeInitIterator(set);
|
||||
while((encoding = setTypeNext(si,&sdsele,&llele)) != -1) {
|
||||
@@ -535,9 +539,7 @@ void spopWithCountCommand(client *c) {
|
||||
decrRefCount(objele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
/* Assign the new set as the key value. */
|
||||
dbOverwrite(c->db,c->argv[1],newset);
|
||||
decrRefCount(set);
|
||||
}
|
||||
|
||||
/* Don't propagate the command itself even if we incremented the
|
||||
|
||||
+54
-145
@@ -207,7 +207,7 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
|
||||
/* Create a new listpack and radix tree node if needed. Note that when
|
||||
* a new listpack is created, we populate it with a "master entry". This
|
||||
* is just a set of fields that is taken as references in order to compress
|
||||
* is just a set of fields that is taken as refernce in order to compress
|
||||
* the stream entries that we'll add inside the listpack.
|
||||
*
|
||||
* Note that while we use the first added entry fields to create
|
||||
@@ -469,7 +469,7 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
|
||||
* iteration is from the start to the end element (inclusive), otherwise
|
||||
* if rev is non-zero, the iteration is reversed.
|
||||
*
|
||||
* Once the iterator is initialized, we iterate like this:
|
||||
* Once the iterator is initalized, we iterate like this:
|
||||
*
|
||||
* streamIterator myiterator;
|
||||
* streamIteratorStart(&myiterator,...);
|
||||
@@ -548,20 +548,13 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
si->master_fields_count = lpGetInteger(si->lp_ele);
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek first field. */
|
||||
si->master_fields_start = si->lp_ele;
|
||||
/* We are now pointing to the first field of the master entry.
|
||||
* We need to seek either the first or the last entry depending
|
||||
* on the direction of the iteration. */
|
||||
if (!si->rev) {
|
||||
/* If we are iterating in normal order, skip the master fields
|
||||
* to seek the first actual entry. */
|
||||
for (uint64_t i = 0; i < si->master_fields_count; i++)
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele);
|
||||
} else {
|
||||
/* If we are iterating in reverse direction, just seek the
|
||||
* last part of the last entry in the listpack (that is, the
|
||||
* fields count). */
|
||||
si->lp_ele = lpLast(si->lp);
|
||||
}
|
||||
/* Skip master fileds to seek the first entry. */
|
||||
for (uint64_t i = 0; i < si->master_fields_count; i++)
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele);
|
||||
/* We are now pointing the zero term of the master entry. If
|
||||
* we are iterating in reverse order, we need to seek the
|
||||
* end of the listpack. */
|
||||
if (si->rev) si->lp_ele = lpLast(si->lp);
|
||||
} else if (si->rev) {
|
||||
/* If we are itereating in the reverse order, and this is not
|
||||
* the first entry emitted for this listpack, then we already
|
||||
@@ -712,19 +705,10 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
|
||||
/* Change the valid/deleted entries count in the master entry. */
|
||||
unsigned char *p = lpFirst(lp);
|
||||
aux = lpGetInteger(p);
|
||||
|
||||
if (aux == 1) {
|
||||
/* If this is the last element in the listpack, we can remove the whole
|
||||
* node. */
|
||||
lpFree(lp);
|
||||
raxRemove(si->stream->rax,si->ri.key,si->ri.key_len,NULL);
|
||||
} else {
|
||||
/* In the base case we alter the counters of valid/deleted entries. */
|
||||
lp = lpReplaceInteger(lp,&p,aux-1);
|
||||
p = lpNext(lp,p); /* Seek deleted field. */
|
||||
aux = lpGetInteger(p);
|
||||
lp = lpReplaceInteger(lp,&p,aux+1);
|
||||
}
|
||||
lp = lpReplaceInteger(lp,&p,aux-1);
|
||||
p = lpNext(lp,p); /* Seek deleted field. */
|
||||
aux = lpGetInteger(p);
|
||||
lp = lpReplaceInteger(lp,&p,aux+1);
|
||||
|
||||
/* Update the number of entries counter. */
|
||||
si->stream->length--;
|
||||
@@ -764,7 +748,6 @@ int streamDeleteItem(stream *s, streamID *id) {
|
||||
streamIteratorRemoveEntry(&si,&myid);
|
||||
deleted = 1;
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -851,7 +834,7 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
|
||||
* given, but currently such a feature is never used by the code base that
|
||||
* will always pass 'spi' and propagate when a group is passed.
|
||||
*
|
||||
* Note that this function is recursive in certain cases. When it's called
|
||||
* 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 entries list. However such a function will then call
|
||||
@@ -922,26 +905,24 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
* 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 group_inserted =
|
||||
raxTryInsert(group->pel,buf,sizeof(buf),nack,NULL);
|
||||
int consumer_inserted =
|
||||
raxTryInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
int retval = 0;
|
||||
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,
|
||||
* or update it if the consumer is the same as before. */
|
||||
if (group_inserted == 0) {
|
||||
* 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 NACK metadata. */
|
||||
/* Update the consumer and idle time. */
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = mstime();
|
||||
nack->delivery_count = 1;
|
||||
nack->delivery_count++;
|
||||
/* Add the entry in the new consumer local PEL. */
|
||||
raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
} else if (group_inserted == 1 && consumer_inserted == 0) {
|
||||
} else if (retval == 1) {
|
||||
serverPanic("NACK half-created. Should not be possible.");
|
||||
}
|
||||
|
||||
@@ -964,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 consumer, fetching the IDs from its PEL.
|
||||
* in the history of the conusmer, 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
|
||||
@@ -1059,20 +1040,15 @@ int string2ull(const char *s, unsigned long long *value) {
|
||||
* to the client, otherwise C_OK is returned. The ID may be in incomplete
|
||||
* form, just stating the milliseconds time part of the stream. In such a case
|
||||
* the missing part is set according to the value of 'missing_seq' parameter.
|
||||
*
|
||||
* The IDs "-" and "+" specify respectively the minimum and maximum IDs
|
||||
* that can be represented. If 'strict' is set to 1, "-" and "+" will be
|
||||
* treated as an invalid ID.
|
||||
* that can be represented.
|
||||
*
|
||||
* If 'c' is set to NULL, no reply is sent to the client. */
|
||||
int streamGenericParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq, int strict) {
|
||||
int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
char buf[128];
|
||||
if (sdslen(o->ptr) > sizeof(buf)-1) goto invalid;
|
||||
memcpy(buf,o->ptr,sdslen(o->ptr)+1);
|
||||
|
||||
if (strict && (buf[0] == '-' || buf[0] == '+') && buf[1] == '\0')
|
||||
goto invalid;
|
||||
|
||||
/* Handle the "-" and "+" special cases. */
|
||||
if (buf[0] == '-' && buf[1] == '\0') {
|
||||
id->ms = 0;
|
||||
@@ -1101,25 +1077,11 @@ invalid:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
|
||||
* 0, to be used when - and + are accetable IDs. */
|
||||
int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
return streamGenericParseIDOrReply(c,o,id,missing_seq,0);
|
||||
}
|
||||
|
||||
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
|
||||
* 1, to be used when we want to return an error if the special IDs + or -
|
||||
* are provided. */
|
||||
int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
return streamGenericParseIDOrReply(c,o,id,missing_seq,1);
|
||||
}
|
||||
|
||||
|
||||
/* XADD key [MAXLEN <count>] <ID or *> [field value] [field value] ... */
|
||||
void xaddCommand(client *c) {
|
||||
streamID id;
|
||||
int id_given = 0; /* Was an ID different than "*" specified? */
|
||||
long long maxlen = -1; /* If left to -1 no trimming is performed. */
|
||||
long long maxlen = 0; /* 0 means no maximum length. */
|
||||
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. */
|
||||
@@ -1143,16 +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 {
|
||||
/* If we are here is a syntax error or a valid ID. */
|
||||
if (streamParseStrictIDOrReply(c,c->argv[i],&id,0) != C_OK) return;
|
||||
if (streamParseIDOrReply(c,c->argv[i],&id,0) != C_OK) return;
|
||||
id_given = 1;
|
||||
break;
|
||||
}
|
||||
@@ -1187,7 +1144,7 @@ void xaddCommand(client *c) {
|
||||
server.dirty++;
|
||||
|
||||
/* Remove older elements if MAXLEN was specified. */
|
||||
if (maxlen >= 0) {
|
||||
if (maxlen) {
|
||||
if (!streamTrimByLength(s,maxlen,approx_maxlen)) {
|
||||
/* If no trimming was performed, for instance because approximated
|
||||
* trimming length was specified, rewrite the MAXLEN argument
|
||||
@@ -1378,14 +1335,6 @@ void xreadCommand(client *c) {
|
||||
}
|
||||
|
||||
if (strcmp(c->argv[i]->ptr,"$") == 0) {
|
||||
if (xreadgroup) {
|
||||
addReplyError(c,"The $ ID is meaningless in the context of "
|
||||
"XREADGROUP: you want to read the history of "
|
||||
"this consumer by specifying a proper ID, or "
|
||||
"use the > ID to get new messages. The $ ID would "
|
||||
"just return an empty result set.");
|
||||
goto cleanup;
|
||||
}
|
||||
if (o) {
|
||||
stream *s = o->ptr;
|
||||
ids[id_idx] = s->last_id;
|
||||
@@ -1395,20 +1344,16 @@ void xreadCommand(client *c) {
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(c->argv[i]->ptr,">") == 0) {
|
||||
if (!xreadgroup) {
|
||||
if (!xreadgroup || groupname == NULL) {
|
||||
addReplyError(c,"The > ID can be specified only when calling "
|
||||
"XREADGROUP using the GROUP <group> "
|
||||
"<consumer> option.");
|
||||
goto cleanup;
|
||||
}
|
||||
/* We use just the maximum ID to signal this is a ">" ID, anyway
|
||||
* the code handling the blocking clients will have to update the
|
||||
* ID later in order to match the changing consumer group last ID. */
|
||||
ids[id_idx].ms = UINT64_MAX;
|
||||
ids[id_idx].seq = UINT64_MAX;
|
||||
ids[id_idx] = group->last_id;
|
||||
continue;
|
||||
}
|
||||
if (streamParseStrictIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
|
||||
if (streamParseIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -1420,36 +1365,9 @@ void xreadCommand(client *c) {
|
||||
if (o == NULL) continue;
|
||||
stream *s = o->ptr;
|
||||
streamID *gt = ids+i; /* ID must be greater than this. */
|
||||
int serve_synchronously = 0;
|
||||
|
||||
/* Check if there are the conditions to serve the client synchronously. */
|
||||
if (groups) {
|
||||
/* If the consumer is blocked on a group, we always serve it
|
||||
* synchronously (serving its local history) if the ID specified
|
||||
* was not the special ">" ID. */
|
||||
if (gt->ms != UINT64_MAX ||
|
||||
gt->seq != UINT64_MAX)
|
||||
{
|
||||
serve_synchronously = 1;
|
||||
} else {
|
||||
/* We also want to serve a consumer in a consumer group
|
||||
* synchronously in case the group top item delivered is smaller
|
||||
* than what the stream has inside. */
|
||||
streamID *last = &groups[i]->last_id;
|
||||
if (streamCompareID(&s->last_id, last) > 0) {
|
||||
serve_synchronously = 1;
|
||||
*gt = *last;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* For consumers without a group, we serve synchronously if we can
|
||||
* actually provide at least one item from the stream. */
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
serve_synchronously = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (serve_synchronously) {
|
||||
if (s->last_id.ms > gt->ms ||
|
||||
(s->last_id.ms == gt->ms && s->last_id.seq > gt->seq))
|
||||
{
|
||||
arraylen++;
|
||||
if (arraylen == 1) arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
/* streamReplyWithRange() handles the 'start' ID as inclusive,
|
||||
@@ -1503,7 +1421,6 @@ void xreadCommand(client *c) {
|
||||
incrRefCount(consumername);
|
||||
c->bpop.xread_group = groupname;
|
||||
c->bpop.xread_consumer = consumername;
|
||||
c->bpop.xread_group_noack = noack;
|
||||
} else {
|
||||
c->bpop.xread_group = NULL;
|
||||
c->bpop.xread_consumer = NULL;
|
||||
@@ -1649,7 +1566,7 @@ void xgroupCommand(client *c) {
|
||||
"CREATE <key> <groupname> <id or $> -- Create a new consumer group.",
|
||||
"SETID <key> <groupname> <id or $> -- Set the current group ID.",
|
||||
"DESTROY <key> <groupname> -- Remove the specified group.",
|
||||
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer.",
|
||||
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified conusmer.",
|
||||
"HELP -- Prints this help.",
|
||||
NULL
|
||||
};
|
||||
@@ -1682,7 +1599,7 @@ NULL
|
||||
streamID id;
|
||||
if (!strcmp(c->argv[4]->ptr,"$")) {
|
||||
id = s->last_id;
|
||||
} else if (streamParseStrictIDOrReply(c,c->argv[4],&id,0) != C_OK) {
|
||||
} else if (streamParseIDOrReply(c,c->argv[4],&id,0) != C_OK) {
|
||||
return;
|
||||
}
|
||||
streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
|
||||
@@ -1728,7 +1645,7 @@ NULL
|
||||
} else if (!strcasecmp(opt,"HELP")) {
|
||||
addReplyHelp(c, help);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReply(c,shared.syntaxerr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1759,7 +1676,7 @@ void xackCommand(client *c) {
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL: it will have a reference to the
|
||||
@@ -2015,9 +1932,9 @@ void xclaimCommand(client *c) {
|
||||
* the client successfully claimed some message, so it should be
|
||||
* executed in a "all or nothing" fashion. */
|
||||
int j;
|
||||
for (j = 5; j < c->argc; j++) {
|
||||
for (j = 4; j < c->argc; j++) {
|
||||
streamID id;
|
||||
if (streamParseStrictIDOrReply(NULL,c->argv[j],&id,0) != C_OK) break;
|
||||
if (streamParseIDOrReply(NULL,c->argv[j],&id,0) != C_OK) break;
|
||||
}
|
||||
int last_id_arg = j-1; /* Next time we iterate the IDs we now the range. */
|
||||
|
||||
@@ -2040,12 +1957,12 @@ void xclaimCommand(client *c) {
|
||||
} else if (!strcasecmp(opt,"TIME") && moreargs) {
|
||||
j++;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&deliverytime,
|
||||
"Invalid TIME option argument for XCLAIM")
|
||||
"Invalid IDLE option argument for XCLAIM")
|
||||
!= C_OK) return;
|
||||
} else if (!strcasecmp(opt,"RETRYCOUNT") && moreargs) {
|
||||
j++;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&retrycount,
|
||||
"Invalid RETRYCOUNT option argument for XCLAIM")
|
||||
"Invalid IDLE option argument for XCLAIM")
|
||||
!= C_OK) return;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unrecognized XCLAIM option '%s'",opt);
|
||||
@@ -2076,7 +1993,7 @@ void xclaimCommand(client *c) {
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL. */
|
||||
@@ -2145,7 +2062,7 @@ void xclaimCommand(client *c) {
|
||||
/* XDEL <key> [<ID1> <ID2> ... <IDN>]
|
||||
*
|
||||
* Removes the specified entries from the stream. Returns the number
|
||||
* of items actually deleted, that may be different from the number
|
||||
* of items actaully deleted, that may be different from the number
|
||||
* of IDs passed in case certain IDs do not exist. */
|
||||
void xdelCommand(client *c) {
|
||||
robj *o;
|
||||
@@ -2156,25 +2073,21 @@ void xdelCommand(client *c) {
|
||||
|
||||
/* We need to sanity check the IDs passed to start. Even if not
|
||||
* a big issue, it is not great that the command is only partially
|
||||
* executed because at some point an invalid ID is parsed. */
|
||||
* executed becuase at some point an invalid ID is parsed. */
|
||||
streamID id;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
}
|
||||
|
||||
/* Actually apply the command. */
|
||||
/* Actaully apply the command. */
|
||||
int deleted = 0;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
streamParseStrictIDOrReply(c,c->argv[j],&id,0); /* Retval already checked. */
|
||||
streamParseIDOrReply(c,c->argv[j],&id,0); /* Retval already checked. */
|
||||
deleted += streamDeleteItem(s,&id);
|
||||
}
|
||||
|
||||
/* Propagate the write if needed. */
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
@@ -2323,20 +2236,18 @@ NULL
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
streamCG *cg = ri.data;
|
||||
addReplyMultiBulkLen(c,8);
|
||||
addReplyMultiBulkLen(c,6);
|
||||
addReplyStatus(c,"name");
|
||||
addReplyBulkCBuffer(c,ri.key,ri.key_len);
|
||||
addReplyStatus(c,"consumers");
|
||||
addReplyLongLong(c,raxSize(cg->consumers));
|
||||
addReplyStatus(c,"pending");
|
||||
addReplyLongLong(c,raxSize(cg->pel));
|
||||
addReplyStatus(c,"last-delivered-id");
|
||||
addReplyStreamID(c,&cg->last_id);
|
||||
}
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
|
||||
/* XINFO STREAM <key> (or the alias XINFO <key>). */
|
||||
addReplyMultiBulkLen(c,14);
|
||||
addReplyMultiBulkLen(c,12);
|
||||
addReplyStatus(c,"length");
|
||||
addReplyLongLong(c,s->length);
|
||||
addReplyStatus(c,"radix-tree-keys");
|
||||
@@ -2345,8 +2256,6 @@ NULL
|
||||
addReplyLongLong(c,s->rax->numnodes);
|
||||
addReplyStatus(c,"groups");
|
||||
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
|
||||
addReplyStatus(c,"last-generated-id");
|
||||
addReplyStreamID(c,&s->last_id);
|
||||
|
||||
/* To emit the first/last entry we us the streamReplyWithRange()
|
||||
* API. */
|
||||
@@ -2363,7 +2272,7 @@ NULL
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
addReplyError(c,"syntax error, try 'XINFO HELP'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -361,7 +361,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
new = o;
|
||||
o->ptr = (void*)((long)value);
|
||||
} else {
|
||||
new = createStringObjectFromLongLongForValue(value);
|
||||
new = createStringObjectFromLongLong(value);
|
||||
if (o) {
|
||||
dbOverwrite(c->db,c->argv[1],new);
|
||||
} else {
|
||||
|
||||
+8
-57
@@ -244,61 +244,6 @@ int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* Update the score of an elmenent inside the sorted set skiplist.
|
||||
* Note that the element must exist and must match 'score'.
|
||||
* This function does not update the score in the hash table side, the
|
||||
* caller should take care of it.
|
||||
*
|
||||
* Note that this function attempts to just update the node, in case after
|
||||
* the score update, the node would be exactly at the same position.
|
||||
* Otherwise the skiplist is modified by removing and re-adding a new
|
||||
* element, which is more costly.
|
||||
*
|
||||
* The function returns the updated element skiplist node pointer. */
|
||||
zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
||||
int i;
|
||||
|
||||
/* We need to seek to element to update to start: this is useful anyway,
|
||||
* we'll have to update or remove it. */
|
||||
x = zsl->header;
|
||||
for (i = zsl->level-1; i >= 0; i--) {
|
||||
while (x->level[i].forward &&
|
||||
(x->level[i].forward->score < curscore ||
|
||||
(x->level[i].forward->score == curscore &&
|
||||
sdscmp(x->level[i].forward->ele,ele) < 0)))
|
||||
{
|
||||
x = x->level[i].forward;
|
||||
}
|
||||
update[i] = x;
|
||||
}
|
||||
|
||||
/* Jump to our element: note that this function assumes that the
|
||||
* element with the matching score exists. */
|
||||
x = x->level[0].forward;
|
||||
serverAssert(x && curscore == x->score && sdscmp(x->ele,ele) == 0);
|
||||
|
||||
/* If the node, after the score update, would be still exactly
|
||||
* at the same position, we can just update the score without
|
||||
* actually removing and re-inserting the element in the skiplist. */
|
||||
if ((x->backward == NULL || x->backward->score < newscore) &&
|
||||
(x->level[0].forward == NULL || x->level[0].forward->score > newscore))
|
||||
{
|
||||
x->score = newscore;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* No way to reuse the old node: we need to remove and insert a new
|
||||
* one at a different place. */
|
||||
zslDeleteNode(zsl, x, update);
|
||||
zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele);
|
||||
/* We reused the old node x->ele SDS string, free the node now
|
||||
* since zslInsert created a new one. */
|
||||
x->ele = NULL;
|
||||
zslFreeNode(x);
|
||||
return newnode;
|
||||
}
|
||||
|
||||
int zslValueGteMin(double value, zrangespec *spec) {
|
||||
return spec->minex ? (value > spec->min) : (value >= spec->min);
|
||||
}
|
||||
@@ -562,7 +507,7 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
||||
* + means the max string possible
|
||||
*
|
||||
* If the string is valid the *dest pointer is set to the redis object
|
||||
* that will be used for the comparison, and ex will be set to 0 or 1
|
||||
* that will be used for the comparision, and ex will be set to 0 or 1
|
||||
* respectively if the item is exclusive or inclusive. C_OK will be
|
||||
* returned.
|
||||
*
|
||||
@@ -1396,7 +1341,13 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
|
||||
/* Remove and re-insert when score changes. */
|
||||
if (score != curscore) {
|
||||
znode = zslUpdateScore(zs->zsl,curscore,ele,score);
|
||||
zskiplistNode *node;
|
||||
serverAssert(zslDelete(zs->zsl,curscore,ele,&node));
|
||||
znode = zslInsert(zs->zsl,score,node->ele);
|
||||
/* We reused the node->ele SDS string, free the node now
|
||||
* since zslInsert created a new one. */
|
||||
node->ele = NULL;
|
||||
zslFreeNode(node);
|
||||
/* Note that we did not removed the original element from
|
||||
* the hash table representing the sorted set, so we just
|
||||
* update the score. */
|
||||
|
||||
+4
-7
@@ -346,7 +346,6 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
int negative = 0;
|
||||
unsigned long long v;
|
||||
|
||||
/* A zero length string is not a valid number. */
|
||||
if (plen == slen)
|
||||
return 0;
|
||||
|
||||
@@ -356,8 +355,6 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Handle negative numbers: just set a flag and continue like if it
|
||||
* was a positive number. Later convert into negative. */
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
@@ -371,11 +368,13 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
if (p[0] >= '1' && p[0] <= '9') {
|
||||
v = p[0]-'0';
|
||||
p++; plen++;
|
||||
} else if (p[0] == '0' && slen == 1) {
|
||||
*value = 0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse all the other digits, checking for overflow at every step. */
|
||||
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||
return 0;
|
||||
@@ -392,8 +391,6 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
if (plen < slen)
|
||||
return 0;
|
||||
|
||||
/* Convert to negative if needed, and do the final overflow check when
|
||||
* converting from unsigned long long to long long. */
|
||||
if (negative) {
|
||||
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||
return 0;
|
||||
@@ -454,7 +451,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
/* Convert a double to a string representation. Returns the number of bytes
|
||||
* required. The representation should always be parsable by strtod(3).
|
||||
* This function does not support human-friendly formatting like ld2string
|
||||
* does. It is intended mainly to be used inside t_zset.c when writing scores
|
||||
* does. It is intented mainly to be used inside t_zset.c when writing scores
|
||||
* into a ziplist representing a sorted set. */
|
||||
int d2string(char *buf, size_t len, double value) {
|
||||
if (isnan(value)) {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "999.999.999"
|
||||
#define REDIS_VERSION "4.9.102"
|
||||
|
||||
+2
-2
@@ -269,7 +269,7 @@
|
||||
* Note that this is not how the data is actually encoded, is just what we
|
||||
* get filled by a function in order to operate more easily. */
|
||||
typedef struct zlentry {
|
||||
unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
|
||||
unsigned int prevrawlensize; /* Bytes used to encode the previos entry len*/
|
||||
unsigned int prevrawlen; /* Previous entry len. */
|
||||
unsigned int lensize; /* Bytes used to encode this entry type/len.
|
||||
For example strings have a 1, 2 or 5 bytes
|
||||
@@ -431,7 +431,7 @@ unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
||||
/* Return the length of the previous element, and the number of bytes that
|
||||
* are used in order to encode the previous element length.
|
||||
* 'ptr' must point to the prevlen prefix of an entry (that encodes the
|
||||
* length of the previous entry in order to navigate the elements backward).
|
||||
* length of the previos entry in order to navigate the elements backward).
|
||||
* The length of the previous entry is stored in 'prevlen', the number of
|
||||
* bytes needed to encode the previous entry length are stored in
|
||||
* 'prevlensize'. */
|
||||
|
||||
+2
-5
@@ -165,7 +165,7 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
|
||||
*((size_t*)newptr) = size;
|
||||
update_zmalloc_stat_free(oldsize);
|
||||
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
|
||||
update_zmalloc_stat_alloc(size);
|
||||
return (char*)newptr+PREFIX_SIZE;
|
||||
#endif
|
||||
}
|
||||
@@ -182,9 +182,6 @@ size_t zmalloc_size(void *ptr) {
|
||||
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
|
||||
return size+PREFIX_SIZE;
|
||||
}
|
||||
size_t zmalloc_usable(void *ptr) {
|
||||
return zmalloc_size(ptr)-PREFIX_SIZE;
|
||||
}
|
||||
#endif
|
||||
|
||||
void zfree(void *ptr) {
|
||||
@@ -383,7 +380,7 @@ size_t zmalloc_get_private_dirty(long pid) {
|
||||
}
|
||||
|
||||
/* Returns the size of physical memory (RAM) in bytes.
|
||||
* It looks ugly, but this is the cleanest way to achieve cross platform results.
|
||||
* It looks ugly, but this is the cleanest way to achive cross platform results.
|
||||
* Cleaned up from:
|
||||
*
|
||||
* http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
|
||||
|
||||
@@ -63,11 +63,6 @@
|
||||
|
||||
#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
|
||||
@@ -98,9 +93,6 @@ void *zmalloc_no_tcache(size_t size);
|
||||
|
||||
#ifndef HAVE_MALLOC_SIZE
|
||||
size_t zmalloc_size(void *ptr);
|
||||
size_t zmalloc_usable(void *ptr);
|
||||
#else
|
||||
#define zmalloc_usable(p) zmalloc_size(p)
|
||||
#endif
|
||||
|
||||
#endif /* __ZMALLOC_H */
|
||||
|
||||
@@ -92,80 +92,3 @@ test "Node #10 should eventually replicate node #5" {
|
||||
fail "#10 didn't became slave of #5"
|
||||
}
|
||||
}
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
# Create a cluster with 3 master and 15 slaves, so that we have 5
|
||||
# slaves for eatch master.
|
||||
test "Create a 3 nodes cluster" {
|
||||
create_cluster 3 15
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "The first master has actually 5 slaves" {
|
||||
assert {[llength [lindex [R 0 role] 2]] == 5}
|
||||
}
|
||||
|
||||
test {Slaves of #0 are instance #3, #6, #9, #12 and #15 as expected} {
|
||||
set port0 [get_instance_attrib redis 0 port]
|
||||
assert {[lindex [R 3 role] 2] == $port0}
|
||||
assert {[lindex [R 6 role] 2] == $port0}
|
||||
assert {[lindex [R 9 role] 2] == $port0}
|
||||
assert {[lindex [R 12 role] 2] == $port0}
|
||||
assert {[lindex [R 15 role] 2] == $port0}
|
||||
}
|
||||
|
||||
test {Instance #3, #6, #9, #12 and #15 synced with the master} {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 3 master_link_status] eq {up} &&
|
||||
[RI 6 master_link_status] eq {up} &&
|
||||
[RI 9 master_link_status] eq {up} &&
|
||||
[RI 12 master_link_status] eq {up} &&
|
||||
[RI 15 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Instance #3 or #6 or #9 or #12 or #15 master link status is not up"
|
||||
}
|
||||
}
|
||||
|
||||
proc master_detected {instances} {
|
||||
foreach instance [dict keys $instances] {
|
||||
if {[RI $instance role] eq {master}} {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
test "New Master down consecutively" {
|
||||
set instances [dict create 0 1 3 1 6 1 9 1 12 1 15 1]
|
||||
|
||||
set loops [expr {[dict size $instances]-1}]
|
||||
for {set i 0} {$i < $loops} {incr i} {
|
||||
set master_id -1
|
||||
foreach instance [dict keys $instances] {
|
||||
if {[RI $instance role] eq {master}} {
|
||||
set master_id $instance
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if {$master_id eq -1} {
|
||||
fail "no master detected, #loop $i"
|
||||
}
|
||||
|
||||
set instances [dict remove $instances $master_id]
|
||||
|
||||
kill_instance redis $master_id
|
||||
wait_for_condition 1000 50 {
|
||||
[master_detected $instances]
|
||||
} else {
|
||||
failover "No failover detected when master $master_id fails"
|
||||
}
|
||||
|
||||
assert_cluster_state ok
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ start_server {} {
|
||||
|
||||
set cycle 1
|
||||
while {([clock seconds]-$start_time) < $duration} {
|
||||
test "PSYNC2: --- CYCLE $cycle ---" {}
|
||||
incr cycle
|
||||
test "PSYNC2: --- CYCLE $cycle ---" {
|
||||
incr cycle
|
||||
}
|
||||
|
||||
# Create a random replication layout.
|
||||
# Start with switching master (this simulates a failover).
|
||||
@@ -138,11 +139,6 @@ start_server {} {
|
||||
}
|
||||
assert {$sum == 4}
|
||||
}
|
||||
|
||||
# Limit anyway the maximum number of cycles. This is useful when the
|
||||
# test is skipped via --only option of the test suite. In that case
|
||||
# we don't want to see many seconds of this test being just skipped.
|
||||
if {$cycle > 50} break
|
||||
}
|
||||
|
||||
test "PSYNC2: Bring the master back again for next test" {
|
||||
|
||||
@@ -39,25 +39,6 @@ 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 {}
|
||||
|
||||
@@ -11,7 +11,7 @@ proc stop_bg_complex_data {handle} {
|
||||
# partial resyncs attempts, all this while flooding the master with
|
||||
# write queries.
|
||||
#
|
||||
# You can specify backlog size, ttl, delay before reconnection, test duration
|
||||
# You can specifiy backlog size, ttl, delay before reconnection, test duration
|
||||
# in seconds, and an additional condition to verify at the end.
|
||||
#
|
||||
# If reconnect is > 0, the test actually try to break the connection and
|
||||
|
||||
@@ -266,47 +266,3 @@ foreach dl {no yes} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set load_handle0 [start_write_load $master_host $master_port 3]
|
||||
start_server {} {
|
||||
test "Master stream is correctly processed while the slave has a script in -BUSY state" {
|
||||
set slave [srv 0 client]
|
||||
puts [srv 0 port]
|
||||
$slave config set lua-time-limit 500
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
# Wait for the slave to be online
|
||||
wait_for_condition 500 100 {
|
||||
[lindex [$slave role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Slave still not connected after some time"
|
||||
}
|
||||
|
||||
# Wait some time to make sure the master is sending data
|
||||
# to the slave.
|
||||
after 5000
|
||||
|
||||
# Stop the ability of the slave to process data by sendig
|
||||
# a script that will put it in BUSY state.
|
||||
$slave eval {for i=1,3000000000 do end} 0
|
||||
|
||||
# Wait some time again so that more master stream will
|
||||
# be processed.
|
||||
after 2000
|
||||
|
||||
# Stop the write load
|
||||
stop_write_load $load_handle0
|
||||
|
||||
# number of keys
|
||||
wait_for_condition 500 100 {
|
||||
[$master debug digest] eq [$slave debug digest]
|
||||
} else {
|
||||
fail "Different datasets between slave and master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ test "Basic failover works if the master is down" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
@@ -108,7 +108,7 @@ test "Failover works if we configure for absolute agreement" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
|
||||
@@ -16,7 +16,7 @@ test "We can failover with Sentinel 1 crashed" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Sentinel $id did not receive failover info"
|
||||
fail "Sentinel $id did not received failover info"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ test "After Sentinel 1 is restarted, its config gets updated" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Restarted Sentinel did not receive failover info"
|
||||
fail "Restarted Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ proc 02_crash_and_failover {} {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
|
||||
@@ -12,7 +12,7 @@ test "Manual failover works" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
|
||||
@@ -66,13 +66,3 @@ 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,12 +276,6 @@ 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} {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
set ::num_tests 0
|
||||
set ::num_passed 0
|
||||
set ::num_failed 0
|
||||
set ::num_skipped 0
|
||||
set ::num_aborted 0
|
||||
set ::tests_failed {}
|
||||
|
||||
proc fail {msg} {
|
||||
@@ -70,26 +68,10 @@ proc test {name code {okpattern undefined}} {
|
||||
# abort if tagged with a tag to deny
|
||||
foreach tag $::denytags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
incr ::num_aborted
|
||||
send_data_packet $::test_server_fd ignore $name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# abort if test name in skiptests
|
||||
if {[lsearch $::skiptests $name] >= 0} {
|
||||
incr ::num_skipped
|
||||
send_data_packet $::test_server_fd skip $name
|
||||
return
|
||||
}
|
||||
|
||||
# abort if test name in skiptests
|
||||
if {[llength $::only_tests] > 0 && [lsearch $::only_tests $name] < 0} {
|
||||
incr ::num_skipped
|
||||
send_data_packet $::test_server_fd skip $name
|
||||
return
|
||||
}
|
||||
|
||||
# check if tagged with at least 1 tag to allow when there *is* a list
|
||||
# of tags to allow, because default policy is to run everything
|
||||
if {[llength $::allowtags] > 0} {
|
||||
@@ -100,8 +82,6 @@ proc test {name code {okpattern undefined}} {
|
||||
}
|
||||
}
|
||||
if {$matched < 1} {
|
||||
incr ::num_aborted
|
||||
send_data_packet $::test_server_fd ignore $name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,19 +375,3 @@ proc start_write_load {host port seconds} {
|
||||
proc stop_write_load {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
proc K { x y } { set x }
|
||||
|
||||
# Shuffle a list. From Tcl wiki. Originally from Steve Cohen that improved
|
||||
# other versions. Code should be under public domain.
|
||||
proc lshuffle {list} {
|
||||
set n [llength $list]
|
||||
while {$n>0} {
|
||||
set j [expr {int(rand()*$n)}]
|
||||
lappend slist [lindex $list $j]
|
||||
incr n -1
|
||||
set temp [lindex $list $n]
|
||||
set list [lreplace [K $list [set list {}]] $j $j $temp]
|
||||
}
|
||||
return $slist
|
||||
}
|
||||
|
||||
+2
-90
@@ -61,7 +61,6 @@ set ::all_tests {
|
||||
unit/hyperloglog
|
||||
unit/lazyfree
|
||||
unit/wait
|
||||
unit/pendingquerybuf
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
@@ -74,11 +73,7 @@ set ::stack_logging 0
|
||||
set ::verbose 0
|
||||
set ::quiet 0
|
||||
set ::denytags {}
|
||||
set ::skiptests {}
|
||||
set ::allowtags {}
|
||||
set ::only_tests {}
|
||||
set ::single_tests {}
|
||||
set ::skip_till ""
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
@@ -87,10 +82,6 @@ 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 ::stop_on_failure 0
|
||||
set ::loop 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
|
||||
@@ -184,9 +175,6 @@ 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.*]}
|
||||
@@ -236,7 +224,6 @@ 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
|
||||
@@ -261,8 +248,6 @@ proc accept_test_clients {fd addr port} {
|
||||
# testing: just used to signal that a given test started.
|
||||
# ok: a test was executed with success.
|
||||
# err: a test was executed with an error.
|
||||
# skip: a test was skipped by skipfile or individual test options.
|
||||
# ignore: a test was skipped by a group tag.
|
||||
# exception: there was a runtime exception while executing the test.
|
||||
# done: all the specified test file was processed, this test client is
|
||||
# ready to accept a new task.
|
||||
@@ -291,24 +276,11 @@ proc read_from_test_client fd {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
}
|
||||
set ::active_clients_task($fd) "(OK) $data"
|
||||
} elseif {$status eq {skip}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr yellow $status]\]: $data"
|
||||
}
|
||||
} elseif {$status eq {ignore}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr cyan $status]\]: $data"
|
||||
}
|
||||
} elseif {$status eq {err}} {
|
||||
set err "\[[colorstr red $status]\]: $data"
|
||||
puts $err
|
||||
lappend ::failed_tests $err
|
||||
set ::active_clients_task($fd) "(ERR) $data"
|
||||
if {$::stop_on_failure} {
|
||||
puts -nonewline "(Test stopped, press enter to continue)"
|
||||
flush stdout
|
||||
gets stdin
|
||||
}
|
||||
} elseif {$status eq {exception}} {
|
||||
puts "\[[colorstr red $status]\]: $data"
|
||||
kill_clients
|
||||
@@ -371,9 +343,6 @@ proc signal_idle_client fd {
|
||||
send_data_packet $fd run [lindex $::all_tests $::next_test]
|
||||
lappend ::active_clients $fd
|
||||
incr ::next_test
|
||||
if {$::loop && $::next_test == [llength $::all_tests]} {
|
||||
set ::next_test 0
|
||||
}
|
||||
} else {
|
||||
lappend ::idle_clients $fd
|
||||
if {[llength $::active_clients] == 0} {
|
||||
@@ -436,19 +405,11 @@ proc print_help_screen {} {
|
||||
"--stack-logging Enable OSX leaks/malloc stack logging."
|
||||
"--accurate Run slow randomized tests for more iterations."
|
||||
"--quiet Don't show individual tests."
|
||||
"--single <unit> Just execute the specified unit (see next option). this option can be repeated."
|
||||
"--single <unit> Just execute the specified unit (see next option)."
|
||||
"--list-tests List all the available test units."
|
||||
"--only <test> Just execute the specified test by test name. this option can be repeated."
|
||||
"--skip-till <unit> Skip all units until (and including) the specified one."
|
||||
"--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."
|
||||
"--config <k> <v> Extra config file argument."
|
||||
"--skipfile <file> Name of a file containing test names that should be skipped (one per line)."
|
||||
"--dont-clean Don't delete redis log files after the run."
|
||||
"--stop Blocks once the first test fails."
|
||||
"--loop Execute the specified set of tests forever."
|
||||
"--wait-server Wait after server is started (so that you can attach a debugger)."
|
||||
"--help Print this help screen."
|
||||
} "\n"]
|
||||
}
|
||||
@@ -466,17 +427,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
incr j
|
||||
} elseif {$opt eq {--config}} {
|
||||
set arg2 [lindex $argv [expr $j+2]]
|
||||
lappend ::global_overrides $arg
|
||||
lappend ::global_overrides $arg2
|
||||
incr j 2
|
||||
} elseif {$opt eq {--skipfile}} {
|
||||
incr j
|
||||
set fp [open $arg r]
|
||||
set file_data [read $fp]
|
||||
close $fp
|
||||
set ::skiptests [split $file_data "\n"]
|
||||
} elseif {$opt eq {--valgrind}} {
|
||||
set ::valgrind 1
|
||||
} elseif {$opt eq {--stack-logging}} {
|
||||
@@ -497,13 +447,7 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--force-failure}} {
|
||||
set ::force_failure 1
|
||||
} elseif {$opt eq {--single}} {
|
||||
lappend ::single_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--only}} {
|
||||
lappend ::only_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--skiptill}} {
|
||||
set ::skip_till $arg
|
||||
set ::all_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--list-tests}} {
|
||||
foreach t $::all_tests {
|
||||
@@ -519,14 +463,6 @@ 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 {--stop}} {
|
||||
set ::stop_on_failure 1
|
||||
} elseif {$opt eq {--loop}} {
|
||||
set ::loop 1
|
||||
} elseif {$opt eq {--timeout}} {
|
||||
set ::timeout $arg
|
||||
incr j
|
||||
@@ -539,30 +475,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
|
||||
# If --skil-till option was given, we populate the list of single tests
|
||||
# to run with everything *after* the specified unit.
|
||||
if {$::skip_till != ""} {
|
||||
set skipping 1
|
||||
foreach t $::all_tests {
|
||||
if {$skipping == 0} {
|
||||
lappend ::single_tests $t
|
||||
}
|
||||
if {$t == $::skip_till} {
|
||||
set skipping 0
|
||||
}
|
||||
}
|
||||
if {$skipping} {
|
||||
puts "test $::skip_till not found"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Override the list of tests with the specific tests we want to run
|
||||
# in case there was some filter, that is --single or --skip-till options.
|
||||
if {[llength $::single_tests] > 0} {
|
||||
set ::all_tests $::single_tests
|
||||
}
|
||||
|
||||
proc attach_to_replication_stream {} {
|
||||
set s [socket [srv 0 "host"] [srv 0 "port"]]
|
||||
fconfigure $s -translation binary
|
||||
|
||||
+1
-34
@@ -25,39 +25,6 @@ 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 >= 2990 && $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 <= 1010}
|
||||
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
|
||||
@@ -279,7 +246,7 @@ start_server {tags {"dump"}} {
|
||||
set e
|
||||
} {*empty string*}
|
||||
|
||||
test {MIGRATE with multiple keys migrate just existing ones} {
|
||||
test {MIGRATE with mutliple keys migrate just existing ones} {
|
||||
set first [srv 0 client]
|
||||
r set key1 "v1"
|
||||
r set key2 "v2"
|
||||
|
||||
@@ -121,7 +121,7 @@ start_server {tags {"expire"}} {
|
||||
list $a $b
|
||||
} {somevalue {}}
|
||||
|
||||
test {TTL returns time to live in seconds} {
|
||||
test {TTL returns tiem to live in seconds} {
|
||||
r del x
|
||||
r setex x 10 somevalue
|
||||
set ttl [r ttl x]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
start_server {tags {"introspection"}} {
|
||||
test {CLIENT LIST} {
|
||||
r client list
|
||||
} {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
|
||||
} {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
|
||||
|
||||
test {MONITOR can log executed commands} {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
@@ -142,101 +142,3 @@ start_server {tags {"maxmemory"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline} {
|
||||
start_server {tags {"maxmemory"}} {
|
||||
start_server {} {
|
||||
set slave_pid [s process_id]
|
||||
test "$test_name" {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
set slave_port [srv 0 port]
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
|
||||
# add 100 keys of 100k (10MB total)
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
$master setrange "key:$j" 100000 asdf
|
||||
}
|
||||
|
||||
# make sure master doesn't disconnect slave because of timeout
|
||||
$master config set repl-timeout 300 ;# 5 minutes
|
||||
$master config set maxmemory-policy allkeys-random
|
||||
$master config set client-output-buffer-limit "slave 100000000 100000000 300"
|
||||
$master config set repl-backlog-size [expr {10*1024}]
|
||||
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
|
||||
# measure used memory after the slave connected and set maxmemory
|
||||
set orig_used [s -1 used_memory]
|
||||
set orig_client_buf [s -1 mem_clients_normal]
|
||||
set orig_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set orig_used_no_repl [expr {$orig_used - $orig_mem_not_counted_for_evict}]
|
||||
set limit [expr {$orig_used - $orig_mem_not_counted_for_evict + 20*1024}]
|
||||
|
||||
if {$limit_memory==1} {
|
||||
$master config set maxmemory $limit
|
||||
}
|
||||
|
||||
# put the slave to sleep
|
||||
set rd_slave [redis_deferring_client]
|
||||
exec kill -SIGSTOP $slave_pid
|
||||
|
||||
# send some 10mb worth of commands that don't increase the memory usage
|
||||
if {$pipeline == 1} {
|
||||
set rd_master [redis_deferring_client -1]
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
$rd_master setrange key:0 0 [string repeat A $payload_len]
|
||||
}
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
#$rd_master read
|
||||
}
|
||||
} else {
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
$master setrange key:0 0 [string repeat A $payload_len]
|
||||
}
|
||||
}
|
||||
|
||||
set new_used [s -1 used_memory]
|
||||
set slave_buf [s -1 mem_clients_slaves]
|
||||
set client_buf [s -1 mem_clients_normal]
|
||||
set mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set used_no_repl [expr {$new_used - $mem_not_counted_for_evict}]
|
||||
set delta [expr {($used_no_repl - $client_buf) - ($orig_used_no_repl - $orig_client_buf)}]
|
||||
|
||||
assert {[$master dbsize] == 100}
|
||||
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
|
||||
assert {$delta < 50*1024 && $delta > -50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
|
||||
$master client kill type slave
|
||||
set killed_used [s -1 used_memory]
|
||||
set killed_slave_buf [s -1 mem_clients_slaves]
|
||||
set killed_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict}]
|
||||
set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}]
|
||||
assert {$killed_slave_buf == 0}
|
||||
assert {$delta_no_repl > -50*1024 && $delta_no_repl < 50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
|
||||
}
|
||||
# unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server
|
||||
exec kill -SIGCONT $slave_pid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# test that slave buffer are counted correctly
|
||||
# we wanna use many small commands, and we don't wanna wait long
|
||||
# so we need to use a pipeline (redis_deferring_client)
|
||||
# that may cause query buffer to fill and induce eviction, so we disable it
|
||||
test_slave_buffers {slave buffer are counted correctly} 1000000 10 0 1
|
||||
|
||||
# test that slave buffer don't induce eviction
|
||||
# test again with fewer (and bigger) commands without pipeline, but with eviction
|
||||
test_slave_buffers "slave buffer don't induce eviction" 100000 100 1 0
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ start_server {tags {"defrag"}} {
|
||||
test "Active defrag" {
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 65
|
||||
r config set active-defrag-cycle-min 25
|
||||
r config set active-defrag-cycle-max 75
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 100mb
|
||||
@@ -66,10 +66,9 @@ start_server {tags {"defrag"}} {
|
||||
}
|
||||
|
||||
# Wait for the active defrag to stop working.
|
||||
wait_for_condition 150 100 {
|
||||
wait_for_condition 100 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
@@ -98,15 +97,10 @@ 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]
|
||||
@@ -115,9 +109,8 @@ 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 < 50000} {incr j} {
|
||||
for {set j 0} {$j < 40000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
|
||||
@@ -141,7 +134,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 500010}
|
||||
assert {[r dbsize] == 500008}
|
||||
|
||||
# create some fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
@@ -150,7 +143,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 250010}
|
||||
assert {[r dbsize] == 250008}
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
@@ -162,7 +155,6 @@ 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)
|
||||
@@ -176,7 +168,6 @@ start_server {tags {"defrag"}} {
|
||||
wait_for_condition 500 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
@@ -201,12 +192,10 @@ start_server {tags {"defrag"}} {
|
||||
assert {$frag < 1.1}
|
||||
# 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 <= 120}
|
||||
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}
|
||||
} {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
proc info_memory {r property} {
|
||||
if {[regexp "\r\n$property:(.*?)\r\n" [{*}$r info memory] _ value]} {
|
||||
set _ $value
|
||||
}
|
||||
}
|
||||
|
||||
proc prepare_value {size} {
|
||||
set _v "c"
|
||||
for {set i 1} {$i < $size} {incr i} {
|
||||
append _v 0
|
||||
}
|
||||
return $_v
|
||||
}
|
||||
|
||||
start_server {tags {"wait"}} {
|
||||
start_server {} {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
set slave_port [srv 0 port]
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
|
||||
test "pending querybuf: check size of pending_querybuf after set a big value" {
|
||||
$slave slaveof $master_host $master_port
|
||||
set _v [prepare_value [expr 32*1024*1024]]
|
||||
$master set key $_v
|
||||
after 2000
|
||||
set m_usedmemory [info_memory $master used_memory]
|
||||
set s_usedmemory [info_memory $slave used_memory]
|
||||
if { $s_usedmemory > $m_usedmemory + 10*1024*1024 } {
|
||||
fail "the used_memory of slave is too larger than master.Master:$m_usedmemory Slave:$s_usedmemory"
|
||||
}
|
||||
}
|
||||
}}
|
||||
+1
-1
@@ -246,7 +246,7 @@ start_server {tags {"scan"}} {
|
||||
array set found {}
|
||||
|
||||
# Populate the set
|
||||
set numele [expr {101+[randomInt 1000]}]
|
||||
set numele [expr {100+[randomInt 1000]}]
|
||||
for {set j 0} {$j < $numele} {incr j} {
|
||||
r sadd set $j
|
||||
if {$j >= 100} {
|
||||
|
||||
@@ -517,7 +517,7 @@ start_server {tags {"scripting"}} {
|
||||
# Note: keep this test at the end of this server stanza because it
|
||||
# kills the server.
|
||||
test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
|
||||
# The server could be still unresponding to normal commands.
|
||||
# The server sould be still unresponding to normal commands.
|
||||
catch {r ping} e
|
||||
assert_match {BUSY*} $e
|
||||
catch {r shutdown nosave}
|
||||
|
||||
@@ -78,14 +78,4 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
|
||||
set e [lindex [r slowlog get] 0]
|
||||
assert_equal {lastentry_client} [lindex $e 5]
|
||||
}
|
||||
|
||||
test {SLOWLOG - can be disabled} {
|
||||
r config set slowlog-log-slower-than 1
|
||||
r slowlog reset
|
||||
assert_equal [r slowlog len] 1
|
||||
r config set slowlog-log-slower-than -1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,19 +81,4 @@ start_server {
|
||||
# just ID2.
|
||||
assert {[r XACK mystream mygroup $id1 $id2] eq 1}
|
||||
}
|
||||
|
||||
test {PEL NACK reassignment after XGROUP SETID event} {
|
||||
r del events
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xgroup create events g1 $
|
||||
r xadd events * f1 v1
|
||||
set c [llength [lindex [r xreadgroup group g1 c1 streams events >] 0 1]]
|
||||
assert {$c == 1}
|
||||
r xgroup setid events g1 -
|
||||
set c [llength [lindex [r xreadgroup group g1 c2 streams events >] 0 1]]
|
||||
assert {$c == 5}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,53 +234,6 @@ start_server {
|
||||
assert {[lindex $res 0 1 1 1] eq {field two}}
|
||||
}
|
||||
|
||||
test {XDEL basic test} {
|
||||
r del somestream
|
||||
r xadd somestream * foo value0
|
||||
set id [r xadd somestream * foo value1]
|
||||
r xadd somestream * foo value2
|
||||
r xdel somestream $id
|
||||
assert {[r xlen somestream] == 2}
|
||||
set result [r xrange somestream - +]
|
||||
assert {[lindex $result 0 1 1] eq {value0}}
|
||||
assert {[lindex $result 1 1 1] eq {value2}}
|
||||
}
|
||||
|
||||
# Here the idea is to check the consistency of the stream data structure
|
||||
# as we remove all the elements down to zero elements.
|
||||
test {XDEL fuzz test} {
|
||||
r del somestream
|
||||
set ids {}
|
||||
set x 0; # Length of the stream
|
||||
while 1 {
|
||||
lappend ids [r xadd somestream * item $x]
|
||||
incr x
|
||||
# Add enough elements to have a few radix tree nodes inside the stream.
|
||||
if {[dict get [r xinfo stream somestream] radix-tree-keys] > 20} break
|
||||
}
|
||||
|
||||
# Now remove all the elements till we reach an empty stream
|
||||
# and after every deletion, check that the stream is sane enough
|
||||
# to report the right number of elements with XRANGE: this will also
|
||||
# force accessing the whole data structure to check sanity.
|
||||
assert {[r xlen somestream] == $x}
|
||||
|
||||
# We want to remove elements in random order to really test the
|
||||
# implementation in a better way.
|
||||
set ids [lshuffle $ids]
|
||||
foreach id $ids {
|
||||
assert {[r xdel somestream $id] == 1}
|
||||
incr x -1
|
||||
assert {[r xlen somestream] == $x}
|
||||
# The test would be too slow calling XRANGE for every iteration.
|
||||
# Do it every 100 removal.
|
||||
if {$x % 100 == 0} {
|
||||
set res [r xrange somestream - +]
|
||||
assert {[llength $res] == $x}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {XRANGE fuzzing} {
|
||||
set low_id [lindex $items 0 0]
|
||||
set high_id [lindex $items end 0]
|
||||
|
||||
@@ -84,7 +84,7 @@ start_server {tags {"zset"}} {
|
||||
set err
|
||||
} {ERR*}
|
||||
|
||||
test "ZADD NX with non existing key" {
|
||||
test "ZADD NX with non exisitng key" {
|
||||
r del ztmp
|
||||
r zadd ztmp nx 10 x 20 y 30 z
|
||||
assert {[r zcard ztmp] == 3}
|
||||
@@ -1185,30 +1185,4 @@ start_server {tags {"zset"}} {
|
||||
stressers ziplist
|
||||
stressers skiplist
|
||||
}
|
||||
|
||||
test {ZSET skiplist order consistency when elements are moved} {
|
||||
set original_max [lindex [r config get zset-max-ziplist-entries] 1]
|
||||
r config set zset-max-ziplist-entries 0
|
||||
for {set times 0} {$times < 10} {incr times} {
|
||||
r del zset
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
r zadd zset [randomInt 50] ele-[randomInt 10]
|
||||
}
|
||||
|
||||
# Make sure that element ordering is correct
|
||||
set prev_element {}
|
||||
set prev_score -1
|
||||
foreach {element score} [r zrange zset 0 -1 WITHSCORES] {
|
||||
# Assert that elements are in increasing ordering
|
||||
assert {
|
||||
$prev_score < $score ||
|
||||
($prev_score == $score &&
|
||||
[string compare $prev_element $element] == -1)
|
||||
}
|
||||
set prev_element $element
|
||||
set prev_score $score
|
||||
}
|
||||
}
|
||||
r config set zset-max-ziplist-entries $original_max
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ rehashing.c
|
||||
|
||||
Visually show buckets in the two hash tables between rehashings. Also stress
|
||||
test getRandomKeys() implementation, that may actually disappear from
|
||||
Redis soon, however visualization some code is reusable in new bugs
|
||||
Redis soon, however visualizaiton some code is reusable in new bugs
|
||||
investigation.
|
||||
|
||||
Compile with:
|
||||
|
||||
Reference in New Issue
Block a user