Compare commits

..
Author SHA1 Message Date
antirez 484af8ed53 Drop useless line from replicationCacheMaster(). 2020-05-27 17:08:51 +02:00
antirez 484cfc3d76 Another meaningful offset test removed. 2020-05-27 12:50:02 +02:00
antirez 32d0df0c1f Remove the PSYNC2 meaningful offset test. 2020-05-27 12:47:34 +02:00
antirez 22472fe5a1 Remove the meaningful offset feature.
After a closer look, the Redis core devleopers all believe that this was
too fragile, caused many bugs that we didn't expect and that were very
hard to track. Better to find an alternative solution that is simpler.
2020-05-27 12:06:33 +02:00
antirez 325409a011 Set a protocol error if master use the inline protocol.
We want to react a bit more aggressively if we sense that the master is
sending us some corrupted stream. By setting the protocol error we both
ensure that the replica will disconnect, and avoid caching the master so
that a full SYNC will be required. This is protective against
replication bugs.
2020-05-27 11:45:49 +02:00
antirez 94c026cd19 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-26 23:55:52 +02:00
antirez 7d35939206 Replication: log backlog creation event. 2020-05-26 23:55:18 +02:00
Salvatore SanfilippoandGitHub caf7c50408 Merge pull request #7328 from oranagra/daily_tls_test
avoid using sendfile if tls-replication is enabled
2020-05-26 13:19:55 +02:00
Oran Agra 1965c0099e daily CI test with tls 2020-05-26 14:00:26 +03:00
Oran Agra b9abecfc4c avoid using sendfile if tls-replication is enabled
this obviously broke the tests, but went unnoticed so far since tls
wasn't often tested.
2020-05-26 13:52:06 +03:00
antirez 091fb64681 Test: PSYNC2 test can now show server logs. 2020-05-25 20:26:29 +02:00
antirez 92a3ff6168 Clarify what is happening in PR #7320. 2020-05-25 11:47:38 +02:00
Salvatore SanfilippoandGitHub 9e055a0f1f Merge pull request #7320 from soloestoy/second-replid-offset-should-be-meaningful-offset
PSYNC2: second_replid_offset should be real meaningful offset
2020-05-25 11:37:25 +02:00
zhaozhao.zz eec769be59 PSYNC2: second_replid_offset should be real meaningful offset
After adjustMeaningfulReplOffset(), all the other related variable
should be updated, including server.second_replid_offset.

Or the old version redis like 5.0 may receive wrong data from
replication stream, cause redis 5.0 can sync with redis 6.0,
but doesn't know meaningful offset.
2020-05-25 11:17:54 +08:00
Salvatore SanfilippoandGitHub b89e6d74fa Merge pull request #7315 from oranagra/ci-32bit
add CI for 32bit build
2020-05-24 09:50:04 +02:00
Oran Agra c1da9420c7 add CI for 32bit build 2020-05-24 09:35:45 +03:00
antirez adc5df1bc3 Make disconnectSlaves() synchronous in the base case.
Otherwise we run into that:

Backtrace:
src/redis-server 127.0.0.1:21322(logStackTrace+0x45)[0x479035]
src/redis-server 127.0.0.1:21322(sigsegvHandler+0xb9)[0x4797f9]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x11390)[0x7fd373c5e390]
src/redis-server 127.0.0.1:21322(_serverAssert+0x6a)[0x47660a]
src/redis-server 127.0.0.1:21322(freeReplicationBacklog+0x42)[0x451282]
src/redis-server 127.0.0.1:21322[0x4552d4]
src/redis-server 127.0.0.1:21322[0x4c5593]
src/redis-server 127.0.0.1:21322(aeProcessEvents+0x2e6)[0x42e786]
src/redis-server 127.0.0.1:21322(aeMain+0x1d)[0x42eb0d]
src/redis-server 127.0.0.1:21322(main+0x4c5)[0x42b145]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fd3738a3830]
src/redis-server 127.0.0.1:21322(_start+0x29)[0x42b409]

Since we disconnect all the replicas and free the replication backlog in
certain replication paths, and the code that will free the replication
backlog expects that no replica is connected.

However we still need to free the replicas asynchronously in certain
cases, as documented in the top comment of disconnectSlaves().
2020-05-22 19:29:09 +02:00
antirez 07c6bee78f Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-22 16:31:05 +02:00
antirez b407590cee Fix #7306 less aggressively.
Citing from the issue:

btw I suggest we change this fix to something else:
* We revert the fix.
* We add a call that disconnects chained replicas in the place where we trim the replica (that is a master i this case) offset.
This way we can avoid disconnections when there is no trimming of the backlog.

Note that we now want to disconnect replicas asynchronously in
disconnectSlaves(), because it's in general safer now that we can call
it from freeClient(). Otherwise for instance the command:

    CLIENT KILL TYPE master

May crash: clientCommand() starts running the linked of of clients,
looking for clients to kill. However it finds the master, kills it
calling freeClient(), but this in turn calls replicationCacheMaster()
that may also call disconnectSlaves() now. So the linked list iterator
of the clientCommand() will no longer be valid.
2020-05-22 16:29:53 +02:00
Salvatore SanfilippoandGitHub 40161553e2 Merge pull request #7096 from ShooterIT/sendfile
Implements sendfile for redis.
2020-05-22 13:55:46 +02:00
Salvatore SanfilippoandGitHub 285817b28a Merge pull request #7305 from madolson/unstable-connection
EAGAIN not handled for TLS during diskless load
2020-05-22 12:25:40 +02:00
Salvatore SanfilippoandGitHub ee93a70e7b Merge pull request #7306 from QuChen88/chained-replica-offset
Disconnect chained replicas when the replica performs PSYNC with the master always to avoid replication offset mismatch between master and chained replicas
2020-05-22 12:23:07 +02:00
Qu Chen 42f5da5d2d Disconnect chained replicas when the replica performs PSYNC with the master always to avoid replication offset mismatch between master and chained replicas. 2020-05-21 18:42:10 -07:00
Madelyn Olson 5109f16b77 EAGAIN for tls during diskless load 2020-05-21 15:20:59 -07:00
Salvatore SanfilippoandGitHub 90fda5c97c Merge pull request #7304 from hwware/lcs_fix
fix server crash for STRALGO command
2020-05-22 00:18:27 +02:00
hwware 06a59fd44d using moreargs variable 2020-05-21 18:13:35 -04:00
hwware 5edb1beb63 fix server crash for STRALGO command 2020-05-21 17:30:36 -04:00
Salvatore SanfilippoandGitHub fe640e5858 Merge pull request #7300 from ShooterIT/comment
Replace 'addDeferredMultiBulkLength' with 'addReplyDeferredLen' in comment
2020-05-21 15:53:01 +02:00
Salvatore SanfilippoandGitHub e387f5ba62 Merge pull request #7288 from yossigo/tls-protocols-redis-conf-example
TLS: Improve tls-protocols clarity in redis.conf.
2020-05-21 15:49:37 +02:00
Salvatore SanfilippoandGitHub 9f932bcc82 Merge pull request #7299 from ShooterIT/reply-bytes
Fix reply bytes calculation error on 32bit platform
2020-05-21 15:47:15 +02:00
ShooterIT 86f0e873c7 Replace addDeferredMultiBulkLength with addReplyDeferredLen in comment 2020-05-21 21:45:35 +08:00
ShooterIT 9018ddc32a Fix reply bytes calculation error
Fix #7275.
2020-05-21 21:00:30 +08:00
Salvatore SanfilippoandGitHub 1afb002d69 Merge pull request #7296 from soloestoy/tracking-broken-redir
Tracking: flag CLIENT_TRACKING_BROKEN_REDIR when redir broken
2020-05-21 09:26:12 +02:00
zhaozhao.zz 4f3ff46a81 Tracking: flag CLIENT_TRACKING_BROKEN_REDIR when redir broken 2020-05-21 13:57:29 +08:00
Salvatore SanfilippoandGitHub af34245692 Merge pull request #7289 from oranagra/defrag_edge_case
fix a rare active defrag edge case bug leading to stagnation
2020-05-20 15:17:02 +02:00
Oran Agra 88d71f4793 fix a rare active defrag edge case bug leading to stagnation
There's a rare case which leads to stagnation in the defragger, causing
it to keep scanning the keyspace and do nothing (not moving any
allocation), this happens when all the allocator slabs of a certain bin
have the same % utilization, but the slab from which new allocations are
made have a lower utilization.

this commit fixes it by removing the current slab from the overall
average utilization of the bin, and also eliminate any precision loss in
the utilization calculation and move the decision about the defrag to
reside inside jemalloc.

and also add a test that consistently reproduce this issue.
2020-05-20 16:04:42 +03:00
Yossi Gottlieb 39d1e39212 TLS: Improve tls-protocols clarity in redis.conf. 2020-05-20 15:14:23 +03:00
Oran Agra 5d83e9e1de improve DEBUG MALLCTL to be able to write to write only fields.
also support:
  debug mallctl-str thread.tcache.flush VOID
2020-05-20 14:09:22 +03:00
Salvatore SanfilippoandGitHub 23a85ba191 Merge pull request #7276 from hujiecs/fix-clear-all-command-flag-in-acl
fix clear USER_FLAG_ALLCOMMANDS flag in acl
2020-05-19 16:19:28 +02:00
Salvatore SanfilippoandGitHub d949e8fe18 Merge pull request #7196 from ShooterIT/benchmark
Redis Benchmark: make test data better
2020-05-19 16:16:38 +02:00
Salvatore SanfilippoandGitHub a9200ed0b3 Merge pull request #7279 from hwware/benchmark_leak_fix
Redis-Benchmark: avoid potentical memmory leaking
2020-05-19 16:14:30 +02:00
Salvatore SanfilippoandGitHub 9fba05f758 Merge pull request #7232 from trevor211/handleHashTagWhenComputingHashSlot
Tcl client support hash tagged keys.
2020-05-19 09:23:44 +02:00
hwware 07e1417ef0 Redis-Benchmark: avoid potentical memmory leaking 2020-05-18 22:10:57 -04:00
hujie edc1f7b176 fix clear USER_FLAG_ALLCOMMANDS flag in acl
in ACLSetUserCommandBit, when the command bit overflows, no operation
is performed, so no need clear the USER_FLAG_ALLCOMMANDS flag.

in ACLSetUser, when adding subcommand, we don't need to call
ACLGetCommandID ahead since subcommand may be empty.
2020-05-19 00:58:58 +08:00
ShooterIT abff264000 Redis Benchmark: generate random test data
The function of generating random data is designed by antirez. See #7196.
2020-05-18 18:18:27 +08:00
Salvatore SanfilippoandGitHub bf3a67be43 Merge pull request #7252 from ShooterIT/dictsize
Use dictSize to get the size of dict in dict.c
2020-05-18 10:33:08 +02:00
Salvatore SanfilippoandGitHub dd78f7463f Merge pull request #7255 from madolson/dev-unstable-converge-hash-validation
Converge hash validation for adding and removing
2020-05-18 10:31:47 +02:00
Salvatore SanfilippoandGitHub c9ef9f1c48 Merge pull request #7247 from bsergean/patch-3
Redis-cli 6.0.1 `--cluster-yes` doesn't work (fix #7246)
2020-05-18 10:23:54 +02:00
Salvatore SanfilippoandGitHub 05db78e9c5 Merge pull request #7271 from oranagra/fix_valgrind_repl_test
fix valgrind test failure in replication test
2020-05-18 10:09:39 +02:00
Oran Agra 75c11d7fec fix valgrind test failure in replication test
in b4416280c i added more keys to that test to make it run longer
but in valgrind this now means the test times out, give valgrind more
time.
2020-05-18 10:26:53 +03:00
antirez 0ca2f4f824 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-17 18:24:48 +02:00
antirez 96bb0c9471 Improve the PSYNC2 test reliability. 2020-05-17 18:24:34 +02:00
Salvatore SanfilippoandGitHub 08d00dad52 Merge pull request #7264 from oranagra/test_for_7205
add regression test for the race in #7205
2020-05-17 17:59:14 +02:00
Oran Agra 357aace895 add regression test for the race in #7205
with the original version of 6.0.0, this test detects an excessive full
sync.
with the fix in 1a7cd2c0e, this test detects memory corruption,
especially when using libc allocator with or without valgrind.
2020-05-17 18:26:02 +03:00
antirez 624742d9b4 Remove the client from CLOSE_ASAP list before caching the master.
This was broken in 1a7cd2c: we identified a crash in the CI, what
was happening before the fix should be like that:

1. The client gets in the async free list.
2. However freeClient() gets called again against the same client
   which is a master.
3. The client arrived in freeClient() with the CLOSE_ASAP flag set.
4. The master gets cached, but NOT removed from the CLOSE_ASAP linked
   list.
5. The master client that was cached was immediately removed since it
   was still in the list.
6. Redis accessed a freed cached master.

This is how the crash looked like:

=== REDIS BUG REPORT START: Cut & paste starting from here ===
1092:S 16 May 2020 11:44:09.731 # Redis 999.999.999 crashed by signal: 11
1092:S 16 May 2020 11:44:09.731 # Crashed running the instruction at: 0x447e18
1092:S 16 May 2020 11:44:09.731 # Accessing address: 0xffffffffffffffff
1092:S 16 May 2020 11:44:09.731 # Failed assertion:  (:0)

------ STACK TRACE ------
EIP:
src/redis-server 127.0.0.1:21300(readQueryFromClient+0x48)[0x447e18]

And the 0xffff address access likely comes from accessing an SDS that is
set to NULL (we go -1 offset to read the header).
2020-05-16 17:15:35 +02:00
antirez 3cd92e87d1 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-15 10:21:51 +02:00
antirez 1a7cd2c0e2 Cache master without checking of deferred close flags.
The context is issue #7205: since the introduction of threaded I/O we close
clients asynchronously by default from readQueryFromClient(). So we
should no longer prevent the caching of the master client, to later
PSYNC incrementally, if such flags are set. However we also don't want
the master client to be cached with such flags (would be closed
immediately after being restored). And yet we want a way to understand
if a master was closed because of a protocol error, and in that case
prevent the caching.
2020-05-15 10:19:13 +02:00
Benjamin Sergeant f4d2abd3df do not handle --cluster-yes for cluster fix mode 2020-05-14 15:29:06 -07:00
Madelyn Olson f0f30fc43f Converge hash validation for adding and removing 2020-05-14 11:07:51 -07:00
Salvatore SanfilippoandGitHub 8f5c2bc8aa Merge pull request #7229 from yossigo/tls-fails-on-recent-debian
TLS: Fix test failures on recent Debian/Ubuntu.
2020-05-14 18:15:17 +02:00
Salvatore SanfilippoandGitHub 112e19a59c Merge pull request #7230 from yossigo/tls-crypto-locks
TLS: Add crypto locks for older OpenSSL support.
2020-05-14 18:14:49 +02:00
Salvatore SanfilippoandGitHub 67a4fb912f Merge pull request #7244 from devnexen/netbsd_build_fix
NetBSD build update.
2020-05-14 18:14:06 +02:00
Salvatore SanfilippoandGitHub 0dea1142da Merge pull request #7253 from madolson/unstable-ae-recursion
Added a refcount on timer events to prevent deletion of recursive timer events
2020-05-14 18:10:57 +02:00
Madelyn Olson 41cabca2ab Added a refcount on timer events to prevent deletion of recursive timer calls 2020-05-14 08:49:11 -07:00
antirez c38fd1f661 Merge branch 'free_clients_during_loading' into unstable 2020-05-14 11:28:08 +02:00
antirez dec6fd3adc Regression test for #7249. 2020-05-14 11:27:31 +02:00
antirez 3e6acc24d6 rax.c updated from upstream antirez/rax. 2020-05-14 11:17:47 +02:00
antirez a4d0720fa1 Tracking: send eviction messages when evicting entries.
A fix for #7249.
2020-05-14 11:17:04 +02:00
antirez bc4667acbc Track events processed while blocked globally.
Related to #7234.
2020-05-14 10:06:27 +02:00
Benjamin Sergeant db39b04d76 fix typo ... 2020-05-13 09:32:27 -07:00
Benjamin SergeantandGitHub 827e6cc644 Redis-cli 6.0.1 --cluster-yes doesn't work (fix #7246)
This make it so that all prompts for all redis-cli --cluster commands are automatically answered with a yes.
2020-05-13 09:24:51 -07:00
David Carlier 4715ce5903 NetBSD build update.
This platform supports CPU affinity (but not OpenBSD).
2020-05-12 21:21:22 +01:00
antirez 27e25e9d1e Some rework of #7234. 2020-05-12 13:07:44 +02:00
Salvatore SanfilippoandGitHub b726d64229 Merge pull request #7240 from oranagra/fix_replication_test
fix unstable replication test
2020-05-12 08:44:18 +02:00
Oran Agra b4416280cf fix unstable replication test
this test which has coverage for varoius flows of diskless master was
failing randomly from time to time.

the failure was:
[err]: diskless all replicas drop during rdb pipe in tests/integration/replication.tcl
log message of '*Diskless rdb transfer, last replica dropped, killing fork child*' not found

what seemed to have happened is that the master didn't detect that all
replicas dropped by the time the replication ended, it thought that one
replica is still connected.

now the test takes a few seconds longer but it seems stable.
2020-05-12 08:59:09 +03:00
Oran Agra 905e28ee87 fix redis 6.0 not freeing closed connections during loading.
This bug was introduced by a recent change in which readQueryFromClient
is using freeClientAsync, and despite the fact that now
freeClientsInAsyncFreeQueue is in beforeSleep, that's not enough since
it's not called during loading in processEventsWhileBlocked.
furthermore, afterSleep was called in that case but beforeSleep wasn't.

This bug also caused slowness sine the level-triggered mode of epoll
kept signaling these connections as readable causing us to keep doing
connRead again and again for ll of these, which keep accumulating.

now both before and after sleep are called, but not all of their actions
are performed during loading, some are only reserved for the main loop.

fixes issue #7215
2020-05-11 11:33:46 +03:00
WuYunlong 70c4851f2c Handle keys with hash tag when computing hash slot using tcl cluster client. 2020-05-11 13:14:18 +08:00
WuYunlong bc9efb577b Add a test to prove current tcl cluster client can not handle keys with hash tag. 2020-05-11 13:14:18 +08:00
Yossi Gottlieb 450f0d7248 TLS: Add crypto locks for older OpenSSL support.
This is really required only for older OpenSSL versions.

Also, at the moment Redis does not use OpenSSL from multiple threads so
this will only be useful if modules end up doing that.
2020-05-10 17:40:31 +03:00
Yossi Gottlieb 4d1178cc24 TLS: Fix test failures on recent Debian/Ubuntu.
Seems like on some systems choosing specific TLS v1/v1.1 versions no
longer works as expected. Test is reduced for v1.2 now which is still
good enough to test the mechansim, and matters most anyway.
2020-05-10 17:38:04 +03:00
antirez 1750513ac7 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-09 11:13:32 +02:00
antirez 4387ba6a17 Cluster: clarify we always resolve the sender. 2020-05-09 11:12:51 +02:00
antirez 79de9d6c78 Cluster: refactor ping/data delay handling. 2020-05-09 11:10:38 +02:00
antirez 00a3bc4359 Cluster: introduce data_received field.
We want to send pings and pongs at specific intervals, since our packets
also contain information about the configuration of the cluster and are
used for gossip. However since our cluster bus is used in a mixed way
for data (such as Pub/Sub or modules cluster messages) and metadata,
sometimes a very busy channel may delay the reception of pong packets.
So after discussing it in #7216, this commit introduces a new field that
is not exposed in the cluster, is only an internal information about
the last time we received any data from a given node: we use this field
in order to avoid detecting failures, claiming data reception of new
data from the node is a proof of liveness.
2020-05-09 11:10:35 +02:00
Salvatore SanfilippoandGitHub 5fa6f9ebe1 Merge pull request #7204 from ShooterIT/benchmark-fix
Redis Benchmark: Fix coredump because of double free
2020-05-08 10:53:20 +02:00
ShooterIT 4afa243ff1 Use dictSize to get the size of dict in dict.c 2020-05-07 11:04:08 +08:00
antirez e17f9311c8 stringmatchlen() should not expect null terminated strings. 2020-05-06 16:18:21 +02:00
Salvatore SanfilippoandGitHub 2e2f57e23b Merge pull request #7201 from braddunbar/stringmatchlen
Remove unreachable branch.
2020-05-06 16:17:10 +02:00
Salvatore SanfilippoandGitHub 028eb45723 Merge pull request #7207 from hwware/redisconffixx
add jemalloc-bg-thread config in redis conf
2020-05-06 15:42:03 +02:00
Salvatore SanfilippoandGitHub 18c4c7bfe7 Merge pull request #7206 from hwware/lolwutmarcofix
add include guard for lolwut.h
2020-05-06 15:40:26 +02:00
hwware 9cfb1c88d6 add jemalloc-bg-thread config in redis conf 2020-05-06 01:07:17 -04:00
hwware 24173d11bf add include guard for lolwut.h 2020-05-05 23:35:08 -04:00
Salvatore SanfilippoandGitHub 54cb8214d9 Merge pull request #7175 from bsergean/bsergean-patch-1
redis-cli cluster reshard does not work with ACL (AUTH2 MIGRATE args un-implemented)
2020-05-05 18:22:24 +02:00
ShooterIT 150ba0e1ff Redis Benchmark: Fix coredump because of double free 2020-05-05 23:09:45 +08:00
Brad Dunbar 5508c228e7 Remove unreachable branch. 2020-05-05 09:57:01 -04:00
antirez 373a3384ac Don't propagate spurious MULTI on DEBUG LOADAOF. 2020-05-05 15:50:00 +02:00
antirez 51ab31dbab Dump recent backlog on master query generating errors. 2020-05-05 13:40:33 +02:00
antirez 415c4f613c Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-05 13:19:01 +02:00
antirez e49d97298a Test: --dont-clean should do first cleanup. 2020-05-05 13:18:53 +02:00
Salvatore SanfilippoandGitHub 64347aaaf9 Merge pull request #7200 from titouanc/fix-redis6-uclibc
make struct user anonymous (only typedefed) in server.h
2020-05-05 11:42:43 +02:00
Titouan Christophe 1f246bfcc8 make struct user anonymous (only typedefed)
This works because this struct is never referenced by its name,
but always by its type.

This prevents a conflict with struct user from <sys/user.h>
when compiling against uclibc.

Signed-off-by: Titouan Christophe <titouan.christophe@railnova.eu>
2020-05-05 11:35:03 +02:00
Salvatore SanfilippoandGitHub 140c20fb5e Merge pull request #7197 from bsergean/patch-1
Add --user argument to redis-benchmark.c (ACL)
2020-05-05 10:28:35 +02:00
antirez 5fdc5e5c25 Drop not needed part from #7194. 2020-05-05 10:27:27 +02:00
Salvatore SanfilippoandGitHub 154e735c46 Merge pull request #7194 from tryfinally/dev-reverse-bits
Fixed compiler warnings on rev(ulong) function
2020-05-05 10:26:02 +02:00
antirez 8257a3c1f6 Move CRC64 initialization in main(). 2020-05-05 10:20:48 +02:00
antirez 1063f1d833 Fix CRC64 initialization outside the Redis server itself. 2020-05-05 10:09:28 +02:00
Benjamin SergeantandGitHub 538c2b2d8d Add --user argument to redis-benchmark.c (ACL) 2020-05-04 08:09:21 -07:00
Muhammad Zahalqa 4477ee33ac Fix compiler warnings on function rev(unsigned long) 2020-05-04 12:36:01 +03:00
antirez 70a80ef3ad Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-04 11:09:32 +02:00
antirez 6fe2b5236d Fix NetBSD build by fixing redis_set_thread_title() support.
See #7188.
2020-05-04 11:09:02 +02:00
Salvatore SanfilippoandGitHub 1aee086a09 Merge pull request #7192 from hwware/trackingprefix
Client Side Caching: Add Number of Tracking Prefix Stats in Server Info
2020-05-04 11:06:44 +02:00
antirez 68b5eed41d Rework a bit the documentation for CPU pinning. 2020-05-04 11:05:48 +02:00
antirez fe9acb3469 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-05-04 10:57:20 +02:00
Salvatore SanfilippoandGitHub acf566b291 Merge pull request #7179 from bytedance/cpu-affinity
Support setcpuaffinity on linux/bsd
2020-05-04 10:56:20 +02:00
Salvatore SanfilippoandGitHub 68ad748e77 Merge pull request #7190 from guybe7/fix_consumer_seen_time
XPENDING should not update consumer's seen-time
2020-05-04 10:50:22 +02:00
Guy Benoish bce3d08c66 XPENDING should not update consumer's seen-time
Same goes for XGROUP DELCONSUMER (But in this case, it doesn't
have any visible effect)
2020-05-04 11:40:42 +03:00
Salvatore SanfilippoandGitHub b3f3d5a42f Merge pull request #7176 from oranagra/fix-optimize-reply-buffer
optimize memory usage of deferred replies - fixed
2020-05-04 10:20:31 +02:00
Salvatore SanfilippoandGitHub 57697373ef Merge pull request #7191 from deliangyang/patch-1
reformat code
2020-05-04 10:14:24 +02:00
Salvatore SanfilippoandGitHub 7dcf3eb3fc Merge pull request #7184 from oranagra/nightly-actions
add daily github actions with libc malloc and valgrind, fix leaks and timing issues
2020-05-04 10:11:24 +02:00
Oran Agra deee2c1ef2 add daily github actions with libc malloc and valgrind
* fix memlry leaks with diskless replica short read.
* fix a few timing issues with valgrind runs
* fix issue with valgrind and watchdog schedule signal

about the valgrind WD issue:
the stack trace test in logging.tcl, has issues with valgrind:
==28808== Can't extend stack to 0x1ffeffdb38 during signal delivery for thread 1:
==28808==   too small or bad protection modes

it seems to be some valgrind bug with SA_ONSTACK.
SA_ONSTACK seems unneeded since WD is not recursive (SA_NODEFER was removed),
also, not sure if it's even valid without a call to sigaltstack()
2020-05-04 09:52:20 +03:00
Deliang YangandGitHub e598d9b59e reformat code 2020-05-04 01:57:38 +08:00
hwware e697ef6dec Client Side Caching: Add Tracking Prefix Number Stats in Server Info 2020-05-02 19:20:44 -04:00
zhenwei pi 1a0deab2a5 Support setcpuaffinity on linux/bsd
Currently, there are several types of threads/child processes of a
redis server. Sometimes we need deeply optimise the performance of
redis, so we would like to isolate threads/processes.

There were some discussion about cpu affinity cases in the issue:
https://github.com/antirez/redis/issues/2863

So implement cpu affinity setting by redis.conf in this patch, then
we can config server_cpulist/bio_cpulist/aof_rewrite_cpulist/
bgsave_cpulist by cpu list.

Examples of cpulist in redis.conf:
server_cpulist 0-7:2      means cpu affinity 0,2,4,6
bio_cpulist 1,3           means cpu affinity 1,3
aof_rewrite_cpulist 8-11  means cpu affinity 8,9,10,11
bgsave_cpulist 1,10-11    means cpu affinity 1,10,11

Test on linux/freebsd, both work fine.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
2020-05-02 21:19:47 +08:00
Oran Agra 6726b3c2cb optimize memory usage of deferred replies - fixed
When deffered reply is added the previous reply node cannot be used so
all the extra space we allocated in it is wasted. in case someone uses
deffered replies in a loop, each time adding a small reply, each of
these reply nodes (the small string reply) would have consumed a 16k
block.
now when we add anther diferred reply node, we trim the unused portion
of the previous reply block.

see #7123

cherry picked from commit fb732f7a94
with fix to handle a crash with LIBC allocator, which apparently can
return the same pointer despite changing it's size.
i.e. shrinking an allocation of 16k into 56 bytes without changing the
pointer.
2020-05-02 07:52:03 +03:00
Benjamin SergeantandGitHub 93021da221 Update redis-cli.c 2020-05-01 20:57:51 -07:00
antirez a07a4ada19 Cast printf() argument to the format specifier.
We could use uint64_t specific macros, but after all it's simpler to
just use an obvious equivalent type plus casting: this will be a no op
and is simpler than fixed size types printf macros.
2020-05-02 00:03:14 +02:00
antirez 365316aa59 Revert "optimize memory usage of deferred replies"
This reverts commit fb732f7a94.
2020-05-01 23:57:11 +02:00
antirez fe980e2334 Save a call to stopThreadedIOIfNeeded() for the base case.
Probably no performance changes, but the code should be trivial to
read as in "No threading? Use the normal function and return".
2020-05-01 18:45:51 +02:00
antirez 69ce235c7b Update help.h again before Redis 6 GA. 2020-04-30 13:43:05 +02:00
antirez 684c5907dd redis-cli: fix hints with subcommands. 2020-04-30 13:42:25 +02:00
antirez 6fcdf75325 redis-cli command help updated. 2020-04-30 13:00:37 +02:00
Salvatore SanfilippoandGitHub 15b040907c Merge pull request #4838 from soloestoy/lazyfree-eviction
lazyfree & eviction: record latency generated by lazyfree eviction
2020-04-30 10:43:12 +02:00
antirez 058c727282 MIGRATE AUTH2 for ACL support. 2020-04-30 10:14:15 +02:00
antirez cec388f208 CLIENT KILL USER <username>. 2020-04-30 09:58:06 +02:00
zhaozhao.zz 528ea98bd3 lazyfree & eviction: record latency generated by lazyfree eviction
1. add eviction-lazyfree monitor
2. put eviction-del & eviction-lazyfree into eviction-cycle
   that means eviction-cycle contains all the latency in
   the eviction cycle including del and lazyfree
3. use getMaxmemoryState to check if we can break in lazyfree-evict
2020-04-30 15:54:14 +08:00
antirez fd8f39a283 Fix tracking table max keys option in redis.conf. 2020-04-29 18:49:42 +02:00
antirez 551fed3169 redis-cli: safer cluster fix with unreachalbe masters. 2020-04-29 16:57:06 +02:00
antirez 7c29c9eec1 redis-cli: simplify cluster nodes coverage display. 2020-04-29 16:33:47 +02:00
antirez 86a1386d6f redis-cli: try to make clusterManagerFixOpenSlot() more readable.
Also improve the message to make clear that there is no *clear* owner,
not that there is no owner at all.
2020-04-29 12:37:47 +02:00
antirez 1a5c3a9a60 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-29 11:16:43 +02:00
antirez d2a6c5ffa2 Comment clearly why we moved some code in #6623. 2020-04-29 11:16:30 +02:00
srzhaoandantirez 4024bc7eee fix pipelined WAIT performance issue.
If client gets blocked again in `processUnblockedClients`, redis will not send
`REPLCONF GETACK *` to slaves untill next eventloop, so the client will be
blocked for 100ms by default(10hz) if no other file event fired.

move server.get_ack_from_slaves sinppet after `processUnblockedClients`, so
that both the first WAIT command that puts client in blocked context and the
following WAIT command processed in processUnblockedClients would trigger
redis-sever to send `REPLCONF GETACK *`, so that the eventloop would get
`REPLCONG ACK <reploffset>` from slaves and unblocked ASAP.
2020-04-29 11:00:35 +02:00
Salvatore SanfilippoandGitHub d4a2970092 Merge pull request #7161 from guybe7/xinfo_full_count
XINFO STREAM FULL should have a default COUNT of 10
2020-04-28 17:19:31 +02:00
Guy Benoish 057865a6a5 XINFO STREAM FULL should have a default COUNT of 10 2020-04-28 18:09:03 +03:00
antirez f95a88d988 Fix create-cluster BIN_PATH. 2020-04-28 16:40:15 +02:00
Salvatore SanfilippoandGitHub e0de7c0852 Merge pull request #7134 from guybe7/xstate_command
Extend XINFO STREAM output
2020-04-28 16:31:00 +02:00
Guy Benoish 1e2aee3919 Extend XINFO STREAM output
Introducing XINFO STREAM <key> FULL
2020-04-28 13:03:43 +03:00
Salvatore SanfilippoandGitHub ce25ceda58 Merge pull request #7101 from hwware/fixunusedmarco
Fix not used marco in cluster.c
2020-04-28 11:22:09 +02:00
Salvatore SanfilippoandGitHub 615eae2c82 Merge pull request #6942 from itamarhaber/clustrutil-binpath
Adds `BIN_PATH` to create-cluster
2020-04-28 11:21:06 +02:00
Salvatore SanfilippoandGitHub 4d4c8c8a40 Merge pull request #7160 from oranagra/refix-get_decoded_object
hickup, re-fix dictEncObjKeyCompare
2020-04-28 11:19:28 +02:00
Oran Agra 9a3dab0a2e hickup, re-fix dictEncObjKeyCompare
come to think of it, in theory (not in practice), getDecodedObject can
return the same original object with refcount incremented, so the
pointer comparision in the previous commit was invalid.
so now instead of checking the encoding, we explicitly check the
refcount.
2020-04-28 12:14:46 +03:00
Salvatore SanfilippoandGitHub e8bc306850 Merge pull request #7155 from oranagra/psync2_tests_race
fix loading race in psync2 tests
2020-04-28 10:04:47 +02:00
Oran Agra d31c0c5264 fix loading race in psync2 tests 2020-04-28 09:18:01 +03:00
antirez 31781e97b6 Rework comment in dictEncObjKeyCompare(). 2020-04-27 22:40:15 +02:00
Salvatore SanfilippoandGitHub 73316758be Merge pull request #7152 from oranagra/dict_find_static_robj
allow dictFind using static robj
2020-04-27 22:36:51 +02:00
Oran Agra 09a5c07886 allow dictFind using static robj
since the recent addition of OBJ_STATIC_REFCOUNT and the assertion in
incrRefCount it is now impossible to use dictFind using a static robj,
because dictEncObjKeyCompare will call getDecodedObject which tries to
increment the refcount just in order to decrement it later.
2020-04-27 23:17:19 +03:00
Salvatore SanfilippoandGitHub 911a2d6cb4 Merge pull request #7148 from madolson/unstable-crc
Unstable crc
2020-04-27 17:29:13 +02:00
Salvatore SanfilippoandGitHub 828736e7d0 Merge pull request #7146 from oranagra/optimize_deferred_reply
optimize memory usage of deferred replies
2020-04-27 16:45:47 +02:00
Oran Agraandantirez 4447ddc8bb Keep track of meaningful replication offset in replicas too
Now both master and replicas keep track of the last replication offset
that contains meaningful data (ignoring the tailing pings), and both
trim that tail from the replication backlog, and the offset with which
they try to use for psync.

the implication is that if someone missed some pings, or even have
excessive pings that the promoted replica has, it'll still be able to
psync (avoid full sync).

the downside (which was already committed) is that replicas running old
code may fail to psync, since the promoted replica trims pings form it's
backlog.

This commit adds a test that reproduces several cases of promotions and
demotions with stale and non-stale pings

Background:
The mearningful offset on the master was added recently to solve a problem were
the master is left all alone, injecting PINGs into it's backlog when no one is
listening and then gets demoted and tries to replicate from a replica that didn't
have any of the PINGs (or at least not the last ones).

however, consider this case:
master A has two replicas (B and C) replicating directly from it.
there's no traffic at all, and also no network issues, just many pings in the
tail of the backlog. now B gets promoted, A becomes a replica of B, and C
remains a replica of A. when A gets demoted, it trims the pings from its
backlog, and successfully replicate from B. however, C is still aware of
these PINGs, when it'll disconnect and re-connect to A, it'll ask for something
that's not in the backlog anymore (since A trimmed the tail of it's backlog),
and be forced to do a full sync (something it didn't have to do before the
meaningful offset fix).

Besides that, the psync2 test was always failing randomly here and there, it
turns out the reason were PINGs. Investigating it shows the following scenario:

cycle 1: redis #1 is master, and all the rest are direct replicas of #1
cycle 2: redis #2 is promoted to master, #1 is a replica of #2 and #3 is replica of #1
now we see that when #1 is demoted it prints:
17339:S 21 Apr 2020 11:16:38.523 * Using the meaningful offset 3929963 instead of 3929977 to exclude the final PINGs (14 bytes difference)
17339:S 21 Apr 2020 11:16:39.391 * Trying a partial resynchronization (request e2b3f8817735fdfe5fa4626766daa938b61419e5:3929964).
17339:S 21 Apr 2020 11:16:39.392 * Successful partial resynchronization with master.
and when #3 connects to the demoted #2, #2 says:
17339:S 21 Apr 2020 11:16:40.084 * Partial resynchronization not accepted: Requested offset for secondary ID was 3929978, but I can reply up to 3929964

so the issue here is that the meaningful offset feature saved the day for the
demoted master (since it needs to sync from a replica that didn't get the last
ping), but it didn't help one of the other replicas which did get the last ping.
2020-04-27 15:52:23 +02:00
antirez 3497fd007f Fix STRALGO command flags. 2020-04-27 13:35:17 +02:00
Madelyn Olson a48dfe98cf Added crcspeed library 2020-04-24 17:11:21 -07:00
Madelyn Olson 2192a91d62 Made crc64 test consistent 2020-04-24 17:05:52 -07:00
Madelyn Olson 486e45ffaf Implemented CRC64 based on slice by 4 2020-04-24 17:00:03 -07:00
antirez 022f09447b Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-24 16:59:56 +02:00
antirez 8a7f255cd0 LCS -> STRALGO LCS.
STRALGO should be a container for mostly read-only string
algorithms in Redis. The algorithms should have two main
characteristics:

1. They should be non trivial to compute, and often not part of
programming language standard libraries.
2. They should be fast enough that it is a good idea to have optimized C
implementations.

Next thing I would love to see? A small strings compression algorithm.
2020-04-24 16:54:32 +02:00
Oran Agra fb732f7a94 optimize memory usage of deferred replies
When deffered reply is added the previous reply node cannot be used so
all the extra space we allocated in it is wasted. in case someone uses
deffered replies in a loop, each time adding a small reply, each of
these reply nodes (the small string reply) would have consumed a 16k
block.
now when we add anther diferred reply node, we trim the unused portion
of the previous reply block.

see #7123
2020-04-24 17:20:28 +03:00
antirez 57a0c9c98d Also use propagate() in streamPropagateGroupID(). 2020-04-24 10:13:20 +02:00
Salvatore SanfilippoandGitHub e83d5b64dc Merge pull request #7120 from Dave-in-lafayette/patch-1
fix for unintended crash during panic response
2020-04-23 16:30:28 +02:00
Salvatore SanfilippoandGitHub 42d309fffc Merge pull request #7114 from guybe7/stream_tag_xsetid
Add the stream tag to XSETID tests
2020-04-23 16:29:46 +02:00
Salvatore SanfilippoandGitHub 25725957e9 Merge pull request #7121 from Dave-in-lafayette/patch-2
fix for crash during panic before all threads are up
2020-04-23 16:29:00 +02:00
Salvatore SanfilippoandGitHub 72f0751905 Merge pull request #7123 from fayadexinqing/optimizeClusterSlots
Optimize the command of cluster slots
2020-04-23 16:18:22 +02:00
antirez 12cd67b7ca Minor aesthetic changes to #7135. 2020-04-23 16:13:45 +02:00
Salvatore SanfilippoandGitHub 2a3d933cc6 Merge pull request #7135 from valentinogeron/optimize-xreadgroup-noack
XREADGROUP with NOACK should propagate only one XGROUP SETID command
2020-04-23 16:12:08 +02:00
antirez 96ebfc3b36 ACL: re-enable command execution of disabled users.
After all I changed idea again: enabled/disabled should have a more
clear meaning, and it only means: you can't authenticate with such user
with new connections, however old connections continue to work as
expected.
2020-04-23 11:56:39 +02:00
antirez 9ae8254e20 getRandomBytes(): use HMAC-SHA256.
Now that we have an interface to use this API directly, via ACL GENPASS,
we are no longer sure what people could do with it. So why don't make it
a strong primitive exported by Redis in order to create unique IDs and
so forth?

The implementation was tested against the test vectors that can
be found in RFC4231.
2020-04-23 11:23:50 +02:00
antirez a95a086be7 ACL GENPASS: take number of bits as argument. 2020-04-23 10:53:21 +02:00
antirez dc1bc0f423 ACL GENPASS: emit 256 bits instead of 128. 2020-04-23 10:39:53 +02:00
antirez 889eaba2b7 ACL: deny commands execution of disabled users. 2020-04-22 17:14:15 +02:00
Valentino Geron 6fd2d7cfee XREADGROUP with NOACK should propagate only one XGROUP SETID command 2020-04-22 17:03:51 +03:00
antirez c2db3de498 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-22 11:51:05 +02:00
antirez 8d67211450 Tracking: test expired keys notifications. 2020-04-22 11:45:34 +02:00
antirez 58d61dd639 Tracking: NOLOOP tests. 2020-04-22 11:24:19 +02:00
Salvatore SanfilippoandGitHub 3e738c8a6c Merge pull request #7131 from botovq/tls_no_renegotiation
TLS: Fix build with SSL_OP_NO_CLIENT_RENEGOTIATION
2020-04-22 11:10:48 +02:00
antirez 2d1968f841 Tracking: signal key as modified when evicting. 2020-04-22 10:49:17 +02:00
Theo Buehler 04f1a280e0 TLS: Fix build with SSL_OP_NO_CLIENT_RENEGOTIATION
There is no ssl in this scope, so the build breaks.
All the other options are set directly on the ctx.
2020-04-22 10:05:47 +02:00
antirez c6ccc2f690 Tracking: NOLOOP further implementation and fixes. 2020-04-21 17:29:18 +02:00
yanhui13 6b547c3956 add tcl test for cluster slots 2020-04-21 16:56:10 +08:00
yanhui13 92974f9a98 optimize the output of cluster slots 2020-04-21 16:55:05 +08:00
antirez 94f2e7f9f9 Tracking: NOLOOP internals implementation. 2020-04-21 10:51:46 +02:00
Dave-in-lafayetteandGitHub 80b6f9b0cb fix for crash during panic before all threads are up
If there's a panic before all threads have been started (say, if file descriptor 0 is closed at exec), the panic response will crash here again.
2020-04-20 16:38:06 -07:00
Dave-in-lafayetteandGitHub 071197fb64 fix for unintended crash during panic response
If redis crashes early, before lua is set up (like, if File Descriptor 0 is closed before exec), it will crash again trying to print memory statistics.
2020-04-20 16:34:36 -07:00
Salvatore SanfilippoandGitHub c49fb47fbe Merge pull request #7118 from yossigo/fix/openssl-build-issue
TLS: Fix build on older verisons of OpenSSL.
2020-04-20 14:53:33 +02:00
Yossi Gottlieb c8a720d17d TLS: Fix build on older verisons of OpenSSL. 2020-04-20 13:34:37 +03:00
antirez c7db333abb Implement redis_set_thread_title for MacOS.
Strange enough, pthread_setname_np() produces a warning for not defined
function even if pthread is included. Moreover the MacOS documentation
claims the return value for the function is void, but actually is int.

Related to #7089.
2020-04-20 12:18:37 +02:00
Salvatore SanfilippoandGitHub 85d1d1f870 Merge pull request #7089 from bytedance/set-thread-name
Threaded IO: set thread name for redis-server
2020-04-20 11:54:25 +02:00
antirez 2321939218 Sentinel: small refactoring of sentinelCollectTerminatedScripts().
Related to #7113.
2020-04-20 11:52:34 +02:00
Salvatore SanfilippoandGitHub f9d624c504 Merge pull request #7113 from OMG-By/unstable
fix(sentinel): sentinel.running_scripts not reset
2020-04-20 11:51:51 +02:00
Guy Benoish 1bc557c9c5 Add the stream tag to XSETID tests 2020-04-19 15:59:58 +03:00
zhenwei pi 5010da6ab0 Threaded IO: set thread name for redis-server
Set thread name for each thread of redis-server, this helps us to
monitor the utilization and optimise the performance.

And suggested-by Salvatore, implement this feature for multi
platforms. Currently support linux and bsd, ignore other OS.

An exmaple on Linux:
 # top -d 5 -p `pidof redis-server ` -H

    PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
3682671 root      20   0  227744   8248   3836 R 99.2  0.0   0:19.53 redis-server
3682677 root      20   0  227744   8248   3836 S 26.4  0.0   0:04.15 io_thd_3
3682675 root      20   0  227744   8248   3836 S 23.6  0.0   0:03.98 io_thd_1
3682676 root      20   0  227744   8248   3836 S 23.6  0.0   0:03.97 io_thd_2
3682672 root      20   0  227744   8248   3836 S  0.2  0.0   0:00.02 bio_close_file
3682673 root      20   0  227744   8248   3836 S  0.2  0.0   0:00.02 bio_aof_fsync
3682674 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 bio_lazy_free
3682678 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd
3682682 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd
3682683 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd
3682684 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd
3682685 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd
3682687 root      20   0  227744   8248   3836 S  0.0  0.0   0:00.00 jemalloc_bg_thd

Another exmaple on FreeBSD-12.1:
  PID USERNAME    PRI NICE   SIZE    RES STATE    C   TIME    WCPU COMMAND
 5212 root        100    0    48M  7280K CPU2     2   0:26  99.52% redis-server{redis-server}
 5212 root         38    0    48M  7280K umtxn    4   0:06  26.94% redis-server{io_thd_3}
 5212 root         36    0    48M  7280K umtxn    6   0:06  26.84% redis-server{io_thd_1}
 5212 root         39    0    48M  7280K umtxn    1   0:06  25.30% redis-server{io_thd_2}
 5212 root         20    0    48M  7280K uwait    3   0:00   0.00% redis-server{redis-server}
 5212 root         21    0    48M  7280K uwait    2   0:00   0.00% redis-server{bio_close_file}
 5212 root         21    0    48M  7280K uwait    3   0:00   0.00% redis-server{bio_aof_fsync}
 5212 root         21    0    48M  7280K uwait    0   0:00   0.00% redis-server{bio_lazy_free}

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
2020-04-18 11:17:21 +08:00
omg-by 9d27e00ddb fix(sentinel): sentinel.running_scripts will always increase more times and not reset
when trigger a always fail scripts, sentinel.running_scripts will increase ten times, however it
only decrease one times onretry the maximum. and it will't reset, when it become
SENTINEL_SCRIPT_MAX_RUNNING, sentinel don't trigger scripts.
2020-04-18 00:49:16 +08:00
antirez c479eace45 Fix XCLAIM propagation in AOF/replicas for blocking XREADGROUP.
See issue #7105.
2020-04-17 12:38:15 +02:00
antirez 002052f8de A few comments and name changes for #7103. 2020-04-17 10:51:12 +02:00
antirez eae2615348 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-17 10:43:50 +02:00
Salvatore SanfilippoandGitHub a71a6b5acf Merge pull request #7103 from oranagra/run_solo_test
testsuite run the defrag latency test solo
2020-04-17 10:43:21 +02:00
Salvatore SanfilippoandGitHub ac441c7413 Merge pull request #7087 from IAmATeaPot418/patch-6
Adding acllog-max-len to Redis.conf
2020-04-16 17:37:44 +02:00
Oran Agra b9fa42a197 testsuite run the defrag latency test solo
this test is time sensitive and it sometimes fail to pass below the
latency threshold, even on strong machines.

this test was the reson we're running just 2 parallel tests in the
github actions CI, revering this.
2020-04-16 18:09:22 +03:00
antirez 37b135a400 Update SDS to latest version. 2020-04-16 16:08:37 +02:00
antirez c9388ecc73 RESP3: fix HELLO map len in Sentinel mode.
See #6160.
2020-04-16 11:21:52 +02:00
hwware 12b9fd4f03 Fix not used marco in cluster.c 2020-04-15 22:00:36 -04:00
antirez 503a5a24fb Don't allow empty spaces in ACL usernames.
Fixes issue #6418.
2020-04-15 16:40:25 +02:00
antirez 3519a5a026 Don't allow empty spaces in ACL key patterns.
Fixes issue #6418.
2020-04-15 16:12:07 +02:00
antirez e8dd99fa53 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-15 16:01:56 +02:00
antirez 7f794a122f Fix HELLO reply in Sentinel mode, see #6160. 2020-04-15 15:59:52 +02:00
Salvatore SanfilippoandGitHub dfd3ead90d Merge pull request #7078 from karelrooted/fix/geo-hash-edge-case
FIX truncate max/min longitude,latitude related geo_point
2020-04-15 10:23:23 +02:00
ShooterIT 9cf500a3f6 Implements sendfile for redis. 2020-04-14 23:56:34 +08:00
Salvatore SanfilippoandGitHub 7428f51217 Merge pull request #7085 from guybe7/timeout_err_typo
Typo in getTimeoutFromObjectOrReply's error reply
2020-04-14 11:40:51 +02:00
antirez 552b36e657 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-14 11:23:57 +02:00
antirez 086c1db2d9 Fix zsetAdd() top comment spelling. 2020-04-14 11:23:44 +02:00
Salvatore SanfilippoandGitHub 33386bed66 Merge pull request #7095 from hwware/typofixacl
fix spelling in acl.c
2020-04-14 11:15:26 +02:00
antirez 5fece85491 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-14 10:52:45 +02:00
antirez 49ccd2a8e1 Fix function names in zslDeleteNode() top comment. 2020-04-14 10:52:40 +02:00
hwware 1323afed8b fix spelling in acl.c 2020-04-14 00:16:29 -04:00
Jamie ScottandGitHub 4f3b15e6ea minor fix 2020-04-12 17:56:58 -07:00
Jamie ScottandGitHub 17bf8dc99c Adding acllog-max-len to Redis.conf
While playing with ACLs I noticed that acllog-max-len wasn't in the redis.conf, but was a supported config. 

This PR documents and adds the directive to the redis.conf file.
2020-04-12 00:10:19 -07:00
Guy Benoish 29760b2eb7 Typo in getTimeoutFromObjectOrReply's error reply 2020-04-11 15:05:01 +03:00
Salvatore SanfilippoandGitHub 767977c533 Merge pull request #7074 from hayleeliu/unstable
fix spelling mistake in bitops.c
2020-04-10 10:15:25 +02:00
antirez 839bbcb039 RESP3: change streams items from maps to arrays.
Streams items are similar to dictionaries, however they preserve both
the order, and allow for duplicated field names. So a map is not a
semantically sounding way to deal with this.

https://twitter.com/antirez/status/1248261087553880069
2020-04-10 10:12:26 +02:00
antirez 3072498665 Merge branch 'faster-rdb-loading' into unstable 2020-04-09 19:15:13 +02:00
antirez 6ad44b76b9 Use the special static refcount for stack objects. 2020-04-09 16:25:30 +02:00
antirez d88f52ee7d RDB: refactor some RDB loading code into dbAddRDBLoad(). 2020-04-09 16:21:48 +02:00
antirez 399a6b2b47 incrRefCount(): abort on statically allocated object. 2020-04-09 16:20:41 +02:00
antirez 451872527c More powerful DEBUG RELOAD.
Related to #3243.
2020-04-09 12:10:10 +02:00
liumiuyong 3fbfa1885c FIX: truncate max/min longitude,latitude related geo_point (ex: {180, 85.05112878} ) 2020-04-09 17:48:29 +08:00
antirez 3d5b2d41b5 RDB: clarify a condition in rdbLoadRio(). 2020-04-09 11:09:40 +02:00
antirez 30adc62232 RDB: load files faster avoiding useless free+realloc.
Reloading of the RDB generated by

    DEBUG POPULATE 5000000
    SAVE

is now 25% faster.

This commit also prepares the ability to have more flexibility when
loading stuff from the RDB, since we no longer use dbAdd() but can
control exactly how things are added in the database.
2020-04-09 10:24:46 +02:00
antirez 96a54866ab Speedup: unblock clients on keys in O(1).
See #7071.
2020-04-08 12:55:57 +02:00
hayleeliuandGitHub 51b9de037d fix spelling mistake in bitops.c 2020-04-08 18:20:32 +08:00
antirez 96688aa646 Fix ACL HELP table missing comma. 2020-04-08 10:56:47 +02:00
antirez a6350f717d Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-07 14:10:43 +02:00
antirez f69876280c Speedup INFO by counting client memory incrementally.
Related to #5145.

Design note: clients may change type when they turn into replicas or are
moved into the Pub/Sub category and so forth. Moreover the recomputation
of the bytes used is problematic for obvious reasons: it changes
continuously, so as a conservative way to avoid accumulating errors,
each client remembers the contribution it gave to the sum, and removes
it when it is freed or before updating it with the new memory usage.
2020-04-07 12:07:54 +02:00
Salvatore SanfilippoandGitHub e93dcba46e Merge pull request #7064 from mymilkbottles/unstable
Optimize lua log level judgment
2020-04-06 17:34:14 +02:00
Salvatore SanfilippoandGitHub 9a9109431b Merge pull request #7065 from qetu3790/unstable
fix comments about RESIZE DB opcode in rdb.c
2020-04-06 17:32:44 +02:00
antirez bb30b9f913 Clarify redis.conf comment about lazyfree-lazy-user-del. 2020-04-06 17:32:04 +02:00
Salvatore SanfilippoandGitHub 094b47391d Merge pull request #6243 from soloestoy/expand-lazy-free-server-del
lazyfree: add a new configuration lazyfree-lazy-user-del
2020-04-06 17:27:39 +02:00
qetu3790andGitHub 2c42f6a8b8 fix comments about RESIZE DB opcode in rdb.c
fix comments about RESIZE DB opcode in rdb.c
2020-04-06 20:52:32 +08:00
antirez 121c51f4f3 Merge branch 'lcs' into unstable 2020-04-06 13:51:55 +02:00
antirez af3c722fec LCS: more tests. 2020-04-06 13:51:49 +02:00
antirez ca8d6f1072 LCS: allow KEYS / STRINGS to be anywhere.
Initially they needed to be at the end so that we could extend to N
strings in the future, but after further consideration I no longer
believe it's worth it.
2020-04-06 13:48:31 +02:00
antirez 8dc28b6c75 LCS tests. 2020-04-06 13:45:37 +02:00
mymilkbottlesandGitHub 7bd5ccaad0 Merge pull request #1 from mymilkbottles/mymilkbottles-log-level-judge
Optimize lua log level judgment
2020-04-06 19:34:07 +08:00
antirez 7261a5550f LCS: get rid of STOREIDX option. Fix get keys helper. 2020-04-06 13:32:15 +02:00
mymilkbottlesandGitHub 25063f75d7 Judge the log level in advance 2020-04-06 19:27:06 +08:00
Salvatore SanfilippoandGitHub af5c11874c Merge pull request #6797 from patpatbear/issue_#6565_memory_borderline
Check OOM at script start to get stable lua OOM state.
2020-04-06 11:59:01 +02:00
Salvatore SanfilippoandGitHub 4bc4d2c767 Merge pull request #7063 from oranagra/oran-runtest-external
diffrent fix for runtest --host --port
2020-04-06 11:47:35 +02:00
Oran Agra cf3789f045 diffrent fix for runtest --host --port 2020-04-06 09:41:14 +03:00
Salvatore SanfilippoandGitHub b0bbfed238 Merge pull request #7051 from guybe7/sleep_in_blockonkeys_tests
Try to fix time-sensitive tests in blockonkey.tcl
2020-04-03 14:12:31 +02:00
Salvatore SanfilippoandGitHub c76d0e7be9 Merge pull request #7057 from guybe7/fix_attr_gnuc
Use __attribute__ only if __GNUC__ is defined
2020-04-03 13:52:24 +02:00
Guy Benoish 1b0d30aeb7 Try to fix time-sensitive tests in blockonkey.tcl
There is an inherent race between the deferring client and the
"main" client of the test: While the deferring client issues a blocking
command, we can't know for sure that by the time the "main" client
tries to issue another command (Usually one that unblocks the deferring
client) the deferring client is even blocked...
For lack of a better choice this commit uses TCL's 'after' in order
to give some time for the deferring client to issues its blocking
command before the "main" client does its thing.
This problem probably exists in many other tests but this commit
tries to fix blockonkeys.tcl
2020-04-03 14:51:45 +03:00
Guy Benoish a76a961cdd Use __attribute__ only if __GNUC__ is defined 2020-04-03 14:49:40 +03:00
Salvatore SanfilippoandGitHub f80b946bea Merge pull request #6855 from guybe7/var_funcs_format_check
Modules: Perform printf-like format checks in variadic API
2020-04-03 13:46:27 +02:00
Guy Benoish a8857320a5 Modules: Perform printf-like format checks in variadic API 2020-04-03 14:39:55 +03:00
Salvatore SanfilippoandGitHub cbf212f981 Merge pull request #7030 from valentinogeron/xread-in-lua
XREAD and XREADGROUP should not be allowed from scripts when BLOCK op…
2020-04-03 11:14:13 +02:00
Salvatore SanfilippoandGitHub 4158664d2b Merge pull request #6933 from guybe7/multi_exec_allow_stale
Stale replica should allow MULTI/EXEC
2020-04-03 11:12:58 +02:00
Salvatore SanfilippoandGitHub 4e8e7fb067 Merge pull request #7055 from zxdvd/patch-1
fix integer overflow
2020-04-03 11:10:26 +02:00
antirez ef610802c7 LCS: fix stale comment. 2020-04-02 21:17:31 +02:00
Salvatore SanfilippoandGitHub 5b73ea3cf6 Merge pull request #7054 from guybe7/fix_neg_zero_test
Fix no-negative-zero test
2020-04-02 20:00:51 +02:00
Salvatore SanfilippoandGitHub eb9ca33414 Merge pull request #6694 from oranagra/signal_modified_key
modules don't signalModifiedKey in setKey() since that's done (optionally) in RM_CloseKey
2020-04-02 19:00:20 +02:00
Xudong Zhang 78df016584 fix integer overflow 2020-04-02 23:43:47 +08:00
Guy Benoish 4665b3ebfb Fix no-negative-zero test 2020-04-02 18:41:29 +03:00
Guy Benoish 85a0d29d04 Stale replica should allow MULTI/EXEC
Example: Client uses a pipe to send the following to a
stale replica:

MULTI
.. do something ...
DISCARD

The replica will reply the MUTLI with -MASTERDOWN and
execute the rest of the commands... A client using a
pipe might not be aware that MULTI failed until it's
too late.

I can't think of a reason why MULTI/EXEC/DISCARD should
not be executed on stale replicas...

Also, enable MULTI/EXEC/DISCARD during loading
2020-04-02 18:23:59 +03:00
Salvatore SanfilippoandGitHub 416d394cd9 Merge pull request #6927 from oranagra/ci-moduleapi-tests
change CI to build and run the module api tests
2020-04-02 16:32:41 +02:00
Salvatore SanfilippoandGitHub 0062194d74 Merge pull request #6654 from oranagra/fix_module_info_warning
fix possible warning on incomplete struct init
2020-04-02 16:32:20 +02:00
Salvatore SanfilippoandGitHub 10b626b3d5 Merge pull request #6546 from guybe7/fix_neg_zero
Make sure Redis does not reply with negative zero
2020-04-02 16:26:57 +02:00
Salvatore SanfilippoandGitHub 2f53ad30ee Merge pull request #6813 from guybe7/debug_object_pass_keyname
DEBUG OBJECT should pass keyname to module when loading
2020-04-02 16:20:48 +02:00
antirez 5a3d85745a LCS: output LCS len as well in IDX mode. 2020-04-02 16:15:17 +02:00
antirez 3f96e1623d LCS: MINMATCHLEN and WITHMATCHLEN options. 2020-04-02 13:37:35 +02:00
Salvatore SanfilippoandGitHub 88351275fe Merge pull request #6960 from devnexen/debug_arm_linux
debug, dump registers on arm too.
2020-04-02 11:26:08 +02:00
Salvatore SanfilippoandGitHub fa4f50739a Merge pull request #7006 from hwware/fixspelling
fix spelling in cluster.c clusterDelNode
2020-04-02 11:23:53 +02:00
Salvatore SanfilippoandGitHub dfef407499 Merge pull request #7029 from valentinogeron/fix-xack
XACK should be executed in a "all or nothing" fashion.
2020-04-02 11:23:23 +02:00
Salvatore SanfilippoandGitHub 8d6dc136e1 Merge pull request #7041 from hwware/trackingfix
CLIENT TRACKING Command Fix: Add More Checking for OPTIN/OPTOUT mode
2020-04-02 11:22:30 +02:00
Salvatore SanfilippoandGitHub b73d87f5e5 Merge pull request #7039 from guybe7/persist_ksn
PERSIST should notify a keyspace event
2020-04-02 11:21:20 +02:00
Salvatore SanfilippoandGitHub babada8b5f Merge pull request #7042 from guybe7/stream_cg_last_id_fix
streamReplyWithRange: Redundant XSETIDs to replica
2020-04-02 11:20:54 +02:00
antirez 80ec0431e8 Simplify comment in moduleTryServeClientBlockedOnKey(). 2020-04-02 11:20:09 +02:00
antirez 545a5046f5 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-04-02 11:18:33 +02:00
Salvatore SanfilippoandGitHub 10afb9639a Merge pull request #6798 from guybe7/module_circular_block
Fix memory corruption in moduleHandleBlockedClients
2020-04-02 11:17:29 +02:00
antirez 88e66ecf94 LCS: 7x speedup by accessing the array with better locality. 2020-04-01 23:45:07 +02:00
antirez 8cdc15c309 LCS: implement KEYS option. 2020-04-01 22:11:59 +02:00
antirez 4cbf3f5ddd LCS: other fixes to range emission. 2020-04-01 17:36:32 +02:00
antirez c9c03c3ee6 LCS: fix emission of last range starting at index 0. 2020-04-01 17:14:40 +02:00
antirez b3400559be LCS: implement range indexes option. 2020-04-01 17:11:31 +02:00
antirez 1010c1b43e LCS: initial functionality implemented. 2020-04-01 16:13:18 +02:00
Guy Benoish c4dc5b80b2 Fix memory corruption in moduleHandleBlockedClients
By using a "circular BRPOPLPUSH"-like scenario it was
possible the get the same client on db->blocking_keys
twice (See comment in moduleTryServeClientBlockedOnKey)

The fix was actually already implememnted in
moduleTryServeClientBlockedOnKey but it had a bug:
the funxction should return 0 or 1 (not OK or ERR)

Other changes:
1. Added two commands to blockonkeys.c test module (To
   reproduce the case described above)
2. Simplify blockonkeys.c in order to make testing easier
3. cast raxSize() to avoid warning with format spec
2020-04-01 12:53:26 +03:00
antirez 38076fd6ba cast raxSize() to avoid warning with format spec. 2020-03-31 17:41:46 +02:00
antirez 1d13ff0b39 Minor changes to #7037. 2020-03-31 17:10:09 +02:00
Salvatore SanfilippoandGitHub 0c52ce6c8e Merge pull request #7037 from guybe7/fix_module_replicate_multi
Modules: Test MULTI/EXEC replication of RM_Replicate
2020-03-31 17:00:57 +02:00
Salvatore SanfilippoandGitHub a20c206de7 Merge pull request #7048 from guybe7/rename_unblock_stream
RENAME can unblock XREADGROUP
2020-03-31 16:55:29 +02:00
Guy Benoish 6c8221580c RENAME can unblock XREADGROUP
Other changes:
Support stream in serverLogObjectDebugInfo
2020-03-31 17:41:10 +03:00
Guy Benoish d6eb3afd13 Modules: Test MULTI/EXEC replication of RM_Replicate
Makse sure call() doesn't wrap replicated commands with
a redundant MULTI/EXEC

Other, unrelated changes:
1. Formatting compiler warning in INFO CLIENTS
2. Use CLIENT_ID_AOF instead of UINT64_MAX
2020-03-31 13:55:51 +03:00
antirez 1b4bc60999 Merge branch 'pubsub_patterns_boost' of https://github.com/leeyiw/redis into leeyiw-pubsub_patterns_boost 2020-03-31 12:40:08 +02:00
antirez 4379b8b411 Fix the propagate Tcl test after module changes. 2020-03-31 12:09:38 +02:00
antirez 95f154985c Modify the propagate unit test to show more cases. 2020-03-31 12:04:06 +02:00
antirez 9dcf878f1b Fix module commands propagation double MULTI bug.
37a10cef introduced automatic wrapping of MULTI/EXEC for the
alsoPropagate API. However this collides with the built-in mechanism
already present in module.c. To avoid complex changes near Redis 6 GA
this commit introduces the ability to exclude call() MUTLI/EXEC wrapping
for also propagate in order to continue to use the old code paths in
module.c.
2020-03-31 11:00:45 +02:00
antirez 7698316fda Fix RM_Call() stale comment due to cut&paste. 2020-03-31 10:27:47 +02:00
antirez f6d5996ead Merge branch 'unstable' of github.com:/antirez/redis into unstable 2020-03-30 17:29:04 +02:00
antirez 3ef59b50c4 Precise timeouts: reference client pointer directly. 2020-03-30 15:22:59 +02:00
Guy Benoish 12d7479144 streamReplyWithRange: Redundant XSETIDs to replica
propagate_last_id is declared outside of the loop but used
only from within the loop. Once it's '1' it will never go
back to '0' and will replicate XSETID even for IDs that
don't actually change the last_id.
While not a serious bug (XSETID always used group->last_id
so there's no risk), it does causes redundant traffic
between master and its replicas
2020-03-30 10:53:03 +03:00
hwware 86c76cad74 add check for not switching between optin optout mode directly 2020-03-29 23:20:54 -04:00
hwware 1ef44f8243 add check for not providing both optin optout flag 2020-03-29 23:06:50 -04:00
Guy Benoish 3f0afab301 PERSIST should notify a keyspace event 2020-03-29 17:50:42 +03:00
Salvatore SanfilippoandGitHub aca7f36b57 Merge pull request #7033 from OMG-By/unstable
fix: dict.c->dictResize()->minimal  type
2020-03-29 16:17:52 +02:00
OMG-ByandGitHub 6d2f1188ac fix: dict.c->dictResize()->minimal type 2020-03-29 00:04:59 +08:00
Salvatore SanfilippoandGitHub e8afadd52c Merge pull request #7032 from soloestoy/psync2-meaningful-offset-bugfix
PSYNC2: reset backlog_idx and master_repl_offset correctly
2020-03-28 14:52:11 +01:00
zhaozhao.zz 36ee294e83 PSYNC2: reset backlog_idx and master_repl_offset correctly 2020-03-28 20:59:01 +08:00
antirez dd7e61d77f timeout.c created: move client timeouts code there. 2020-03-27 16:35:03 +01:00
antirez 0e22cb2680 Precise timeouts: cleaup the table on unblock.
Now that this mechanism is the sole one used for blocked clients
timeouts, it is more wise to cleanup the table when the client unblocks
for any reason. We use a flag: CLIENT_IN_TO_TABLE, in order to avoid a
radix tree lookup when the client was already removed from the table
because we processed it by scanning the radix tree.
2020-03-27 16:35:03 +01:00
antirez 13e4c2a9b1 Precise timeouts: fix comments after functional change. 2020-03-27 16:35:03 +01:00
antirez aa9d92d94a Precise timeouts: use only radix tree for timeouts. 2020-03-27 16:35:03 +01:00
antirez 077f965426 Precise timeouts: fast exit for clientsHandleShortTimeout(). 2020-03-27 16:35:03 +01:00
antirez 8d11e0df7a Precise timeouts: fix bugs in initial implementation. 2020-03-27 16:35:03 +01:00
antirez 324a8c91d0 Precise timeouts: working initial implementation. 2020-03-27 16:35:03 +01:00
antirez 8d116cc8a2 Precise timeouts: refactor unblocking on timeout. 2020-03-27 16:35:02 +01:00
antirez 97f1c808ce PSYNC2: fix backlog_idx when adjusting for meaningful offset
See #7002.
2020-03-27 16:20:02 +01:00
Valentino Geron 9a1843ef2d XREAD and XREADGROUP should not be allowed from scripts when BLOCK option is being used 2020-03-26 15:46:31 +02:00
Valentino Geron 1547d72cf3 XACK should be executed in a "all or nothing" fashion.
First, we must parse the IDs, so that we abort ASAP.
The return value of this command cannot be an error if
the client successfully acknowledged some messages,
so it should be executed in a "all or nothing" fashion.
2020-03-26 15:40:23 +02:00
hwware 262262fbb2 fix spelling in cluster.c 2020-03-18 09:48:03 -04:00
David Carlier 6548157293 debug, dump registers on arm too. 2020-03-07 10:43:41 +00:00
Itamar HaberandGitHub d25709cdea Update create-cluster 2020-02-28 13:36:50 +02:00
Itamar Haber 0285c22a2d Adds BIN_PATH to create-cluster
Allows for setting the binaries path if used outside the upstream repo.

Also documents `call` in usage clause (TODO: port to
`redis-cli --cluster call` or just deprecate it).
2020-02-28 13:35:10 +02:00
Oran Agra a477b68109 change CI to build and run the module api tests 2020-02-27 11:33:07 +02:00
Guy Benoish d72b7ed4fb DEBUG OBJECT should pass keyname to module when loading 2020-01-30 19:15:12 +05:30
srzhao 38f6207f88 Check OOM at script start to get stable lua OOM state.
Checking OOM by `getMaxMemoryState` inside script might get different result
with `freeMemoryIfNeededAndSafe` at script start, because lua stack and
arguments also consume memory.

This leads to memory `borderline` when memory grows near server.maxmemory:

- `freeMemoryIfNeededAndSafe` at script start detects no OOM, no memory freed
- `getMaxMemoryState` inside script detects OOM, script aborted

We solve this 'borderline' issue by saving OOM state at script start to get
stable lua OOM state.

related to issue #6565 and #5250.
2020-01-20 21:34:05 +08:00
Oran Agra 660645c742 modules don't signalModifiedKey in setKey() since that's done (optionally) in RM_CloseKey 2019-12-23 10:15:52 +02:00
zhaozhao.zz 3c0ed0309a lazyfree: add a new configuration lazyfree-lazy-user-del
Delete keys in async way when executing DEL command, if
lazyfree-lazy-user-del is yes.
2019-12-18 16:54:49 +08:00
Oran Agra 173cca5413 fix possible warning on incomplete struct init 2019-12-10 11:16:13 +02:00
Guy Benoish 8beec4f0e7 Make sure Redis does not reply with negative zero 2019-11-05 19:23:37 +05:30
伯成 dfb12f0628 Boost up performance for redis PUB-SUB patterns matching
If lots of clients PSUBSCRIBE to same patterns, multiple pattens matching will take place. This commit change it into just one single pattern matching by using a `dict *` to store the unique pattern and which clients subscribe to it.
2018-03-01 11:46:56 +08:00
94 changed files with 4279 additions and 1305 deletions
+21 -1
View File
@@ -3,6 +3,7 @@ name: CI
on: [push, pull_request]
jobs:
test-ubuntu-latest:
runs-on: ubuntu-latest
steps:
@@ -12,7 +13,9 @@ jobs:
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --clients 2 --verbose
./runtest --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
build-ubuntu-old:
runs-on: ubuntu-16.04
@@ -27,3 +30,20 @@ jobs:
- uses: actions/checkout@v1
- name: make
run: make
biuld-32bit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
make 32bit
build-libc-malloc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make
run: make MALLOC=libc
+79
View File
@@ -0,0 +1,79 @@
name: Daily
on:
schedule:
- cron: '0 7 * * *'
jobs:
test-jemalloc:
runs-on: ubuntu-latest
timeout-minutes: 1200
steps:
- uses: actions/checkout@v1
- name: make
run: make
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
test-libc-malloc:
runs-on: ubuntu-latest
timeout-minutes: 1200
steps:
- uses: actions/checkout@v1
- name: make
run: make MALLOC=libc
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
test-32bit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
make 32bit
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
test-tls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make
run: |
make BUILD_TLS=yes
- name: test
run: |
sudo apt-get install tcl8.5 tcl-tls
./utils/gen-test-certs.sh
./runtest --accurate --verbose --tls
- name: module api test
run: ./runtest-moduleapi --verbose --tls
test-valgrind:
runs-on: ubuntu-latest
timeout-minutes: 14400
steps:
- uses: actions/checkout@v1
- name: make
run: make valgrind
- name: test
run: |
sudo apt-get install tcl8.5 valgrind -y
./runtest --valgrind --verbose --clients 1
- name: module api test
run: ./runtest-moduleapi --valgrind --verbose --clients 1
@@ -216,7 +216,7 @@ ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra,
}
JEMALLOC_ALWAYS_INLINE int
iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
iget_defrag_hint(tsdn_t *tsdn, void* ptr) {
int defrag = 0;
rtree_ctx_t rtree_ctx_fallback;
rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
@@ -232,11 +232,22 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
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;
int free_in_slab = extent_nfree_get(slab);
if (free_in_slab) {
const bin_info_t *bin_info = &bin_infos[binind];
int curslabs = bin->stats.curslabs;
size_t curregs = bin->stats.curregs;
if (bin->slabcur) {
/* remove slabcur from the overall utilization */
curregs -= bin_info->nregs - extent_nfree_get(bin->slabcur);
curslabs -= 1;
}
/* Compare the utilization ratio of the slab in question to the total average,
* to avoid precision lost and division, we do that by extrapolating the usage
* of the slab as if all slabs have the same usage. If this slab is less used
* than the average, we'll prefer to evict the data to hopefully more used ones */
defrag = (bin_info->nregs - free_in_slab) * curslabs <= curregs;
}
}
malloc_mutex_unlock(tsdn, &bin->lock);
}
+3 -5
View File
@@ -3326,12 +3326,10 @@ 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.
* returns 1 if the allocation should be moved, and 0 if the allocation be kept.
* 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) {
get_defrag_hint(void* ptr) {
assert(ptr != NULL);
return iget_defrag_hint(TSDN_NULL, ptr, bin_util, run_util);
return iget_defrag_hint(TSDN_NULL, ptr);
}
+59 -12
View File
@@ -176,9 +176,10 @@ tcp-keepalive 300
# tls-cluster yes
# Explicitly specify TLS versions to support. Allowed values are case insensitive
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
# any combination. To enable only TLSv1.2 and TLSv1.3, use:
#
# tls-protocols TLSv1.2
# tls-protocols "TLSv1.2 TLSv1.3"
# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
# about the syntax of this string.
@@ -626,20 +627,23 @@ replica-priority 100
# to track the keys fetched by many clients.
#
# For this reason it is possible to configure a maximum fill value for the
# invalidation table. By default it is set to 10%, and once this limit is
# reached, Redis will start to evict caching slots in the invalidation table
# even if keys are not modified, just to reclaim memory: this will in turn
# invalidation table. By default it is set to 1M of keys, and once this limit
# is reached, Redis will start to evict keys in the invalidation table
# even if they were not modified, just to reclaim memory: this will in turn
# force the clients to invalidate the cached values. Basically the table
# maximum fill rate is a trade off between the memory you want to spend server
# maximum size is a trade off between the memory you want to spend server
# side to track information about who cached what, and the ability of clients
# to retain cached objects in memory.
#
# If you set the value to 0, it means there are no limits, and all the 16
# millions of caching slots can be used at the same time. In the "stats"
# INFO section, you can find information about the amount of caching slots
# used at every given moment.
# If you set the value to 0, it means there are no limits, and Redis will
# retain as many keys as needed in the invalidation table.
# In the "stats" INFO section, you can find information about the number of
# keys in the invalidation table at every given moment.
#
# tracking-table-max-fill 10
# Note: when key tracking is used in broadcasting mode, no memory is used
# in the server side so this setting is useless.
#
# tracking-table-max-keys 1000000
################################## SECURITY ###################################
@@ -737,6 +741,14 @@ replica-priority 100
# For more information about ACL configuration please refer to
# the Redis web site at https://redis.io/topics/acl
# ACL LOG
#
# The ACL Log tracks failed commands and authentication events associated
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
acllog-max-len 128
# Using an external ACL file
#
# Instead of configuring users here in this file, it is possible to use
@@ -943,13 +955,20 @@ replica-priority 100
# 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
# in order to instead release memory in a non-blocking way like if UNLINK
# was called, using the following configuration directives:
# was called, using the following configuration directives.
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
# It is also possible, for the case when to replace the user code DEL calls
# with UNLINK calls is not easy, to modify the default behavior of the DEL
# command to act exactly like UNLINK, using the following configuration
# directive:
lazyfree-lazy-user-del no
################################ THREADED I/O #################################
# Redis is mostly single threaded, however there are certain threaded
@@ -1762,3 +1781,31 @@ rdb-save-incremental-fsync yes
# Maximum number of set/hash/zset/list fields that will be processed from
# the main dictionary scan
# active-defrag-max-scan-fields 1000
# Jemalloc background thread for purging will be enabled by default
jemalloc-bg-thread yes
# It is possible to pin different threads and processes of Redis to specific
# CPUs in your system, in order to maximize the performances of the server.
# This is useful both in order to pin different Redis threads in different
# CPUs, but also in order to make sure that multiple Redis instances running
# in the same host will be pinned to different CPUs.
#
# Normally you can do this using the "taskset" command, however it is also
# possible to this via Redis configuration directly, both in Linux and FreeBSD.
#
# You can pin the server/IO threads, bio threads, aof rewrite child process, and
# the bgsave child process. The syntax to specify the cpu list is the same as
# the taskset command:
#
# Set redis server/io threads to cpu affinity 0,2,4,6:
# server_cpulist 0-7:2
#
# Set bio threads to cpu affinity 1,3:
# bio_cpulist 1,3
#
# Set aof rewrite child process to cpu affinity 8,9,10,11:
# aof_rewrite_cpulist 8-11
#
# Set bgsave child process to cpu affinity 1,10,11
# bgsave_cpulist 1,10-11
+12 -2
View File
@@ -128,6 +128,14 @@ else
ifeq ($(uname_S),DragonFly)
# FreeBSD
FINAL_LIBS+= -lpthread -lexecinfo
else
ifeq ($(uname_S),OpenBSD)
# OpenBSD
FINAL_LIBS+= -lpthread -lexecinfo
else
ifeq ($(uname_S),NetBSD)
# NetBSD
FINAL_LIBS+= -lpthread -lexecinfo
else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
@@ -138,6 +146,8 @@ endif
endif
endif
endif
endif
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
@@ -206,9 +216,9 @@ 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 lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.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 crcspeed.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 lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crc64.o siphash.o crc16.o
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o
REDIS_CHECK_RDB_NAME=redis-check-rdb
+100 -38
View File
@@ -30,6 +30,7 @@
#include "server.h"
#include "sha256.h"
#include <fcntl.h>
#include <ctype.h>
/* =============================================================================
* Global state for ACLs
@@ -165,10 +166,41 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
return sdsnewlen(hex,HASH_PASSWORD_LEN);
}
/* Given a hash and the hash length, returns C_OK if it is a valid password
* hash, or C_ERR otherwise. */
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
if (hashlen != HASH_PASSWORD_LEN) {
return C_ERR;
}
/* Password hashes can only be characters that represent
* hexadecimal values, which are numbers and lowercase
* characters 'a' through 'f'. */
for(int i = 0; i < HASH_PASSWORD_LEN; i++) {
char c = hash[i];
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
return C_ERR;
}
}
return C_OK;
}
/* =============================================================================
* Low level ACL API
* ==========================================================================*/
/* Return 1 if the specified string contains spaces or null characters.
* We do this for usernames and key patterns for simpler rewriting of
* ACL rules, presentation on ACL list, and to avoid subtle security bugs
* that may arise from parsing the rules in presence of escapes.
* The function returns 0 if the string has no spaces. */
int ACLStringHasSpaces(const char *s, size_t len) {
for (size_t i = 0; i < len; i++) {
if (isspace(s[i]) || s[i] == 0) return 1;
}
return 0;
}
/* Given the category name the command returns the corresponding flag, or
* zero if there is no match. */
uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
@@ -343,12 +375,13 @@ int ACLUserCanExecuteFutureCommands(user *u) {
* to skip the command bit explicit test. */
void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
uint64_t word, bit;
if (value == 0) u->flags &= ~USER_FLAG_ALLCOMMANDS;
if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return;
if (value)
if (value) {
u->allowed_commands[word] |= bit;
else
} else {
u->allowed_commands[word] &= ~bit;
u->flags &= ~USER_FLAG_ALLCOMMANDS;
}
}
/* This is like ACLSetUserCommandBit(), but instead of setting the specified
@@ -690,7 +723,8 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
*
* When an error is returned, errno is set to the following values:
*
* EINVAL: The specified opcode is not understood.
* EINVAL: The specified opcode is not understood or the key pattern is
* invalid (contains non allowed characters).
* ENOENT: The command name or command category provided with + or - is not
* known.
* EBUSY: The subcommand you want to add is about a command that is currently
@@ -739,22 +773,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
if (op[0] == '>') {
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
errno = EBADMSG;
return C_ERR;
}
/* Password hashes can only be characters that represent
* hexadecimal values, which are numbers and lowercase
* characters 'a' through 'f'.
*/
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
char c = op[i];
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
errno = EBADMSG;
return C_ERR;
}
}
newpass = sdsnewlen(op+1,oplen-1);
}
@@ -770,7 +792,7 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
if (op[0] == '<') {
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
errno = EBADMSG;
return C_ERR;
}
@@ -789,6 +811,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
errno = EEXIST;
return C_ERR;
}
if (ACLStringHasSpaces(op+1,oplen-1)) {
errno = EINVAL;
return C_ERR;
}
sds newpat = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->patterns,newpat);
/* Avoid re-adding the same pattern multiple times. */
@@ -820,7 +846,6 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
errno = ENOENT;
return C_ERR;
}
unsigned long id = ACLGetCommandID(copy);
/* The subcommand cannot be empty, so things like DEBUG|
* are syntax errors of course. */
@@ -833,6 +858,7 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
/* The command should not be set right now in the command
* bitmap, because adding a subcommand of a fully added
* command is probably an error on the user side. */
unsigned long id = ACLGetCommandID(copy);
if (ACLGetUserCommandBit(u,id) == 1) {
zfree(copy);
errno = EBUSY;
@@ -1164,6 +1190,12 @@ int ACLLoadConfiguredUsers(void) {
while ((ln = listNext(&li)) != NULL) {
sds *aclrules = listNodeValue(ln);
sds username = aclrules[0];
if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) {
serverLog(LL_WARNING,"Spaces not allowed in ACL usernames");
return C_ERR;
}
user *u = ACLCreateUser(username,sdslen(username));
if (!u) {
u = ACLGetUserByName(username,sdslen(username));
@@ -1289,6 +1321,14 @@ sds ACLLoadFromFile(const char *filename) {
continue;
}
/* Spaces are not allowed in usernames. */
if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) {
errors = sdscatprintf(errors,
"'%s:%d: username '%s' contains invalid characters. ",
server.acl_filename, linenum, argv[1]);
continue;
}
/* Try to process the line using the fake user to validate iif
* the rules are able to apply cleanly. */
ACLSetUser(fakeuser,"reset",-1);
@@ -1590,7 +1630,7 @@ void addACLLogEntry(client *c, int reason, int keypos, sds username) {
* ACL SETUSER <username> ... acl rules ...
* ACL DELUSER <username> [...]
* ACL GETUSER <username>
* ACL GENPASS
* ACL GENPASS [<bits>]
* ACL WHOAMI
* ACL LOG [<count> | RESET]
*/
@@ -1598,6 +1638,13 @@ void aclCommand(client *c) {
char *sub = c->argv[1]->ptr;
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
sds username = c->argv[2]->ptr;
/* Check username validity. */
if (ACLStringHasSpaces(username,sdslen(username))) {
addReplyErrorFormat(c,
"Usernames can't contain spaces or null characters");
return;
}
/* Create a temporary user to validate and stage all changes against
* before applying to an existing user or creating a new user. If all
* arguments are valid the user parameters will all be applied together.
@@ -1775,16 +1822,31 @@ void aclCommand(client *c) {
}
dictReleaseIterator(di);
setDeferredArrayLen(c,dl,arraylen);
} else if (!strcasecmp(sub,"genpass") && c->argc == 2) {
char pass[32]; /* 128 bits of actual pseudo random data. */
getRandomHexChars(pass,sizeof(pass));
addReplyBulkCBuffer(c,pass,sizeof(pass));
} else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) {
#define GENPASS_MAX_BITS 4096
char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */
long bits = 256; /* By default generate 256 bits passwords. */
if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL)
!= C_OK) return;
if (bits <= 0 || bits > GENPASS_MAX_BITS) {
addReplyErrorFormat(c,
"ACL GENPASS argument must be the number of "
"bits for the output password, a positive number "
"up to %d",GENPASS_MAX_BITS);
return;
}
long chars = (bits+3)/4; /* Round to number of characters to emit. */
getRandomHexChars(pass,chars);
addReplyBulkCBuffer(c,pass,chars);
} else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) {
long count = 10; /* Number of entries to emit by default. */
/* Parse the only argument that LOG may have: it could be either
* the number of entires the user wants to display, or alternatively
* the "RESET" command in order to flush the old entires. */
* the number of entries the user wants to display, or alternatively
* the "RESET" command in order to flush the old entries. */
if (c->argc == 3) {
if (!strcasecmp(c->argv[2]->ptr,"reset")) {
listSetFreeMethod(ACLLog,ACLFreeLogEntry);
@@ -1847,18 +1909,18 @@ void aclCommand(client *c) {
}
} else if (!strcasecmp(sub,"help")) {
const char *help[] = {
"LOAD -- Reload users from the ACL file.",
"SAVE -- Save the current config to the ACL file."
"LIST -- Show user details in config file format.",
"USERS -- List all the registered usernames.",
"SETUSER <username> [attribs ...] -- Create or modify a user.",
"GETUSER <username> -- Get the user details.",
"DELUSER <username> [...] -- Delete a list of users.",
"CAT -- List available categories.",
"CAT <category> -- List commands inside category.",
"GENPASS -- Generate a secure user password.",
"WHOAMI -- Return the current connection username.",
"LOG [<count> | RESET] -- Show the ACL log entries.",
"LOAD -- Reload users from the ACL file.",
"SAVE -- Save the current config to the ACL file.",
"LIST -- Show user details in config file format.",
"USERS -- List all the registered usernames.",
"SETUSER <username> [attribs ...] -- Create or modify a user.",
"GETUSER <username> -- Get the user details.",
"DELUSER <username> [...] -- Delete a list of users.",
"CAT -- List available categories.",
"CAT <category> -- List commands inside category.",
"GENPASS [<bits>] -- Generate a secure user password.",
"WHOAMI -- Return the current connection username.",
"LOG [<count> | RESET] -- Show the ACL log entries.",
NULL
};
addReplyHelp(c,help);
+17 -3
View File
@@ -327,12 +327,11 @@ listNode *listIndex(list *list, long index) {
}
/* Rotate the list removing the tail node and inserting it to the head. */
void listRotate(list *list) {
listNode *tail = list->tail;
void listRotateTailToHead(list *list) {
if (listLength(list) <= 1) return;
/* Detach current tail */
listNode *tail = list->tail;
list->tail = tail->prev;
list->tail->next = NULL;
/* Move it as head */
@@ -342,6 +341,21 @@ void listRotate(list *list) {
list->head = tail;
}
/* Rotate the list removing the head node and inserting it to the tail. */
void listRotateHeadToTail(list *list) {
if (listLength(list) <= 1) return;
listNode *head = list->head;
/* Detach current head */
list->head = head->next;
list->head->prev = NULL;
/* Move it as tail */
list->tail->next = head;
head->next = NULL;
head->prev = list->tail;
list->tail = head;
}
/* Add all the elements of the list 'o' at the end of the
* list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o) {
+2 -1
View File
@@ -85,7 +85,8 @@ listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotate(list *list);
void listRotateTailToHead(list *list);
void listRotateHeadToTail(list *list);
void listJoin(list *l, list *o);
/* Directions for iterators */
+17 -3
View File
@@ -238,6 +238,7 @@ long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
te->clientData = clientData;
te->prev = NULL;
te->next = eventLoop->timeEventHead;
te->refcount = 0;
if (te->next)
te->next->prev = te;
eventLoop->timeEventHead = te;
@@ -316,6 +317,13 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
/* Remove events scheduled for deletion. */
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
/* If a reference exists for this timer event,
* don't free it. This is currently incremented
* for recursive timerProc calls */
if (te->refcount) {
te = next;
continue;
}
if (te->prev)
te->prev->next = te->next;
else
@@ -345,7 +353,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
int retval;
id = te->id;
te->refcount++;
retval = te->timeProc(eventLoop, id, te->clientData);
te->refcount--;
processed++;
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
@@ -370,6 +380,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
* if flags has AE_CALL_BEFORE_SLEEP set, the beforesleep callback is called.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
@@ -428,6 +439,9 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
tvp = &tv;
}
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
eventLoop->beforesleep(eventLoop);
/* Call the multiplexing API, will return only on timeout or when
* some event fires. */
numevents = aeApiPoll(eventLoop, tvp);
@@ -522,9 +536,9 @@ int aeWait(int fd, int mask, long long milliseconds) {
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}
+7 -4
View File
@@ -47,11 +47,12 @@
things to disk before sending replies, and want
to do that in a group fashion. */
#define AE_FILE_EVENTS 1
#define AE_TIME_EVENTS 2
#define AE_FILE_EVENTS (1<<0)
#define AE_TIME_EVENTS (1<<1)
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
#define AE_DONT_WAIT 4
#define AE_CALL_AFTER_SLEEP 8
#define AE_DONT_WAIT (1<<2)
#define AE_CALL_BEFORE_SLEEP (1<<3)
#define AE_CALL_AFTER_SLEEP (1<<4)
#define AE_NOMORE -1
#define AE_DELETED_EVENT_ID -1
@@ -85,6 +86,8 @@ typedef struct aeTimeEvent {
void *clientData;
struct aeTimeEvent *prev;
struct aeTimeEvent *next;
int refcount; /* refcount to prevent timer events from being
* freed in recursive time event calls. */
} aeTimeEvent;
/* A fired event */
+2 -1
View File
@@ -831,7 +831,7 @@ int loadAppendOnlyFile(char *filename) {
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
/* Run the command in the context of a fake client */
fakeClient->cmd = cmd;
fakeClient->cmd = fakeClient->lastcmd = cmd;
if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand)
{
@@ -1596,6 +1596,7 @@ int rewriteAppendOnlyFileBackground(void) {
/* Child */
redisSetProcTitle("redis-aof-rewrite");
redisSetCpuAffinity(server.aof_rewrite_cpulist);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
+15 -1
View File
@@ -154,6 +154,20 @@ void *bioProcessBackgroundJobs(void *arg) {
return NULL;
}
switch (type) {
case BIO_CLOSE_FILE:
redis_set_thread_title("bio_close_file");
break;
case BIO_AOF_FSYNC:
redis_set_thread_title("bio_aof_fsync");
break;
case BIO_LAZY_FREE:
redis_set_thread_title("bio_lazy_free");
break;
}
redisSetCpuAffinity(server.bio_cpulist);
/* Make the thread killable at any time, so that bioKillThreads()
* can work reliably. */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
@@ -254,7 +268,7 @@ void bioKillThreads(void) {
int err, j;
for (j = 0; j < BIO_NUM_OPS; j++) {
if (pthread_cancel(bio_threads[j]) == 0) {
if (bio_threads[j] && pthread_cancel(bio_threads[j]) == 0) {
if ((err = pthread_join(bio_threads[j],NULL)) != 0) {
serverLog(LL_WARNING,
"Bio thread for job type #%d can be joined: %s",
+5 -5
View File
@@ -269,7 +269,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
* then zero is returned, otherwise in case of overflow, 1 is returned,
* otherwise in case of underflow, -1 is returned.
*
* When non-zero is returned (oferflow or underflow), if not NULL, *limit is
* When non-zero is returned (overflow or underflow), if not NULL, *limit is
* set to the value the operation should result when an overflow happens,
* depending on the specified overflow semantics:
*
@@ -554,7 +554,7 @@ void setbitCommand(client *c) {
byteval &= ~(1 << bit);
byteval |= ((on & 0x1) << bit);
((uint8_t*)o->ptr)[byte] = byteval;
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty++;
addReply(c, bitval ? shared.cone : shared.czero);
@@ -754,11 +754,11 @@ void bitopCommand(client *c) {
/* Store the computed value into the target key */
if (maxlen) {
o = createObject(OBJ_STRING,res);
setKey(c->db,targetkey,o);
setKey(c,c->db,targetkey,o);
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
decrRefCount(o);
} else if (dbDelete(c->db,targetkey)) {
signalModifiedKey(c->db,targetkey);
signalModifiedKey(c,c->db,targetkey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
}
server.dirty++;
@@ -1135,7 +1135,7 @@ void bitfieldGeneric(client *c, int flags) {
}
if (changes) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty += changes;
}
+36 -61
View File
@@ -31,9 +31,6 @@
*
* API:
*
* getTimeoutFromObjectOrReply() is just an utility function to parse a
* timeout argument since blocking operations usually require a timeout.
*
* blockClient() set the CLIENT_BLOCKED flag in the client, and set the
* specified block type 'btype' filed to one of BLOCKED_* macros.
*
@@ -67,41 +64,20 @@
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
/* Get a timeout value from an object and store it into 'timeout'.
* The final timeout is always stored as milliseconds as a time where the
* timeout will expire, however the parsing is performed according to
* the 'unit' that can be seconds or milliseconds.
*
* Note that if the timeout is zero (usually from the point of view of
* commands API this means no timeout) the value stored into 'timeout'
* is zero. */
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
long long tval;
long double ftval;
if (unit == UNIT_SECONDS) {
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
"timeout is not an float or out of range") != C_OK)
return C_ERR;
tval = (long long) (ftval * 1000.0);
} else {
if (getLongLongFromObjectOrReply(c,object,&tval,
"timeout is not an integer or out of range") != C_OK)
return C_ERR;
}
if (tval < 0) {
addReplyError(c,"timeout is negative");
return C_ERR;
}
if (tval > 0) {
tval += mstime();
}
*timeout = tval;
return C_OK;
}
/* This structure represents the blocked key information that we store
* in the client structure. Each client blocked on keys, has a
* client->bpop.keys hash table. The keys of the hash table are Redis
* keys pointers to 'robj' structures. The value is this structure.
* The structure has two goals: firstly we store the list node that this
* client uses to be listed in the database "blocked clients for this key"
* list, so we can later unblock in O(1) without a list scan.
* Secondly for certain blocking types, we have additional info. Right now
* the only use for additional info we have is when clients are blocked
* on streams, as we have to remember the ID it blocked for. */
typedef struct bkinfo {
listNode *listnode; /* List node for db->blocking_keys[key] list. */
streamID stream_id; /* Stream ID if we blocked in a stream. */
} bkinfo;
/* Block a client for the specific operation type. Once the CLIENT_BLOCKED
* flag is set client query buffer is not longer processed, but accumulated,
@@ -111,7 +87,7 @@ void blockClient(client *c, int btype) {
c->btype = btype;
server.blocked_clients++;
server.blocked_clients_by_type[btype]++;
addClientToShortTimeoutTable(c);
addClientToTimeoutTable(c);
}
/* This function is called in the beforeSleep() function of the event loop
@@ -134,7 +110,7 @@ void processUnblockedClients(void) {
* the code is conceptually more correct this way. */
if (!(c->flags & CLIENT_BLOCKED)) {
if (c->querybuf && sdslen(c->querybuf) > 0) {
processInputBufferAndReplicate(c);
processInputBuffer(c);
}
}
}
@@ -186,6 +162,7 @@ void unblockClient(client *c) {
server.blocked_clients_by_type[c->btype]--;
c->flags &= ~CLIENT_BLOCKED;
c->btype = BLOCKED_NONE;
removeClientFromTimeoutTable(c);
queueClientForReprocessing(c);
}
@@ -249,8 +226,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
if (receiver->btype != BLOCKED_LIST) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
listRotateHeadToTail(clients);
continue;
}
@@ -311,8 +287,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
if (receiver->btype != BLOCKED_ZSET) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
listRotateHeadToTail(clients);
continue;
}
@@ -358,8 +333,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
while((ln = listNext(&li))) {
client *receiver = listNodeValue(ln);
if (receiver->btype != BLOCKED_STREAM) continue;
streamID *gt = dictFetchValue(receiver->bpop.keys,
rl->key);
bkinfo *bki = dictFetchValue(receiver->bpop.keys,rl->key);
streamID *gt = &bki->stream_id;
/* If we blocked in the context of a consumer
* group, we need to resolve the group and update the
@@ -396,9 +371,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
int noack = 0;
if (group) {
consumer = streamLookupConsumer(group,
receiver->bpop.xread_consumer->ptr,
1);
consumer =
streamLookupConsumer(group,
receiver->bpop.xread_consumer->ptr,
SLC_NONE);
noack = receiver->bpop.xread_group_noack;
}
@@ -457,8 +433,7 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
* ready to be served, so they'll remain in the list
* sometimes. We want also be able to skip clients that are
* not blocked for the MODULE type safely. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
listRotateHeadToTail(clients);
if (receiver->btype != BLOCKED_MODULE) continue;
@@ -589,17 +564,15 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
if (target != NULL) incrRefCount(target);
for (j = 0; j < numkeys; j++) {
/* The value associated with the key name in the bpop.keys dictionary
* is NULL for lists and sorted sets, or the stream ID for streams. */
void *key_data = NULL;
if (btype == BLOCKED_STREAM) {
key_data = zmalloc(sizeof(streamID));
memcpy(key_data,ids+j,sizeof(streamID));
}
/* Allocate our bkinfo structure, associated to each key the client
* is blocked for. */
bkinfo *bki = zmalloc(sizeof(*bki));
if (btype == BLOCKED_STREAM)
bki->stream_id = ids[j];
/* If the key already exists in the dictionary ignore it. */
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
zfree(key_data);
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
zfree(bki);
continue;
}
incrRefCount(keys[j]);
@@ -618,6 +591,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
l = dictGetVal(de);
}
listAddNodeTail(l,c);
bki->listnode = listLast(l);
}
blockClient(c,btype);
}
@@ -634,11 +608,12 @@ void unblockClientWaitingData(client *c) {
/* The client may wait for multiple keys, so unblock it for every key. */
while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de);
bkinfo *bki = dictGetVal(de);
/* Remove this client from the list of clients waiting for this key. */
l = dictFetchValue(c->db->blocking_keys,key);
serverAssertWithInfo(c,key,l != NULL);
listDelNode(l,listSearchKey(l,c));
listDelNode(l,bki->listnode);
/* If the list is empty we need to remove it to avoid wasting memory */
if (listLength(l) == 0)
dictDelete(c->db->blocking_keys,key);
+65 -28
View File
@@ -749,6 +749,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->slaves = NULL;
node->slaveof = NULL;
node->ping_sent = node->pong_received = 0;
node->data_received = 0;
node->fail_time = 0;
node->link = NULL;
memset(node->ip,0,sizeof(node->ip));
@@ -933,7 +934,7 @@ int clusterAddNode(clusterNode *node) {
return (retval == DICT_OK) ? C_OK : C_ERR;
}
/* Remove a node from the cluster. The functio performs the high level
/* Remove a node from the cluster. The function performs the high level
* cleanup, calling freeClusterNode() for the low level cleanup.
* Here we do the following:
*
@@ -1678,6 +1679,7 @@ int clusterProcessPacket(clusterLink *link) {
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
uint32_t totlen = ntohl(hdr->totlen);
uint16_t type = ntohs(hdr->type);
mstime_t now = mstime();
if (type < CLUSTERMSG_TYPE_COUNT)
server.cluster->stats_bus_messages_received[type]++;
@@ -1739,8 +1741,17 @@ int clusterProcessPacket(clusterLink *link) {
if (totlen != explen) return 1;
}
/* Check if the sender is a known node. */
/* Check if the sender is a known node. Note that for incoming connections
* we don't store link->node information, but resolve the node by the
* ID in the header each time in the current implementation. */
sender = clusterLookupNode(hdr->sender);
/* Update the last time we saw any data from this node. We
* use this in order to avoid detecting a timeout from a node that
* is just sending a lot of data in the cluster bus, for instance
* because of Pub/Sub. */
if (sender) sender->data_received = now;
if (sender && !nodeInHandshake(sender)) {
/* Update our curretEpoch if we see a newer epoch in the cluster. */
senderCurrentEpoch = ntohu64(hdr->currentEpoch);
@@ -1755,7 +1766,7 @@ int clusterProcessPacket(clusterLink *link) {
}
/* Update the replication offset info for this node. */
sender->repl_offset = ntohu64(hdr->offset);
sender->repl_offset_time = mstime();
sender->repl_offset_time = now;
/* If we are a slave performing a manual failover and our master
* sent its offset while already paused, populate the MF state. */
if (server.cluster->mf_end &&
@@ -1869,7 +1880,7 @@ int clusterProcessPacket(clusterLink *link) {
* address. */
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
link->node->name,
(int)(mstime()-(link->node->ctime)),
(int)(now-(link->node->ctime)),
link->node->flags);
link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0';
@@ -1904,7 +1915,7 @@ int clusterProcessPacket(clusterLink *link) {
/* Update our info about the node */
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
link->node->pong_received = mstime();
link->node->pong_received = now;
link->node->ping_sent = 0;
/* The PFAIL condition can be reversed without external
@@ -2051,7 +2062,7 @@ int clusterProcessPacket(clusterLink *link) {
"FAIL message received from %.40s about %.40s",
hdr->sender, hdr->data.fail.about.nodename);
failing->flags |= CLUSTER_NODE_FAIL;
failing->fail_time = mstime();
failing->fail_time = now;
failing->flags &= ~CLUSTER_NODE_PFAIL;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE);
@@ -2104,9 +2115,9 @@ int clusterProcessPacket(clusterLink *link) {
/* Manual failover requested from slaves. Initialize the state
* accordingly. */
resetManualFailover();
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
server.cluster->mf_slave = sender;
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
sender->name);
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
@@ -3529,7 +3540,6 @@ void clusterCron(void) {
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
now = mstime(); /* Use an updated time at every iteration. */
mstime_t delay;
if (node->flags &
(CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
@@ -3553,16 +3563,20 @@ void clusterCron(void) {
this_slaves = okslaves;
}
/* If we are waiting for the PONG more than half the cluster
/* If we are not receiving any data for more than half the cluster
* timeout, reconnect the link: maybe there is a connection
* issue even if the node is alive. */
mstime_t ping_delay = now - node->ping_sent;
mstime_t data_delay = now - node->data_received;
if (node->link && /* is connected */
now - node->link->ctime >
server.cluster_node_timeout && /* was not already reconnected */
node->ping_sent && /* we already sent a ping */
node->pong_received < node->ping_sent && /* still waiting pong */
/* and we are waiting for the pong more than timeout/2 */
now - node->ping_sent > server.cluster_node_timeout/2)
ping_delay > server.cluster_node_timeout/2 &&
/* and in such interval we are not seeing any traffic at all. */
data_delay > server.cluster_node_timeout/2)
{
/* Disconnect the link, it will be reconnected automatically. */
freeClusterLink(node->link);
@@ -3594,12 +3608,18 @@ void clusterCron(void) {
/* Check only if we have an active ping for this instance. */
if (node->ping_sent == 0) continue;
/* Compute the delay of the PONG. Note that if we already received
* the PONG, then node->ping_sent is zero, so can't reach this
* code at all. */
delay = now - node->ping_sent;
/* Check if this node looks unreachable.
* Note that if we already received the PONG, then node->ping_sent
* is zero, so can't reach this code at all, so we don't risk of
* checking for a PONG delay if we didn't sent the PING.
*
* We also consider every incoming data as proof of liveness, since
* our cluster bus link is also used for data: under heavy data
* load pong delays are possible. */
mstime_t node_delay = (ping_delay < data_delay) ? ping_delay :
data_delay;
if (delay > server.cluster_node_timeout) {
if (node_delay > server.cluster_node_timeout) {
/* Timeout reached. Set the node as possibly failing if it is
* not already in this state. */
if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) {
@@ -4191,11 +4211,17 @@ void clusterReplyMultiBulkSlots(client *c) {
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
int j = 0, start = -1;
int i, nested_elements = 0;
/* Skip slaves (that are iterated when producing the output of their
* master) and masters not serving any slot. */
if (!nodeIsMaster(node) || node->numslots == 0) continue;
for(i = 0; i < node->numslaves; i++) {
if (nodeFailed(node->slaves[i])) continue;
nested_elements++;
}
for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit, i;
@@ -4203,8 +4229,7 @@ void clusterReplyMultiBulkSlots(client *c) {
if (start == -1) start = j;
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (1). */
void *nested_replylen = addReplyDeferredLen(c);
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
if (bit && j == CLUSTER_SLOTS-1) j++;
@@ -4234,9 +4259,7 @@ void clusterReplyMultiBulkSlots(client *c) {
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredArrayLen(c, nested_replylen, nested_elements);
num_masters++;
}
}
@@ -4966,7 +4989,7 @@ void restoreCommand(client *c) {
rioInitWithBuffer(&payload,c->argv[3]->ptr);
if (((type = rdbLoadObjectType(&payload)) == -1) ||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
((obj = rdbLoadObject(type,&payload,c->argv[1]->ptr)) == NULL))
{
addReplyError(c,"Bad data format");
return;
@@ -4982,7 +5005,7 @@ void restoreCommand(client *c) {
setExpire(c,c->db,c->argv[1],ttl);
}
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
addReply(c,shared.ok);
server.dirty++;
@@ -5098,15 +5121,17 @@ void migrateCloseTimedoutSockets(void) {
dictReleaseIterator(di);
}
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password |
* AUTH2 username password]
*
* On in the multiple keys form:
*
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
* key2 ... keyN */
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password |
* AUTH2 username password] KEYS key1 key2 ... keyN */
void migrateCommand(client *c) {
migrateCachedSocket *cs;
int copy = 0, replace = 0, j;
char *username = NULL;
char *password = NULL;
long timeout;
long dbid;
@@ -5124,7 +5149,7 @@ void migrateCommand(client *c) {
/* Parse additional options */
for (j = 6; j < c->argc; j++) {
int moreargs = j < c->argc-1;
int moreargs = (c->argc-1) - j;
if (!strcasecmp(c->argv[j]->ptr,"copy")) {
copy = 1;
} else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
@@ -5136,6 +5161,13 @@ void migrateCommand(client *c) {
}
j++;
password = c->argv[j]->ptr;
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
if (moreargs < 2) {
addReply(c,shared.syntaxerr);
return;
}
username = c->argv[++j]->ptr;
password = c->argv[++j]->ptr;
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
if (sdslen(c->argv[3]->ptr) != 0) {
addReplyError(c,
@@ -5196,8 +5228,13 @@ try_again:
/* Authentication */
if (password) {
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
int arity = username ? 3 : 2;
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',arity));
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
if (username) {
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,username,
sdslen(username)));
}
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
sdslen(password)));
}
@@ -5329,7 +5366,7 @@ try_again:
if (!copy) {
/* No COPY option: remove the local key, signal the change. */
dbDelete(c->db,kv[j]);
signalModifiedKey(c->db,kv[j]);
signalModifiedKey(c,c->db,kv[j]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
server.dirty++;
+1
View File
@@ -124,6 +124,7 @@ typedef struct clusterNode {
tables. */
mstime_t ping_sent; /* Unix time we sent latest ping */
mstime_t pong_received; /* Unix time we received the pong */
mstime_t data_received; /* Unix time we received any data */
mstime_t fail_time; /* Unix time when FAIL flag was set */
mstime_t voted_time; /* Last time we voted for a slave of this master */
mstime_t repl_offset_time; /* Unix time we received offset for this node */
+5
View File
@@ -2099,6 +2099,7 @@ standardConfig configs[] = {
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL),
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL),
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL),
createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL),
@@ -2132,6 +2133,10 @@ standardConfig configs[] = {
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL),
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
/* Enum Configs */
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
+33
View File
@@ -133,6 +133,12 @@ void setproctitle(const char *fmt, ...);
/* Byte ordering detection */
#include <sys/types.h> /* This will likely define BYTE_ORDER */
/* Define redis_sendfile. */
#if defined(__linux__) || (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_5))
#define HAVE_SENDFILE 1
ssize_t redis_sendfile(int out_fd, int in_fd, off_t offset, size_t count);
#endif
#ifndef BYTE_ORDER
#if (BSD >= 199103)
# include <machine/endian.h>
@@ -226,4 +232,31 @@ void setproctitle(const char *fmt, ...);
#define USE_ALIGNED_ACCESS
#endif
/* Define for redis_set_thread_title */
#ifdef __linux__
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name)
#else
#if (defined __FreeBSD__ || defined __OpenBSD__)
#include <pthread_np.h>
#define redis_set_thread_title(name) pthread_set_name_np(pthread_self(), name)
#elif defined __NetBSD__
#include <pthread.h>
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name, NULL)
#else
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
int pthread_setname_np(const char *name);
#include <pthread.h>
#define redis_set_thread_title(name) pthread_setname_np(name)
#else
#define redis_set_thread_title(name)
#endif
#endif
#endif
/* Check if we can use setcpuaffinity(). */
#if (defined __linux || defined __NetBSD__ || defined __FreeBSD__)
#define USE_SETCPUAFFINITY
void setcpuaffinity(const char *cpulist);
#endif
#endif
+1 -1
View File
@@ -222,6 +222,6 @@ const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
/* Helpers for tls special considerations */
int tlsHasPendingData();
void tlsProcessPendingData();
int tlsProcessPendingData();
#endif /* __REDIS_CONNECTION_H */
+117 -152
View File
@@ -1,16 +1,5 @@
/* Redis uses the CRC64 variant with "Jones" coefficients and init value of 0.
*
* Specification of this CRC64 variant follows:
* Name: crc-64-jones
* Width: 64 bites
* Poly: 0xad93d23594c935a9
* Reflected In: True
* Xor_In: 0xffffffffffffffff
* Reflected_Out: True
* Xor_Out: 0x0
* Check("123456789"): 0xe9c6d914c4b8d9ca
*
* Copyright (c) 2012, Salvatore Sanfilippo <antirez at gmail dot com>
/* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
* Copyright (c) 2020, Amazon Web Services
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -37,147 +26,100 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. */
#include <stdint.h>
#include "crc64.h"
#include "crcspeed.h"
static uint64_t crc64_table[8][256] = {{0}};
static const uint64_t crc64_tab[256] = {
UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979),
UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b),
UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6),
UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04),
UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c),
UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe),
UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183),
UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371),
UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8),
UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a),
UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077),
UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285),
UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d),
UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f),
UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02),
UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0),
UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b),
UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489),
UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4),
UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206),
UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e),
UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc),
UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81),
UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73),
UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa),
UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08),
UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75),
UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87),
UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f),
UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d),
UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100),
UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2),
UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416),
UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4),
UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299),
UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b),
UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63),
UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891),
UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec),
UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e),
UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97),
UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965),
UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18),
UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea),
UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2),
UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710),
UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d),
UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f),
UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14),
UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6),
UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b),
UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69),
UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561),
UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793),
UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee),
UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c),
UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495),
UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667),
UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a),
UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8),
UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0),
UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812),
UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f),
UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d),
UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc),
UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e),
UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643),
UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1),
UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9),
UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b),
UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836),
UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4),
UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d),
UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf),
UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2),
UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30),
UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138),
UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca),
UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7),
UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545),
UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce),
UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c),
UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941),
UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3),
UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb),
UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349),
UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734),
UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6),
UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f),
UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd),
UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0),
UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432),
UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a),
UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8),
UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5),
UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47),
UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3),
UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51),
UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c),
UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de),
UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6),
UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124),
UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559),
UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab),
UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222),
UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0),
UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad),
UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f),
UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57),
UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5),
UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8),
UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a),
UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1),
UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053),
UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e),
UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc),
UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4),
UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26),
UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b),
UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9),
UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20),
UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2),
UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf),
UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d),
UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355),
UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7),
UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da),
UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728),
};
#define POLY UINT64_C(0xad93d23594c935a9)
/******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/
/**
* Generated on Sun Dec 21 14:14:07 2014,
* by pycrc v0.8.2, https://www.tty1.net/pycrc/
*
* LICENSE ON GENERATED CODE:
* ==========================
* As of version 0.6, pycrc is released under the terms of the MIT licence.
* The code generated by pycrc is not considered a substantial portion of the
* software, therefore the author of pycrc will not claim any copyright on
* the generated code.
* ==========================
*
* CRC configuration:
* Width = 64
* Poly = 0xad93d23594c935a9
* XorIn = 0xffffffffffffffff
* ReflectIn = True
* XorOut = 0x0000000000000000
* ReflectOut = True
* Algorithm = bit-by-bit-fast
*
* Modifications after generation (by matt):
* - included finalize step in-line with update for single-call generation
* - re-worked some inner variable architectures
* - adjusted function parameters to match expected prototypes.
*****************************************************************************/
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
uint64_t j;
/**
* Reflect all bits of a \a data word of \a data_len bytes.
*
* \param data The data word to be reflected.
* \param data_len The width of \a data expressed in number of bits.
* \return The reflected data.
*****************************************************************************/
static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) {
uint_fast64_t ret = data & 0x01;
for (j = 0; j < l; j++) {
uint8_t byte = s[j];
crc = crc64_tab[(uint8_t)crc ^ byte] ^ (crc >> 8);
for (size_t i = 1; i < data_len; i++) {
data >>= 1;
ret = (ret << 1) | (data & 0x01);
}
return crc;
return ret;
}
/**
* Update the crc value with new data.
*
* \param crc The current crc value.
* \param data Pointer to a buffer of \a data_len bytes.
* \param data_len Number of bytes in the \a data buffer.
* \return The updated crc value.
******************************************************************************/
uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) {
const uint8_t *data = in_data;
unsigned long long bit;
for (uint64_t offset = 0; offset < len; offset++) {
uint8_t c = data[offset];
for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) {
bit = crc & 0x8000000000000000;
if (c & i) {
bit = !bit;
}
crc <<= 1;
if (bit) {
crc ^= POLY;
}
}
crc &= 0xffffffffffffffff;
}
crc = crc & 0xffffffffffffffff;
return crc_reflect(crc, 64) ^ 0x0000000000000000;
}
/******************** END GENERATED PYCRC FUNCTIONS ********************/
/* Initializes the 16KB lookup tables. */
void crc64_init(void) {
crcspeed64native_init(_crc64, crc64_table);
}
/* Compute crc64 */
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
return crcspeed64native(crc64_table, crc, (void *) s, l);
}
/* Test main */
@@ -188,8 +130,31 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
int crc64Test(int argc, char *argv[]) {
UNUSED(argc);
UNUSED(argv);
printf("e9c6d914c4b8d9ca == %016llx\n",
(unsigned long long) crc64(0,(unsigned char*)"123456789",9));
crc64_init();
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
(uint64_t)_crc64(0, "123456789", 9));
printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
(uint64_t)crc64(0, "123456789", 9));
char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
"do eiusmod tempor incididunt ut labore et dolore magna "
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
"aute irure dolor in reprehenderit in voluptate velit esse "
"cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia "
"deserunt mollit anim id est laborum.";
printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
(uint64_t)_crc64(0, li, sizeof(li)));
printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n",
(uint64_t)crc64(0, li, sizeof(li)));
return 0;
}
#endif
#ifdef REDIS_TEST_MAIN
int main(int argc, char *argv[]) {
return crc64Test(argc, argv);
}
#endif
+1
View File
@@ -3,6 +3,7 @@
#include <stdint.h>
void crc64_init(void);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
#ifdef REDIS_TEST
+281
View File
@@ -0,0 +1,281 @@
/*
* Copyright (C) 2013 Mark Adler
* Originally by: crc64.c Version 1.4 16 Dec 2013 Mark Adler
* Modifications by Matt Stancliff <matt@genges.com>:
* - removed CRC64-specific behavior
* - added generation of lookup tables by parameters
* - removed inversion of CRC input/result
* - removed automatic initialization in favor of explicit initialization
This software is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Mark Adler
madler@alumni.caltech.edu
*/
#include "crcspeed.h"
/* Fill in a CRC constants table. */
void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) {
uint64_t crc;
/* generate CRCs for all single byte sequences */
for (int n = 0; n < 256; n++) {
table[0][n] = crcfn(0, &n, 1);
}
/* generate nested CRC table for future slice-by-8 lookup */
for (int n = 0; n < 256; n++) {
crc = table[0][n];
for (int k = 1; k < 8; k++) {
crc = table[0][crc & 0xff] ^ (crc >> 8);
table[k][n] = crc;
}
}
}
void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) {
uint16_t crc;
/* generate CRCs for all single byte sequences */
for (int n = 0; n < 256; n++) {
table[0][n] = crcfn(0, &n, 1);
}
/* generate nested CRC table for future slice-by-8 lookup */
for (int n = 0; n < 256; n++) {
crc = table[0][n];
for (int k = 1; k < 8; k++) {
crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8);
table[k][n] = crc;
}
}
}
/* Reverse the bytes in a 64-bit word. */
static inline uint64_t rev8(uint64_t a) {
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(a);
#else
uint64_t m;
m = UINT64_C(0xff00ff00ff00ff);
a = ((a >> 8) & m) | (a & m) << 8;
m = UINT64_C(0xffff0000ffff);
a = ((a >> 16) & m) | (a & m) << 16;
return a >> 32 | a << 32;
#endif
}
/* This function is called once to initialize the CRC table for use on a
big-endian architecture. */
void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) {
/* Create the little endian table then reverse all the entires. */
crcspeed64little_init(fn, big_table);
for (int k = 0; k < 8; k++) {
for (int n = 0; n < 256; n++) {
big_table[k][n] = rev8(big_table[k][n]);
}
}
}
void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) {
/* Create the little endian table then reverse all the entires. */
crcspeed16little_init(fn, big_table);
for (int k = 0; k < 8; k++) {
for (int n = 0; n < 256; n++) {
big_table[k][n] = rev8(big_table[k][n]);
}
}
}
/* Calculate a non-inverted CRC multiple bytes at a time on a little-endian
* architecture. If you need inverted CRC, invert *before* calling and invert
* *after* calling.
* 64 bit crc = process 8 bytes at once;
*/
uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc,
void *buf, size_t len) {
unsigned char *next = buf;
/* process individual bytes until we reach an 8-byte aligned pointer */
while (len && ((uintptr_t)next & 7) != 0) {
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
len--;
}
/* fast middle processing, 8 bytes (aligned!) per loop */
while (len >= 8) {
crc ^= *(uint64_t *)next;
crc = little_table[7][crc & 0xff] ^
little_table[6][(crc >> 8) & 0xff] ^
little_table[5][(crc >> 16) & 0xff] ^
little_table[4][(crc >> 24) & 0xff] ^
little_table[3][(crc >> 32) & 0xff] ^
little_table[2][(crc >> 40) & 0xff] ^
little_table[1][(crc >> 48) & 0xff] ^
little_table[0][crc >> 56];
next += 8;
len -= 8;
}
/* process remaining bytes (can't be larger than 8) */
while (len) {
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
len--;
}
return crc;
}
uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc,
void *buf, size_t len) {
unsigned char *next = buf;
/* process individual bytes until we reach an 8-byte aligned pointer */
while (len && ((uintptr_t)next & 7) != 0) {
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
len--;
}
/* fast middle processing, 8 bytes (aligned!) per loop */
while (len >= 8) {
uint64_t n = *(uint64_t *)next;
crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^
little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
little_table[5][(n >> 16) & 0xff] ^
little_table[4][(n >> 24) & 0xff] ^
little_table[3][(n >> 32) & 0xff] ^
little_table[2][(n >> 40) & 0xff] ^
little_table[1][(n >> 48) & 0xff] ^
little_table[0][n >> 56];
next += 8;
len -= 8;
}
/* process remaining bytes (can't be larger than 8) */
while (len) {
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
len--;
}
return crc;
}
/* Calculate a non-inverted CRC eight bytes at a time on a big-endian
* architecture.
*/
uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf,
size_t len) {
unsigned char *next = buf;
crc = rev8(crc);
while (len && ((uintptr_t)next & 7) != 0) {
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
len--;
}
while (len >= 8) {
crc ^= *(uint64_t *)next;
crc = big_table[0][crc & 0xff] ^
big_table[1][(crc >> 8) & 0xff] ^
big_table[2][(crc >> 16) & 0xff] ^
big_table[3][(crc >> 24) & 0xff] ^
big_table[4][(crc >> 32) & 0xff] ^
big_table[5][(crc >> 40) & 0xff] ^
big_table[6][(crc >> 48) & 0xff] ^
big_table[7][crc >> 56];
next += 8;
len -= 8;
}
while (len) {
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
len--;
}
return rev8(crc);
}
/* WARNING: Completely untested on big endian architecture. Possibly broken. */
uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf,
size_t len) {
unsigned char *next = buf;
uint64_t crc = crc_in;
crc = rev8(crc);
while (len && ((uintptr_t)next & 7) != 0) {
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
len--;
}
while (len >= 8) {
uint64_t n = *(uint64_t *)next;
crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^
big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
big_table[2][(n >> 16) & 0xff] ^
big_table[3][(n >> 24) & 0xff] ^
big_table[4][(n >> 32) & 0xff] ^
big_table[5][(n >> 40) & 0xff] ^
big_table[6][(n >> 48) & 0xff] ^
big_table[7][n >> 56];
next += 8;
len -= 8;
}
while (len) {
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
len--;
}
return rev8(crc);
}
/* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes
at a time using passed-in lookup table.
This selects one of two routines depending on the endianess of
the architecture. */
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len) {
uint64_t n = 1;
return *(char *)&n ? crcspeed64little(table, crc, buf, len)
: crcspeed64big(table, crc, buf, len);
}
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len) {
uint64_t n = 1;
return *(char *)&n ? crcspeed16little(table, crc, buf, len)
: crcspeed16big(table, crc, buf, len);
}
/* Initialize CRC lookup table in architecture-dependent manner. */
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) {
uint64_t n = 1;
*(char *)&n ? crcspeed64little_init(fn, table)
: crcspeed64big_init(fn, table);
}
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) {
uint64_t n = 1;
*(char *)&n ? crcspeed16little_init(fn, table)
: crcspeed16big_init(fn, table);
}
+60
View File
@@ -0,0 +1,60 @@
/* Copyright (c) 2014, Matt Stancliff <matt@genges.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. */
#ifndef CRCSPEED_H
#define CRCSPEED_H
#include <inttypes.h>
#include <stdio.h>
typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t);
typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t);
/* CRC-64 */
void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]);
void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]);
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]);
uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len);
uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len);
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len);
/* CRC-16 */
void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]);
void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]);
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]);
uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len);
uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len);
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len);
#endif
+71 -22
View File
@@ -182,9 +182,28 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET)
val->type == OBJ_ZSET ||
val->type == OBJ_STREAM)
signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}
/* This is a special version of dbAdd() that is used only when loading
* keys from the RDB file: the key is passed as an SDS string that is
* retained by the function (and not freed by the caller).
*
* Moreover this function will not abort if the key is already busy, to
* give more control to the caller, nor will signal the key as ready
* since it is not useful in this context.
*
* The function returns 1 if the key was added to the database, taking
* ownership of the SDS string, otherwise 0 is returned, and is up to the
* caller to free the SDS string. */
int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
int retval = dictAdd(db->dict, key, val);
if (retval != DICT_OK) return 0;
if (server.cluster_enabled) slotToKeyAdd(key);
return 1;
}
/* Overwrite an existing key with a new value. Incrementing the reference
@@ -219,8 +238,10 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
* 3) The expire time of the key is reset (the key is made persistent),
* unless 'keepttl' is true.
*
* All the new keys in the database should be created via this interface. */
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
* All the new keys in the database should be created via this interface.
* The client 'c' argument may be set to NULL if the operation is performed
* in a context where there is no clear client performing the operation. */
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
} else {
@@ -228,12 +249,12 @@ void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
}
incrRefCount(val);
if (!keepttl) removeExpire(db,key);
signalModifiedKey(db,key);
if (signal) signalModifiedKey(c,db,key);
}
/* Common case for genericSetKey() where the TTL is not retained. */
void setKey(redisDb *db, robj *key, robj *val) {
genericSetKey(db,key,val,0);
void setKey(client *c, redisDb *db, robj *key, robj *val) {
genericSetKey(c,db,key,val,0,1);
}
/* Return true if the specified key exists in the specified database.
@@ -287,7 +308,7 @@ int dbSyncDelete(redisDb *db, robj *key) {
* the key, because it is shared with the main dictionary. */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
if (server.cluster_enabled) slotToKeyDel(key);
if (server.cluster_enabled) slotToKeyDel(key->ptr);
return 1;
} else {
return 0;
@@ -448,9 +469,11 @@ long long dbTotalServerKeyCount() {
* Every time a DB is flushed the function signalFlushDb() is called.
*----------------------------------------------------------------------------*/
void signalModifiedKey(redisDb *db, robj *key) {
/* Note that the 'c' argument may be NULL if the key was modified out of
* a context of a client. */
void signalModifiedKey(client *c, redisDb *db, robj *key) {
touchWatchedKey(db,key);
trackingInvalidateKey(key);
trackingInvalidateKey(c,key);
}
void signalFlushedDb(int dbid) {
@@ -544,7 +567,7 @@ void delGenericCommand(client *c, int lazy) {
int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
dbSyncDelete(c->db,c->argv[j]);
if (deleted) {
signalModifiedKey(c->db,c->argv[j]);
signalModifiedKey(c,c->db,c->argv[j]);
notifyKeyspaceEvent(NOTIFY_GENERIC,
"del",c->argv[j],c->db->id);
server.dirty++;
@@ -555,7 +578,7 @@ void delGenericCommand(client *c, int lazy) {
}
void delCommand(client *c) {
delGenericCommand(c,0);
delGenericCommand(c,server.lazyfree_lazy_user_del);
}
void unlinkCommand(client *c) {
@@ -984,8 +1007,8 @@ void renameGenericCommand(client *c, int nx) {
dbAdd(c->db,c->argv[2],o);
if (expire != -1) setExpire(c,c->db,c->argv[2],expire);
dbDelete(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]);
signalModifiedKey(c,c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[2]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from",
c->argv[1],c->db->id);
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to",
@@ -1053,8 +1076,8 @@ void moveCommand(client *c) {
/* OK! key moved, free the entry in the source DB */
dbDelete(src,c->argv[1]);
signalModifiedKey(src,c->argv[1]);
signalModifiedKey(dst,c->argv[1]);
signalModifiedKey(c,src,c->argv[1]);
signalModifiedKey(c,dst,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,
"move_from",c->argv[1],src->id);
notifyKeyspaceEvent(NOTIFY_GENERIC,
@@ -1298,7 +1321,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
"expired",key,db->id);
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
if (retval) signalModifiedKey(db,key);
if (retval) signalModifiedKey(NULL,db,key);
return retval;
}
@@ -1553,6 +1576,32 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
return keys;
}
/* LCS ... [KEYS <key1> <key2>] ... */
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
{
int i;
int *keys = getKeysTempBuffer;
UNUSED(cmd);
/* We need to parse the options of the command in order to check for the
* "KEYS" argument before the "STRINGS" argument. */
for (i = 1; i < argc; i++) {
char *arg = argv[i]->ptr;
int moreargs = (argc-1) - i;
if (!strcasecmp(arg, "strings")) {
break;
} else if (!strcasecmp(arg, "keys") && moreargs >= 2) {
keys[0] = i+1;
keys[1] = i+2;
*numkeys = 2;
return keys;
}
}
*numkeys = 0;
return keys;
}
/* Helper function to extract keys from memory command.
* MEMORY USAGE <key> */
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
@@ -1620,17 +1669,17 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
* a fast way a key that belongs to a specified hash slot. This is useful
* while rehashing the cluster and in other conditions when we need to
* understand if we have keys for a given hash slot. */
void slotToKeyUpdateKey(robj *key, int add) {
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
void slotToKeyUpdateKey(sds key, int add) {
size_t keylen = sdslen(key);
unsigned int hashslot = keyHashSlot(key,keylen);
unsigned char buf[64];
unsigned char *indexed = buf;
size_t keylen = sdslen(key->ptr);
server.cluster->slots_keys_count[hashslot] += add ? 1 : -1;
if (keylen+2 > 64) indexed = zmalloc(keylen+2);
indexed[0] = (hashslot >> 8) & 0xff;
indexed[1] = hashslot & 0xff;
memcpy(indexed+2,key->ptr,keylen);
memcpy(indexed+2,key,keylen);
if (add) {
raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
} else {
@@ -1639,11 +1688,11 @@ void slotToKeyUpdateKey(robj *key, int add) {
if (indexed != buf) zfree(indexed);
}
void slotToKeyAdd(robj *key) {
void slotToKeyAdd(sds key) {
slotToKeyUpdateKey(key,1);
}
void slotToKeyDel(robj *key) {
void slotToKeyDel(sds key) {
slotToKeyUpdateKey(key,0);
}
+124 -45
View File
@@ -311,6 +311,13 @@ void mallctl_int(client *c, robj **argv, int argc) {
size_t sz = sizeof(old);
while (sz > 0) {
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
if (ret == EPERM && argc > 1) {
/* if this option is write only, try just writing to it. */
if (!(ret=je_mallctl(argv[0]->ptr, NULL, 0, &val, sz))) {
addReply(c, shared.ok);
return;
}
}
if (ret==EINVAL) {
/* size might be wrong, try a smaller one */
sz /= 2;
@@ -333,17 +340,30 @@ void mallctl_int(client *c, robj **argv, int argc) {
}
void mallctl_string(client *c, robj **argv, int argc) {
int ret;
int rret, wret;
char *old;
size_t sz = sizeof(old);
/* for strings, it seems we need to first get the old value, before overriding it. */
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
addReplyErrorFormat(c,"%s", strerror(ret));
return;
if ((rret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
/* return error unless this option is write only. */
if (!(rret == EPERM && argc > 1)) {
addReplyErrorFormat(c,"%s", strerror(rret));
return;
}
}
addReplyBulkCString(c, old);
if(argc > 1)
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
if(argc > 1) {
char *val = argv[1]->ptr;
char **valref = &val;
if ((!strcmp(val,"VOID")))
valref = NULL, sz = 0;
wret = je_mallctl(argv[0]->ptr, NULL, 0, valref, sz);
}
if (!rret)
addReplyBulkCString(c, old);
else if (wret)
addReplyErrorFormat(c,"%s", strerror(wret));
else
addReply(c, shared.ok);
}
#endif
@@ -366,7 +386,7 @@ void debugCommand(client *c) {
"OOM -- Crash the server simulating an out-of-memory error.",
"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.",
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conficts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.",
"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.",
@@ -411,15 +431,44 @@ NULL
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);
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
addReply(c,shared.err);
return;
int flush = 1, save = 1;
int flags = RDBFLAGS_NONE;
/* Parse the additional options that modify the RELOAD
* behavior. */
for (int j = 2; j < c->argc; j++) {
char *opt = c->argv[j]->ptr;
if (!strcasecmp(opt,"MERGE")) {
flags |= RDBFLAGS_ALLOW_DUP;
} else if (!strcasecmp(opt,"NOFLUSH")) {
flush = 0;
} else if (!strcasecmp(opt,"NOSAVE")) {
save = 0;
} else {
addReplyError(c,"DEBUG RELOAD only supports the "
"MERGE, NOFLUSH and NOSAVE options.");
return;
}
}
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
/* The default beahvior is to save the RDB file before loading
* it back. */
if (save) {
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
addReply(c,shared.err);
return;
}
}
/* The default behavior is to remove the current dataset from
* memory before loading the RDB file, however when MERGE is
* used together with NOFLUSH, we are able to merge two datasets. */
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c);
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
int ret = rdbLoad(server.rdb_filename,NULL,flags);
unprotectClient(c);
if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump");
@@ -490,7 +539,7 @@ NULL
"encoding:%s serializedlength:%zu "
"lru:%d lru_seconds_idle:%llu%s",
(void*)val, val->refcount,
strenc, rdbSavedObjectLen(val),
strenc, rdbSavedObjectLen(val, c->argv[2]),
val->lru, estimateObjectIdleTime(val)/1000, extra);
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
dictEntry *de;
@@ -559,7 +608,7 @@ NULL
memcpy(val->ptr, buf, valsize<=buflen? valsize: buflen);
}
dbAdd(c->db,key,val);
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
decrRefCount(key);
}
addReply(c,shared.ok);
@@ -817,6 +866,8 @@ void serverLogObjectDebugInfo(const robj *o) {
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
if (o->encoding == OBJ_ENCODING_SKIPLIST)
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
} else if (o->type == OBJ_STREAM) {
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
}
}
@@ -1045,6 +1096,61 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext.gregs[18]
);
logStackContent((void**)uc->uc_mcontext.gregs[15]);
#elif defined(__aarch64__) /* Linux AArch64 */
serverLog(LL_WARNING,
"\n"
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
"X30:%016lx\n"
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
(unsigned long) uc->uc_mcontext.regs[18],
(unsigned long) uc->uc_mcontext.regs[19],
(unsigned long) uc->uc_mcontext.regs[20],
(unsigned long) uc->uc_mcontext.regs[21],
(unsigned long) uc->uc_mcontext.regs[22],
(unsigned long) uc->uc_mcontext.regs[23],
(unsigned long) uc->uc_mcontext.regs[24],
(unsigned long) uc->uc_mcontext.regs[25],
(unsigned long) uc->uc_mcontext.regs[26],
(unsigned long) uc->uc_mcontext.regs[27],
(unsigned long) uc->uc_mcontext.regs[28],
(unsigned long) uc->uc_mcontext.regs[29],
(unsigned long) uc->uc_mcontext.regs[30],
(unsigned long) uc->uc_mcontext.pc,
(unsigned long) uc->uc_mcontext.sp,
(unsigned long) uc->uc_mcontext.pstate,
(unsigned long) uc->uc_mcontext.fault_address
);
logStackContent((void**)uc->uc_mcontext.sp);
#elif defined(__arm__) /* Linux ARM */
serverLog(LL_WARNING,
"\n"
"R10:%016lx R9 :%016lx\nR8 :%016lx R7 :%016lx\n"
"R6 :%016lx R5 :%016lx\nR4 :%016lx R3 :%016lx\n"
"R2 :%016lx R1 :%016lx\nR0 :%016lx EC :%016lx\n"
"fp: %016lx ip:%016lx\n",
"pc:%016lx sp:%016lx\ncpsr:%016lx fault_address:%016lx\n",
(unsigned long) uc->uc_mcontext.arm_r10,
(unsigned long) uc->uc_mcontext.arm_r9,
(unsigned long) uc->uc_mcontext.arm_r8,
(unsigned long) uc->uc_mcontext.arm_r7,
(unsigned long) uc->uc_mcontext.arm_r6,
(unsigned long) uc->uc_mcontext.arm_r5,
(unsigned long) uc->uc_mcontext.arm_r4,
(unsigned long) uc->uc_mcontext.arm_r3,
(unsigned long) uc->uc_mcontext.arm_r2,
(unsigned long) uc->uc_mcontext.arm_r1,
(unsigned long) uc->uc_mcontext.arm_r0,
(unsigned long) uc->uc_mcontext.error_code,
(unsigned long) uc->uc_mcontext.arm_fp,
(unsigned long) uc->uc_mcontext.arm_ip,
(unsigned long) uc->uc_mcontext.arm_pc,
(unsigned long) uc->uc_mcontext.arm_sp,
(unsigned long) uc->uc_mcontext.arm_cpsr,
(unsigned long) uc->uc_mcontext.fault_address
);
logStackContent((void**)uc->uc_mcontext.arm_sp);
#endif
#elif defined(__FreeBSD__)
#if defined(__x86_64__)
@@ -1185,33 +1291,6 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext.mc_cs
);
logStackContent((void**)uc->uc_mcontext.mc_rsp);
#elif defined(__aarch64__) /* Linux AArch64 */
serverLog(LL_WARNING,
"\n"
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
"X30:%016lx\n"
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
(unsigned long) uc->uc_mcontext.regs[18],
(unsigned long) uc->uc_mcontext.regs[19],
(unsigned long) uc->uc_mcontext.regs[20],
(unsigned long) uc->uc_mcontext.regs[21],
(unsigned long) uc->uc_mcontext.regs[22],
(unsigned long) uc->uc_mcontext.regs[23],
(unsigned long) uc->uc_mcontext.regs[24],
(unsigned long) uc->uc_mcontext.regs[25],
(unsigned long) uc->uc_mcontext.regs[26],
(unsigned long) uc->uc_mcontext.regs[27],
(unsigned long) uc->uc_mcontext.regs[28],
(unsigned long) uc->uc_mcontext.regs[29],
(unsigned long) uc->uc_mcontext.regs[30],
(unsigned long) uc->uc_mcontext.pc,
(unsigned long) uc->uc_mcontext.sp,
(unsigned long) uc->uc_mcontext.pstate,
(unsigned long) uc->uc_mcontext.fault_address
);
logStackContent((void**)uc->uc_mcontext.sp);
#else
serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch");
@@ -1577,7 +1656,7 @@ void enableWatchdog(int period) {
/* Watchdog was actually disabled, so we have to setup the signal
* handler. */
sigemptyset(&act.sa_mask);
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = watchdogSignalHandler;
sigaction(SIGALRM, &act, NULL);
}
+3 -10
View File
@@ -43,7 +43,7 @@
/* this method was added to jemalloc in order to help us understand which
* pointers are worthwhile moving and which aren't */
int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
int je_get_defrag_hint(void* ptr);
/* forward declarations*/
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
@@ -55,18 +55,11 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
* when it returns a non-null value, the old pointer was already released
* and should NOT be accessed. */
void* activeDefragAlloc(void *ptr) {
int bin_util, run_util;
size_t size;
void *newptr;
if(!je_get_defrag_hint(ptr, &bin_util, &run_util)) {
server.stat_active_defrag_misses++;
return NULL;
}
/* if this run is more utilized than the average utilization in this bin
* (or it is full), skip it. This will eventually move all the allocations
* from relatively empty runs into relatively full runs. */
if (run_util > bin_util || run_util == 1<<16) {
if(!je_get_defrag_hint(ptr)) {
server.stat_active_defrag_misses++;
size = zmalloc_size(ptr);
return NULL;
}
/* move this allocation to a new allocation.
+5 -5
View File
@@ -134,7 +134,7 @@ int _dictInit(dict *d, dictType *type,
* but with the invariant of a USED/BUCKETS ratio near to <= 1 */
int dictResize(dict *d)
{
int minimal;
unsigned long minimal;
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
minimal = d->ht[0].used;
@@ -478,7 +478,7 @@ dictEntry *dictFind(dict *d, const void *key)
dictEntry *he;
uint64_t h, idx, table;
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
if (dictSize(d) == 0) return NULL; /* dict is empty */
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
for (table = 0; table <= 1; table++) {
@@ -766,8 +766,8 @@ dictEntry *dictGetFairRandomKey(dict *d) {
/* Function to reverse bits. Algorithm from:
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) {
unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
unsigned long mask = ~0;
unsigned long s = CHAR_BIT * sizeof(v); // bit size; must be power of 2
unsigned long mask = ~0UL;
while ((s >>= 1) > 0) {
mask ^= (mask << s);
v = ((v >> s) & mask) | ((v << s) & ~mask);
@@ -1044,7 +1044,7 @@ dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t h
dictEntry *he, **heref;
unsigned long idx, table;
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
if (dictSize(d) == 0) return NULL; /* dict is empty */
for (table = 0; table <= 1; table++) {
idx = hash & d->ht[table].sizemask;
heref = &d->ht[table].table[idx];
+19 -13
View File
@@ -450,9 +450,10 @@ int freeMemoryIfNeeded(void) {
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
size_t mem_reported, mem_tofree, mem_freed;
mstime_t latency, eviction_latency;
mstime_t latency, eviction_latency, lazyfree_latency;
long long delta;
int slaves = listLength(server.slaves);
int result = C_ERR;
/* When clients are paused the dataset should be static not just from the
* POV of clients not being able to write, but also from the POV of
@@ -463,10 +464,10 @@ int freeMemoryIfNeeded(void) {
mem_freed = 0;
latencyStartMonitor(latency);
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
goto cant_free; /* We need to free memory, but policy forbids. */
latencyStartMonitor(latency);
while (mem_freed < mem_tofree) {
int j, k, i;
static unsigned int next_db = 0;
@@ -569,9 +570,9 @@ int freeMemoryIfNeeded(void) {
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
signalModifiedKey(NULL,db,keyobj);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
latencyRemoveNestedEvent(latency,eviction_latency);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
server.stat_evictedkeys++;
@@ -600,25 +601,30 @@ int freeMemoryIfNeeded(void) {
}
}
} else {
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
goto cant_free; /* nothing to free... */
}
}
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
return C_OK;
result = C_OK;
cant_free:
/* We are here if we are not able to reclaim memory. There is only one
* last thing we can try: check if the lazyfree thread has jobs in queue
* and wait... */
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
break;
usleep(1000);
if (result != C_OK) {
latencyStartMonitor(lazyfree_latency);
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
result = C_OK;
break;
}
usleep(1000);
}
latencyEndMonitor(lazyfree_latency);
latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
}
return C_ERR;
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
return result;
}
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
+4 -3
View File
@@ -64,7 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
dbSyncDelete(db,keyobj);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyobj,db->id);
trackingInvalidateKey(keyobj);
trackingInvalidateKey(NULL,keyobj);
decrRefCount(keyobj);
server.stat_expiredkeys++;
return 1;
@@ -519,14 +519,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
/* Replicate/AOF this as an explicit DEL or UNLINK. */
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
rewriteClientCommandVector(c,2,aux,key);
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
setExpire(c,c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
@@ -590,6 +590,7 @@ void pttlCommand(client *c) {
void persistCommand(client *c) {
if (lookupKeyWrite(c->db,c->argv[1])) {
if (removeExpire(c->db,c->argv[1])) {
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
addReply(c,shared.cone);
server.dirty++;
} else {
+2 -2
View File
@@ -657,13 +657,13 @@ void georadiusGeneric(client *c, int flags) {
if (returned_items) {
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
setKey(c->db,storekey,zobj);
setKey(c,c->db,storekey,zobj);
decrRefCount(zobj);
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
c->db->id);
server.dirty += returned_items;
} else if (dbDelete(c->db,storekey)) {
signalModifiedKey(c->db,storekey);
signalModifiedKey(c,c->db,storekey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
server.dirty++;
}
+4
View File
@@ -206,7 +206,11 @@ int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {
if (!xy) return 0;
xy[0] = (area->longitude.min + area->longitude.max) / 2;
if (xy[0] > GEO_LONG_MAX) xy[0] = GEO_LONG_MAX;
if (xy[0] < GEO_LONG_MIN) xy[0] = GEO_LONG_MIN;
xy[1] = (area->latitude.min + area->latitude.max) / 2;
if (xy[1] > GEO_LAT_MAX) xy[1] = GEO_LAT_MAX;
if (xy[1] < GEO_LAT_MIN) xy[1] = GEO_LAT_MIN;
return 1;
}
+179 -34
View File
@@ -28,6 +28,56 @@ struct commandHelp {
int group;
char *since;
} commandHelp[] = {
{ "ACL CAT",
"[categoryname]",
"List the ACL categories or the commands inside a category",
9,
"6.0.0" },
{ "ACL DELUSER",
"username [username ...]",
"Remove the specified ACL users and the associated rules",
9,
"6.0.0" },
{ "ACL GENPASS",
"[bits]",
"Generate a pseudorandom secure password to use for ACL users",
9,
"6.0.0" },
{ "ACL LIST",
"-",
"List the current ACL rules in ACL config file format",
9,
"6.0.0" },
{ "ACL LOAD",
"-",
"Reload the ACLs from the configured ACL file",
9,
"6.0.0" },
{ "ACL LOG",
"[count or RESET]",
"List latest events denied because of ACLs in place",
9,
"6.0.0" },
{ "ACL SAVE",
"-",
"Save the current ACL rules in the configured ACL file",
9,
"6.0.0" },
{ "ACL SETUSER",
"rule [rule ...]",
"Modify or create the rules for a specific ACL user",
9,
"6.0.0" },
{ "ACL USERS",
"-",
"List the username of all the configured ACL rules",
9,
"6.0.0" },
{ "ACL WHOAMI",
"-",
"Return the name of the user associated to the current connection",
9,
"6.0.0" },
{ "APPEND",
"key value",
"Append a value to a key",
@@ -44,7 +94,7 @@ struct commandHelp {
9,
"1.0.0" },
{ "BGSAVE",
"-",
"[SCHEDULE]",
"Asynchronously save the dataset to disk",
9,
"1.0.0" },
@@ -80,7 +130,7 @@ struct commandHelp {
"2.0.0" },
{ "BRPOPLPUSH",
"source destination timeout",
"Pop a value from a list, push it to another list and return it; or block until one is available",
"Pop an element from a list, push it to another list and return it; or block until one is available",
2,
"2.2.0" },
{ "BZPOPMAX",
@@ -93,51 +143,71 @@ struct commandHelp {
"Remove and return the member with the lowest score from one or more sorted sets, or block until one is available",
4,
"5.0.0" },
{ "CLIENT CACHING",
"YES|NO",
"Instruct the server about tracking or not keys in the next request",
8,
"6.0.0" },
{ "CLIENT GETNAME",
"-",
"Get the current connection name",
9,
8,
"2.6.9" },
{ "CLIENT GETREDIR",
"-",
"Get tracking notifications redirection client ID if any",
8,
"6.0.0" },
{ "CLIENT ID",
"-",
"Returns the client ID for the current connection",
9,
8,
"5.0.0" },
{ "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
"Kill the connection of a client",
9,
8,
"2.4.0" },
{ "CLIENT LIST",
"-",
"[TYPE normal|master|replica|pubsub]",
"Get the list of client connections",
9,
8,
"2.4.0" },
{ "CLIENT PAUSE",
"timeout",
"Stop processing commands from clients for some time",
9,
8,
"2.9.50" },
{ "CLIENT REPLY",
"ON|OFF|SKIP",
"Instruct the server whether to reply to commands",
9,
8,
"3.2" },
{ "CLIENT SETNAME",
"connection-name",
"Set the current connection name",
9,
8,
"2.6.9" },
{ "CLIENT TRACKING",
"ON|OFF [REDIRECT client-id] [PREFIX prefix] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]",
"Enable or disable server assisted client side caching support",
8,
"6.0.0" },
{ "CLIENT UNBLOCK",
"client-id [TIMEOUT|ERROR]",
"Unblock a client blocked in a blocking command from a different connection",
9,
8,
"5.0.0" },
{ "CLUSTER ADDSLOTS",
"slot [slot ...]",
"Assign new hash slots to receiving node",
12,
"3.0.0" },
{ "CLUSTER BUMPEPOCH",
"-",
"Advance the cluster config epoch",
12,
"3.0.0" },
{ "CLUSTER COUNT-FAILURE-REPORTS",
"node-id",
"Return the number of failure reports active for a given node",
@@ -158,6 +228,11 @@ struct commandHelp {
"Forces a replica to perform a manual failover of its master.",
12,
"3.0.0" },
{ "CLUSTER FLUSHSLOTS",
"-",
"Delete a node's own slots information",
12,
"3.0.0" },
{ "CLUSTER FORGET",
"node-id",
"Remove a node from the nodes table",
@@ -183,6 +258,11 @@ struct commandHelp {
"Force a node cluster to handshake with another node",
12,
"3.0.0" },
{ "CLUSTER MYID",
"-",
"Return the node id",
12,
"3.0.0" },
{ "CLUSTER NODES",
"-",
"Get Cluster config for the node",
@@ -359,7 +439,7 @@ struct commandHelp {
13,
"3.2.0" },
{ "GEODIST",
"key member1 member2 [unit]",
"key member1 member2 [m|km|ft|mi]",
"Returns the distance between two members of a geospatial index",
13,
"3.2.0" },
@@ -408,6 +488,11 @@ struct commandHelp {
"Delete one or more hash fields",
5,
"2.0.0" },
{ "HELLO",
"protover [AUTH username password] [SETNAME clientname]",
"switch Redis protocol",
8,
"6.0.0" },
{ "HEXISTS",
"key field",
"Determine if a hash field exists",
@@ -459,7 +544,7 @@ struct commandHelp {
5,
"2.8.0" },
{ "HSET",
"key field value",
"key field value [field value ...]",
"Set the string value of a hash field",
5,
"2.0.0" },
@@ -508,13 +593,43 @@ struct commandHelp {
"Get the UNIX time stamp of the last successful save to disk",
9,
"1.0.0" },
{ "LATENCY DOCTOR",
"-",
"Return a human readable latency analysis report.",
9,
"2.8.13" },
{ "LATENCY GRAPH",
"event",
"Return a latency graph for the event.",
9,
"2.8.13" },
{ "LATENCY HELP",
"-",
"Show helpful text about the different subcommands.",
9,
"2.8.13" },
{ "LATENCY HISTORY",
"event",
"Return timestamp-latency samples for the event.",
9,
"2.8.13" },
{ "LATENCY LATEST",
"-",
"Return the latest latency samples for all events.",
9,
"2.8.13" },
{ "LATENCY RESET",
"[event]",
"Reset latency data for one or more events.",
9,
"2.8.13" },
{ "LINDEX",
"key index",
"Get an element from a list by its index",
2,
"1.0.0" },
{ "LINSERT",
"key BEFORE|AFTER pivot value",
"key BEFORE|AFTER pivot element",
"Insert an element before or after another element in a list",
2,
"2.2.0" },
@@ -523,19 +638,24 @@ struct commandHelp {
"Get the length of a list",
2,
"1.0.0" },
{ "LOLWUT",
"[VERSION version]",
"Display some computer art and the Redis version",
9,
"5.0.0" },
{ "LPOP",
"key",
"Remove and get the first element in a list",
2,
"1.0.0" },
{ "LPUSH",
"key value [value ...]",
"Prepend one or multiple values to a list",
"key element [element ...]",
"Prepend one or multiple elements to a list",
2,
"1.0.0" },
{ "LPUSHX",
"key value",
"Prepend a value to a list, only if the list exists",
"key element [element ...]",
"Prepend an element to a list, only if the list exists",
2,
"2.2.0" },
{ "LRANGE",
@@ -544,12 +664,12 @@ struct commandHelp {
2,
"1.0.0" },
{ "LREM",
"key count value",
"key count element",
"Remove elements from a list",
2,
"1.0.0" },
{ "LSET",
"key index value",
"key index element",
"Set the value of an element in a list by its index",
2,
"1.0.0" },
@@ -594,10 +714,25 @@ struct commandHelp {
1,
"1.0.0" },
{ "MIGRATE",
"host port key|"" destination-db timeout [COPY] [REPLACE] [KEYS key]",
"host port key|"" destination-db timeout [COPY] [REPLACE] [AUTH password] [KEYS key]",
"Atomically transfer a key from a Redis instance to another one.",
0,
"2.6.0" },
{ "MODULE LIST",
"-",
"List all modules loaded by the server",
9,
"4.0.0" },
{ "MODULE LOAD",
"path [arg]",
"Load a module",
9,
"4.0.0" },
{ "MODULE UNLOAD",
"name",
"Unload a module",
9,
"4.0.0" },
{ "MONITOR",
"-",
"Listen for all requests received by the server in real time",
@@ -673,6 +808,11 @@ struct commandHelp {
"Listen for messages published to channels matching the given patterns",
6,
"2.0.0" },
{ "PSYNC",
"replicationid offset",
"Internal command used for replication",
9,
"2.8.0" },
{ "PTTL",
"key",
"Get the time to live for a key in milliseconds",
@@ -729,7 +869,7 @@ struct commandHelp {
9,
"5.0.0" },
{ "RESTORE",
"key ttl serialized-value [REPLACE]",
"key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]",
"Create a key using the provided serialized value, previously obtained using DUMP.",
0,
"2.6.0" },
@@ -749,13 +889,13 @@ struct commandHelp {
2,
"1.2.0" },
{ "RPUSH",
"key value [value ...]",
"Append one or multiple values to a list",
"key element [element ...]",
"Append one or multiple elements to a list",
2,
"1.0.0" },
{ "RPUSHX",
"key value",
"Append a value to a list, only if the list exists",
"key element [element ...]",
"Append an element to a list, only if the list exists",
2,
"2.2.0" },
{ "SADD",
@@ -769,7 +909,7 @@ struct commandHelp {
9,
"1.0.0" },
{ "SCAN",
"cursor [MATCH pattern] [COUNT count]",
"cursor [MATCH pattern] [COUNT count] [TYPE type]",
"Incrementally iterate the keys space",
0,
"2.8.0" },
@@ -819,7 +959,7 @@ struct commandHelp {
8,
"1.0.0" },
{ "SET",
"key value [expiration EX seconds|PX milliseconds] [NX|XX]",
"key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]",
"Set the string value of a key",
1,
"1.0.0" },
@@ -908,6 +1048,11 @@ struct commandHelp {
"Incrementally iterate Set elements",
3,
"2.8.0" },
{ "STRALGO",
"LCS algo-specific-argument [algo-specific-argument ...]",
"Run algorithms (currently LCS) against strings",
1,
"6.0.0" },
{ "STRLEN",
"key",
"Get the length of the value stored in a key",
@@ -929,9 +1074,9 @@ struct commandHelp {
3,
"1.0.0" },
{ "SWAPDB",
"index index",
"index1 index2",
"Swaps two Redis databases",
8,
9,
"4.0.0" },
{ "SYNC",
"-",
@@ -989,7 +1134,7 @@ struct commandHelp {
14,
"5.0.0" },
{ "XADD",
"key ID field string [field string ...]",
"key ID field value [field value ...]",
"Appends a new entry to a stream",
14,
"5.0.0" },
@@ -1004,7 +1149,7 @@ struct commandHelp {
14,
"5.0.0" },
{ "XGROUP",
"[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
"[CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
"Create, destroy, and manage consumer groups.",
14,
"5.0.0" },
@@ -1029,12 +1174,12 @@ struct commandHelp {
14,
"5.0.0" },
{ "XREAD",
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]",
"Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.",
14,
"5.0.0" },
{ "XREADGROUP",
"GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
"GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]",
"Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.",
14,
"5.0.0" },
+3 -3
View File
@@ -1209,7 +1209,7 @@ void pfaddCommand(client *c) {
}
hdr = o->ptr;
if (updated) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
server.dirty++;
HLL_INVALIDATE_CACHE(hdr);
@@ -1300,7 +1300,7 @@ void pfcountCommand(client *c) {
* data structure is not modified, since the cached value
* may be modified and given that the HLL is a Redis string
* we need to propagate the change. */
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
}
addReplyLongLong(c,card);
@@ -1373,7 +1373,7 @@ void pfmergeCommand(client *c) {
last hllSparseSet() call. */
HLL_INVALIDATE_CACHE(hdr);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
/* We generate a PFADD event for PFMERGE for semantical simplicity
* since in theory this is a mass-add of elements. */
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
+1 -1
View File
@@ -83,7 +83,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
* field to NULL in order to lazy free it later. */
if (de) {
dictFreeUnlinkedEntry(db->dict,de);
if (server.cluster_enabled) slotToKeyDel(key);
if (server.cluster_enabled) slotToKeyDel(key->ptr);
return 1;
} else {
return 0;
+6
View File
@@ -34,6 +34,10 @@
/* This represents a very simple generic canvas in order to draw stuff.
* It's up to each LOLWUT versions to translate what they draw to the
* screen, depending on the result to accomplish. */
#ifndef __LOLWUT_H
#define __LOLWUT_H
typedef struct lwCanvas {
int width;
int height;
@@ -47,3 +51,5 @@ void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
int lwGetPixel(lwCanvas *canvas, int x, int y);
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
#endif
+14 -18
View File
@@ -896,7 +896,7 @@ void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
/* Signals that the key is modified from user's perspective (i.e. invalidate WATCH
* and client side caching). */
int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) {
signalModifiedKey(ctx->client->db,keyname);
signalModifiedKey(ctx->client,ctx->client->db,keyname);
return REDISMODULE_OK;
}
@@ -2016,7 +2016,7 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
static void moduleCloseKey(RedisModuleKey *key) {
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
if ((key->mode & REDISMODULE_WRITE) && signal)
signalModifiedKey(key->db,key->key);
signalModifiedKey(key->ctx->client,key->db,key->key);
/* TODO: if (key->iter) RM_KeyIteratorStop(kp); */
RM_ZsetRangeStop(key);
decrRefCount(key->key);
@@ -2157,7 +2157,7 @@ RedisModuleString *RM_RandomKey(RedisModuleCtx *ctx) {
int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) {
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
RM_DeleteKey(key);
setKey(key->db,key->key,str);
genericSetKey(key->ctx->client,key->db,key->key,str,0,0);
key->value = str;
return REDISMODULE_OK;
}
@@ -2237,7 +2237,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
if (key->value == NULL) {
/* Empty key: create it with the new size. */
robj *o = createObject(OBJ_STRING,sdsnewlen(NULL, newlen));
setKey(key->db,key->key,o);
genericSetKey(key->ctx->client,key->db,key->key,o,0,0);
key->value = o;
decrRefCount(o);
} else {
@@ -3326,13 +3326,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
* a Lua script in the context of AOF and slaves. */
if (replicate) moduleReplicateMultiIfNeeded(ctx);
if (ctx->client->flags & CLIENT_MULTI ||
ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
c->flags |= CLIENT_MULTI;
}
/* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOWRAP;
if (replicate) {
if (!(flags & REDISMODULE_ARGV_NO_AOF))
call_flags |= CMD_CALL_PROPAGATE_AOF;
@@ -3341,9 +3336,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
}
call(c,call_flags);
/* Convert the result of the Redis command into a suitable Lua type.
* The first thing we need is to create a single string from the client
* output buffers. */
/* Convert the result of the Redis command into a module reply. */
sds proto = sdsnewlen(c->buf,c->bufpos);
c->bufpos = 0;
while(listLength(c->reply)) {
@@ -3632,7 +3625,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
RM_DeleteKey(key);
robj *o = createModuleObject(mt,value);
setKey(key->db,key->key,o);
genericSetKey(key->ctx->client,key->db,key->key,o,0,0);
decrRefCount(o);
key->value = o;
return REDISMODULE_OK;
@@ -4400,14 +4393,17 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
* can really be unblocked, since the module was able to serve the client.
* If the callback returns REDISMODULE_OK, then the client can be unblocked,
* otherwise the client remains blocked and we'll retry again when one of
* the keys it blocked for becomes "ready" again. */
* the keys it blocked for becomes "ready" again.
* This function returns 1 if client was served (and should be unblocked) */
int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
int served = 0;
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
/* Protect against re-processing: don't serve clients that are already
* in the unblocking list for any reason (including RM_UnblockClient()
* explicit call). */
if (bc->unblocked) return REDISMODULE_ERR;
* explicit call). See #6798. */
if (bc->unblocked) return 0;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
ctx.blocked_ready_key = key;
@@ -5982,7 +5978,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int
struct RedisModule *module = dictGetVal(de);
if (!module->info_cb)
continue;
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0, 0};
module->info_cb(&info_ctx, for_crash_report);
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
if (info_ctx.in_dict_field)
+4 -1
View File
@@ -172,7 +172,10 @@ void execCommand(client *c) {
* This way we'll deliver the MULTI/..../EXEC block as a whole and
* both the AOF and the replication link will have the same consistency
* and atomicity guarantees. */
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
if (!must_propagate &&
!server.loading &&
!(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)))
{
execCommandPropagateMulti(c);
must_propagate = 1;
}
+215 -77
View File
@@ -157,6 +157,8 @@ client *createClient(connection *conn) {
c->client_list_node = NULL;
c->client_tracking_redirection = 0;
c->client_tracking_prefixes = NULL;
c->client_cron_last_memory_usage = 0;
c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL;
c->auth_callback = NULL;
c->auth_callback_privdata = NULL;
c->auth_module = NULL;
@@ -266,7 +268,7 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
/* 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
* addReplyDeferredLen() 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. */
@@ -373,13 +375,54 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
* 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. */
int ctype = getClientType(c);
if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE) {
char* to = ctype == CLIENT_TYPE_MASTER? "master": "replica";
char* from = ctype == CLIENT_TYPE_MASTER? "replica": "master";
if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE || c->id == CLIENT_ID_AOF) {
char *to, *from;
if (c->id == CLIENT_ID_AOF) {
to = "AOF-loading-client";
from = "server";
} else if (ctype == CLIENT_TYPE_MASTER) {
to = "master";
from = "replica";
} else {
to = "replica";
from = "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);
if (ctype == CLIENT_TYPE_MASTER && server.repl_backlog &&
server.repl_backlog_histlen > 0)
{
long long dumplen = 256;
if (server.repl_backlog_histlen < dumplen)
dumplen = server.repl_backlog_histlen;
/* Identify the first byte to dump. */
long long idx =
(server.repl_backlog_idx + (server.repl_backlog_size - dumplen)) %
server.repl_backlog_size;
/* Scan the circular buffer to collect 'dumplen' bytes. */
sds dump = sdsempty();
while(dumplen) {
long long thislen =
((server.repl_backlog_size - idx) < dumplen) ?
(server.repl_backlog_size - idx) : dumplen;
dump = sdscatrepr(dump,server.repl_backlog+idx,thislen);
dumplen -= thislen;
idx = 0;
}
/* Finally log such bytes: this is vital debugging info to
* understand what happened. */
serverLog(LL_WARNING,"Latest backlog is: '%s'", dump);
sdsfree(dump);
}
server.stat_unexpected_error_replies++;
}
}
@@ -422,6 +465,34 @@ void addReplyStatusFormat(client *c, const char *fmt, ...) {
sdsfree(s);
}
/* Sometimes we are forced to create a new reply node, and we can't append to
* the previous one, when that happens, we wanna try to trim the unused space
* at the end of the last reply node which we won't use anymore. */
void trimReplyUnusedTailSpace(client *c) {
listNode *ln = listLast(c->reply);
clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
/* Note that 'tail' may be NULL even if we have a tail node, becuase when
* addReplyDeferredLen() is used */
if (!tail) return;
/* We only try to trim the space is relatively high (more than a 1/4 of the
* allocation), otherwise there's a high chance realloc will NOP.
* Also, to avoid large memmove which happens as part of realloc, we only do
* that if the used part is small. */
if (tail->size - tail->used > tail->size / 4 &&
tail->used < PROTO_REPLY_CHUNK_BYTES)
{
size_t old_size = tail->size;
tail = zrealloc(tail, tail->used + sizeof(clientReplyBlock));
/* take over the allocation's internal fragmentation (at least for
* memory usage tracking) */
tail->size = zmalloc_usable(tail) - sizeof(clientReplyBlock);
c->reply_bytes = c->reply_bytes + tail->size - old_size;
listNodeValue(ln) = tail;
}
}
/* Adds an empty object to the reply list that will contain the multi bulk
* length, which is not known when this function is called. */
void *addReplyDeferredLen(client *c) {
@@ -429,6 +500,7 @@ void *addReplyDeferredLen(client *c) {
* ready to be sent, since we are sure that before returning to the
* event loop setDeferredAggregateLen() will be called. */
if (prepareClientToWrite(c) != C_OK) return NULL;
trimReplyUnusedTailSpace(c);
listAddNodeTail(c->reply,NULL); /* NULL is our placeholder. */
return listLast(c->reply);
}
@@ -967,7 +1039,10 @@ static void freeClientArgv(client *c) {
* when we resync with our own master and want to force all our slaves to
* resync with us as well. */
void disconnectSlaves(void) {
while (listLength(server.slaves)) {
listIter li;
listNode *ln;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
listNode *ln = listFirst(server.slaves);
freeClient((client*)ln->value);
}
@@ -1062,6 +1137,16 @@ void freeClient(client *c) {
/* Notify module system that this client auth status changed. */
moduleNotifyUserChanged(c);
/* If this client was scheduled for async freeing we need to remove it
* from the queue. Note that we need to do this here, because later
* we may call replicationCacheMaster() and the client should already
* be removed from the list of clients to free. */
if (c->flags & CLIENT_CLOSE_ASAP) {
ln = listSearchKey(server.clients_to_close,c);
serverAssert(ln != NULL);
listDelNode(server.clients_to_close,ln);
}
/* If it is our master that's beging disconnected we should make sure
* to cache the state to try a partial resynchronization later.
*
@@ -1069,10 +1154,8 @@ void freeClient(client *c) {
* some unexpected state, by checking its flags. */
if (server.master && c->flags & CLIENT_MASTER) {
serverLog(LL_WARNING,"Connection with master lost.");
if (!(c->flags & (CLIENT_CLOSE_AFTER_REPLY|
CLIENT_CLOSE_ASAP|
CLIENT_BLOCKED)))
{
if (!(c->flags & (CLIENT_PROTOCOL_ERROR|CLIENT_BLOCKED))) {
c->flags &= ~(CLIENT_CLOSE_ASAP|CLIENT_CLOSE_AFTER_REPLY);
replicationCacheMaster(c);
return;
}
@@ -1140,13 +1223,10 @@ void freeClient(client *c) {
* we lost the connection with the master. */
if (c->flags & CLIENT_MASTER) replicationHandleMasterDisconnection();
/* If this client was scheduled for async freeing we need to remove it
* from the queue. */
if (c->flags & CLIENT_CLOSE_ASAP) {
ln = listSearchKey(server.clients_to_close,c);
serverAssert(ln != NULL);
listDelNode(server.clients_to_close,ln);
}
/* Remove the contribution that this client gave to our
* incrementally computed memory usage. */
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
c->client_cron_last_memory_usage;
/* Release other dynamically allocated client structure fields,
* and finally release the client structure itself. */
@@ -1180,7 +1260,10 @@ void freeClientAsync(client *c) {
pthread_mutex_unlock(&async_free_queue_mutex);
}
void freeClientsInAsyncFreeQueue(void) {
/* Free the clietns marked as CLOSE_ASAP, return the number of clients
* freed. */
int freeClientsInAsyncFreeQueue(void) {
int freed = listLength(server.clients_to_close);
while (listLength(server.clients_to_close)) {
listNode *ln = listFirst(server.clients_to_close);
client *c = listNodeValue(ln);
@@ -1189,6 +1272,7 @@ void freeClientsInAsyncFreeQueue(void) {
freeClient(c);
listDelNode(server.clients_to_close,ln);
}
return freed;
}
/* Return a client by ID, or NULL if the client ID is not in the set
@@ -1458,6 +1542,19 @@ int processInlineBuffer(client *c) {
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
c->repl_ack_time = server.unixtime;
/* Masters should never send us inline protocol to run actual
* commands. If this happens, it is likely due to a bug in Redis where
* we got some desynchronization in the protocol, for example
* beause of a PSYNC gone bad.
*
* However the is an exception: masters may send us just a newline
* to keep the connection active. */
if (querylen != 0 && c->flags & CLIENT_MASTER) {
serverLog(LL_WARNING,"WARNING: Receiving inline protocol from master, master stream corruption? Closing the master connection and discarding the cached master.");
setProtocolError("Master using the inline protocol. Desync?",c);
return C_ERR;
}
/* Move querybuffer position to the next query in the buffer. */
c->qb_pos += querylen+linefeed_chars;
@@ -1477,10 +1574,11 @@ int processInlineBuffer(client *c) {
}
/* Helper function. Record protocol erro details in server log,
* and set the client as CLIENT_CLOSE_AFTER_REPLY. */
* and set the client as CLIENT_CLOSE_AFTER_REPLY and
* CLIENT_PROTOCOL_ERROR. */
#define PROTO_DUMP_LEN 128
static void setProtocolError(const char *errstr, client *c) {
if (server.verbosity <= LL_VERBOSE) {
if (server.verbosity <= LL_VERBOSE || c->flags & CLIENT_MASTER) {
sds client = catClientInfoString(sdsempty(),c);
/* Sample some protocol to given an idea about what was inside. */
@@ -1499,11 +1597,13 @@ static void setProtocolError(const char *errstr, client *c) {
}
/* Log all the client and protocol info. */
serverLog(LL_VERBOSE,
int loglevel = (c->flags & CLIENT_MASTER) ? LL_WARNING :
LL_VERBOSE;
serverLog(loglevel,
"Protocol error (%s) from client: %s. %s", errstr, client, buf);
sdsfree(client);
}
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
c->flags |= (CLIENT_CLOSE_AFTER_REPLY|CLIENT_PROTOCOL_ERROR);
}
/* Process the query buffer for client 'c', setting up the client argument
@@ -1652,12 +1752,49 @@ int processMultibulkBuffer(client *c) {
return C_ERR;
}
/* Perform necessary tasks after a command was executed:
*
* 1. The client is reset unless there are reasons to avoid doing it.
* 2. In the case of master clients, the replication offset is updated.
* 3. Propagate commands we got from our master to replicas down the line. */
void commandProcessed(client *c) {
long long prev_offset = c->reploff;
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;
}
/* Don't reset the client structure for clients blocked in a
* module blocking command, so that the reply callback will
* still be able to access the client argv and argc field.
* The client will be reset in unblockClientFromModule(). */
if (!(c->flags & CLIENT_BLOCKED) ||
c->btype != BLOCKED_MODULE)
{
resetClient(c);
}
/* If the client is a master we need to compute the difference
* between the applied offset before and after processing the buffer,
* to understand how much of the replication stream was actually
* applied to the master state: this quantity, and its corresponding
* part of the replication stream, will be propagated to the
* sub-replicas and to the replication backlog. */
if (c->flags & CLIENT_MASTER) {
long long applied = c->reploff - prev_offset;
if (applied) {
replicationFeedSlavesFromMasterStream(server.slaves,
c->pending_querybuf, applied);
sdsrange(c->pending_querybuf,applied,-1);
}
}
}
/* This function calls processCommand(), but also performs a few sub tasks
* that are useful in that context:
* for the client that are useful in that context:
*
* 1. It sets the current client to the client 'c'.
* 2. In the case of master clients, the replication offset is updated.
* 3. The client is reset unless there are reasons to avoid doing it.
* 2. calls commandProcessed() if the command was handled.
*
* The function returns C_ERR in case the client was freed as a side effect
* of processing the command, otherwise C_OK is returned. */
@@ -1665,20 +1802,7 @@ int processCommandAndResetClient(client *c) {
int deadclient = 0;
server.current_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;
}
/* Don't reset the client structure for clients blocked in a
* module blocking command, so that the reply callback will
* still be able to access the client argv and argc field.
* The client will be reset in unblockClientFromModule(). */
if (!(c->flags & CLIENT_BLOCKED) ||
c->btype != BLOCKED_MODULE)
{
resetClient(c);
}
commandProcessed(c);
}
if (server.current_client == NULL) deadclient = 1;
server.current_client = NULL;
@@ -1775,31 +1899,6 @@ void processInputBuffer(client *c) {
}
}
/* This is a wrapper for processInputBuffer that also cares about handling
* the replication forwarding to the sub-replicas, in case the client 'c'
* is flagged as master. Usually you want to call this instead of the
* raw processInputBuffer(). */
void processInputBufferAndReplicate(client *c) {
if (!(c->flags & CLIENT_MASTER)) {
processInputBuffer(c);
} else {
/* If the client is a master we need to compute the difference
* between the applied offset before and after processing the buffer,
* to understand how much of the replication stream was actually
* applied to the master state: this quantity, and its corresponding
* part of the replication stream, will be propagated to the
* sub-replicas and to the replication backlog. */
size_t prev_offset = c->reploff;
processInputBuffer(c);
size_t applied = c->reploff - prev_offset;
if (applied) {
replicationFeedSlavesFromMasterStream(server.slaves,
c->pending_querybuf, applied);
sdsrange(c->pending_querybuf,applied,-1);
}
}
}
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, readlen;
@@ -1867,7 +1966,7 @@ void readQueryFromClient(connection *conn) {
/* There is more data in the client input buffer, continue parsing it
* in case to check if there is a full command to execute. */
processInputBufferAndReplicate(c);
processInputBuffer(c);
}
void getClientsMaxBuffers(unsigned long *longest_output_list,
@@ -2046,6 +2145,7 @@ void clientCommand(client *c) {
"KILL <option> <value> [option value ...] -- Kill connections. Options are:",
" ADDR <ip:port> -- Kill connection made from <ip:port>",
" TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
" USER <username> -- Kill connections authenticated with such user.",
" SKIPME (yes|no) -- Skip killing current connection (default: yes).",
"LIST [options ...] -- Return information about client connections. Options:",
" TYPE (normal|master|replica|pubsub) -- Return clients of specified type.",
@@ -2096,6 +2196,7 @@ NULL
/* CLIENT KILL <ip:port>
* CLIENT KILL <option> [value] ... <option> [value] */
char *addr = NULL;
user *user = NULL;
int type = -1;
uint64_t id = 0;
int skipme = 1;
@@ -2127,6 +2228,14 @@ NULL
}
} else if (!strcasecmp(c->argv[i]->ptr,"addr") && moreargs) {
addr = c->argv[i+1]->ptr;
} else if (!strcasecmp(c->argv[i]->ptr,"user") && moreargs) {
user = ACLGetUserByName(c->argv[i+1]->ptr,
sdslen(c->argv[i+1]->ptr));
if (user == NULL) {
addReplyErrorFormat(c,"No such user '%s'",
(char*) c->argv[i+1]->ptr);
return;
}
} else if (!strcasecmp(c->argv[i]->ptr,"skipme") && moreargs) {
if (!strcasecmp(c->argv[i+1]->ptr,"yes")) {
skipme = 1;
@@ -2154,6 +2263,7 @@ NULL
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
if (type != -1 && getClientType(client) != type) continue;
if (id != 0 && client->id != id) continue;
if (user && client->user != user) continue;
if (c == client && skipme) continue;
/* Kill it. */
@@ -2270,6 +2380,8 @@ NULL
options |= CLIENT_TRACKING_OPTIN;
} else if (!strcasecmp(c->argv[j]->ptr,"optout")) {
options |= CLIENT_TRACKING_OPTOUT;
} else if (!strcasecmp(c->argv[j]->ptr,"noloop")) {
options |= CLIENT_TRACKING_NOLOOP;
} else if (!strcasecmp(c->argv[j]->ptr,"prefix") && moreargs) {
j++;
prefix = zrealloc(prefix,sizeof(robj*)*(numprefix+1));
@@ -2314,6 +2426,25 @@ NULL
return;
}
if (options & CLIENT_TRACKING_OPTIN && options & CLIENT_TRACKING_OPTOUT)
{
addReplyError(c,
"You can't specify both OPTIN mode and OPTOUT mode");
zfree(prefix);
return;
}
if ((options & CLIENT_TRACKING_OPTIN && c->flags & CLIENT_TRACKING_OPTOUT) ||
(options & CLIENT_TRACKING_OPTOUT && c->flags & CLIENT_TRACKING_OPTIN))
{
addReplyError(c,
"You can't switch OPTIN/OPTOUT mode before disabling "
"tracking for this client, and then re-enabling it with "
"a different mode.");
zfree(prefix);
return;
}
enableTracking(c,redir,options,prefix,numprefix);
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
disableTracking(c);
@@ -2406,7 +2537,7 @@ void helloCommand(client *c) {
/* Let's switch to the specified RESP mode. */
c->resp = ver;
addReplyMapLen(c,7);
addReplyMapLen(c,6 + !server.sentinel_mode);
addReplyBulkCString(c,"server");
addReplyBulkCString(c,"redis");
@@ -2422,7 +2553,7 @@ void helloCommand(client *c) {
addReplyBulkCString(c,"mode");
if (server.sentinel_mode) addReplyBulkCString(c,"sentinel");
if (server.cluster_enabled) addReplyBulkCString(c,"cluster");
else if (server.cluster_enabled) addReplyBulkCString(c,"cluster");
else addReplyBulkCString(c,"standalone");
if (!server.sentinel_mode) {
@@ -2738,9 +2869,8 @@ int clientsArePaused(void) {
* write, close sequence needed to serve a client.
*
* The function returns the total number of events processed. */
int processEventsWhileBlocked(void) {
void processEventsWhileBlocked(void) {
int iterations = 4; /* See the function top-comment. */
int count = 0;
/* Note: when we are processing events while blocked (for instance during
* busy Lua scripts), we set a global flag. When such flag is set, we
@@ -2748,14 +2878,17 @@ int processEventsWhileBlocked(void) {
* See https://github.com/antirez/redis/issues/6988 for more info. */
ProcessingEventsWhileBlocked = 1;
while (iterations--) {
int events = 0;
events += aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
events += handleClientsWithPendingWrites();
long long startval = server.events_processed_while_blocked;
long long ae_events = aeProcessEvents(server.el,
AE_FILE_EVENTS|AE_DONT_WAIT|
AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
/* Note that server.events_processed_while_blocked will also get
* incremeted by callbacks called by the event loop handlers. */
server.events_processed_while_blocked += ae_events;
long long events = server.events_processed_while_blocked - startval;
if (!events) break;
count += events;
}
ProcessingEventsWhileBlocked = 0;
return count;
}
/* ==========================================================================
@@ -2783,6 +2916,11 @@ void *IOThreadMain(void *myid) {
/* The ID is the thread number (from 0 to server.iothreads_num-1), and is
* used by the thread to just manipulate a single sub-array of clients. */
long id = (unsigned long)myid;
char thdname[16];
snprintf(thdname, sizeof(thdname), "io_thd_%ld", id);
redis_set_thread_title(thdname);
redisSetCpuAffinity(server.server_cpulist);
while(1) {
/* Wait for start */
@@ -2906,9 +3044,9 @@ int handleClientsWithPendingWritesUsingThreads(void) {
int processed = listLength(server.clients_pending_write);
if (processed == 0) return 0; /* Return ASAP if there are no clients. */
/* If we have just a few clients to serve, don't use I/O threads, but the
* boring synchronous code. */
if (stopThreadedIOIfNeeded()) {
/* If I/O threads are disabled or we have few clients to serve, don't
* use I/O threads, but thejboring synchronous code. */
if (server.io_threads_num == 1 || stopThreadedIOIfNeeded()) {
return handleClientsWithPendingWrites();
}
@@ -3057,7 +3195,7 @@ int handleClientsWithPendingReadsUsingThreads(void) {
continue;
}
}
processInputBufferAndReplicate(c);
processInputBuffer(c);
}
return processed;
}
+18 -25
View File
@@ -347,7 +347,15 @@ void freeStreamObject(robj *o) {
}
void incrRefCount(robj *o) {
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
o->refcount++;
} else {
if (o->refcount == OBJ_SHARED_REFCOUNT) {
/* Nothing to do: this refcount is immutable. */
} else if (o->refcount == OBJ_STATIC_REFCOUNT) {
serverPanic("You tried to retain an object allocated in the stack");
}
}
}
void decrRefCount(robj *o) {
@@ -974,30 +982,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->repl_backlog = mem;
mem_total += mem;
mem = 0;
if (listLength(server.clients)) {
listIter li;
listNode *ln;
size_t mem_normal = 0, mem_slaves = 0;
listRewind(server.clients,&li);
while((ln = listNext(&li))) {
size_t mem_curr = 0;
client *c = listNodeValue(ln);
int type = getClientType(c);
mem_curr += getClientOutputBufferMemoryUsage(c);
mem_curr += sdsAllocSize(c->querybuf);
mem_curr += sizeof(client);
if (type == CLIENT_TYPE_SLAVE)
mem_slaves += mem_curr;
else
mem_normal += mem_curr;
}
mh->clients_slaves = mem_slaves;
mh->clients_normal = mem_normal;
mem = mem_slaves + mem_normal;
}
mem_total+=mem;
/* Computing the memory used by the clients would be O(N) if done
* here online. We use our values computed incrementally by
* clientsCronTrackClientsMemUsage(). */
mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
mem_total += mh->clients_slaves;
mem_total += mh->clients_normal;
mem = 0;
if (server.aof_state != AOF_OFF) {
+41 -11
View File
@@ -206,6 +206,8 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
int pubsubSubscribePattern(client *c, robj *pattern) {
dictEntry *de;
list *clients;
int retval = 0;
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
@@ -217,6 +219,16 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
pat->pattern = getDecodedObject(pattern);
pat->client = c;
listAddNodeTail(server.pubsub_patterns,pat);
/* Add the client to the pattern -> list of clients hash table */
de = dictFind(server.pubsub_patterns_dict,pattern);
if (de == NULL) {
clients = listCreate();
dictAdd(server.pubsub_patterns_dict,pattern,clients);
incrRefCount(pattern);
} else {
clients = dictGetVal(de);
}
listAddNodeTail(clients,c);
}
/* Notify the client */
addReplyPubsubPatSubscribed(c,pattern);
@@ -226,6 +238,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
* 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
dictEntry *de;
list *clients;
listNode *ln;
pubsubPattern pat;
int retval = 0;
@@ -238,6 +252,18 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
pat.pattern = pattern;
ln = listSearchKey(server.pubsub_patterns,&pat);
listDelNode(server.pubsub_patterns,ln);
/* Remove the client from the pattern -> clients list hash table */
de = dictFind(server.pubsub_patterns_dict,pattern);
serverAssertWithInfo(c,NULL,de != NULL);
clients = dictGetVal(de);
ln = listSearchKey(clients,c);
serverAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
if (listLength(clients) == 0) {
/* Free the list and associated hash entry at all if this was
* the latest client. */
dictDelete(server.pubsub_patterns_dict,pattern);
}
}
/* Notify the client */
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
@@ -284,6 +310,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
int pubsubPublishMessage(robj *channel, robj *message) {
int receivers = 0;
dictEntry *de;
dictIterator *di;
listNode *ln;
listIter li;
@@ -302,23 +329,26 @@ int pubsubPublishMessage(robj *channel, robj *message) {
}
}
/* Send to clients listening to matching channels */
if (listLength(server.pubsub_patterns)) {
listRewind(server.pubsub_patterns,&li);
di = dictGetIterator(server.pubsub_patterns_dict);
if (di) {
channel = getDecodedObject(channel);
while ((ln = listNext(&li)) != NULL) {
pubsubPattern *pat = ln->value;
if (stringmatchlen((char*)pat->pattern->ptr,
sdslen(pat->pattern->ptr),
while((de = dictNext(di)) != NULL) {
robj *pattern = dictGetKey(de);
list *clients = dictGetVal(de);
if (!stringmatchlen((char*)pattern->ptr,
sdslen(pattern->ptr),
(char*)channel->ptr,
sdslen(channel->ptr),0))
{
addReplyPubsubPatMessage(pat->client,
pat->pattern,channel,message);
sdslen(channel->ptr),0)) continue;
listRewind(clients,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
addReplyPubsubPatMessage(c,pattern,channel,message);
receivers++;
}
}
decrRefCount(channel);
dictReleaseIterator(di);
}
return receivers;
}
+2 -2
View File
@@ -110,7 +110,7 @@ quicklist *quicklistCreate(void) {
return quicklist;
}
#define COMPRESS_MAX (1 << QL_COMP_BITS)
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
if (compress > COMPRESS_MAX) {
compress = COMPRESS_MAX;
@@ -120,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
quicklist->compress = compress;
}
#define FILL_MAX (1 << (QL_FILL_BITS-1))
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
void quicklistSetFill(quicklist *quicklist, int fill) {
if (fill > FILL_MAX) {
fill = FILL_MAX;
+4 -2
View File
@@ -1,6 +1,8 @@
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* Version 1.2 -- 7 February 2019
*
* Copyright (c) 2017-2019, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -1737,7 +1739,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
}
if (steps == 0) {
size_t fle = floor(log(it->rt->numele));
size_t fle = 1+floor(log(it->rt->numele));
fle *= 2;
steps = 1 + rand() % fle;
}
+98 -41
View File
@@ -1002,8 +1002,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
* the rdbSaveObject() function. Currently we use a trick to get
* this length with very little changes to the code. In the future
* we could switch to a faster solution. */
size_t rdbSavedObjectLen(robj *o) {
ssize_t len = rdbSaveObject(NULL,o,NULL);
size_t rdbSavedObjectLen(robj *o, robj *key) {
ssize_t len = rdbSaveObject(NULL,o,key);
serverAssertWithInfo(NULL,o,len != -1);
return len;
}
@@ -1175,10 +1175,7 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
if (rdbSaveLen(rdb,j) == -1) goto werr;
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
* 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. */
/* Write the RESIZE DB opcode. */
uint64_t db_size, expires_size;
db_size = dictSize(db->dict);
expires_size = dictSize(db->expires);
@@ -1354,6 +1351,7 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
/* Child */
redisSetProcTitle("redis-rdb-bgsave");
redisSetCpuAffinity(server.bgsave_cpulist);
retval = rdbSave(filename,rsi);
if (retval == C_OK) {
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
@@ -1425,7 +1423,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
/* Load a Redis object of the specified type from the specified file.
* On success a newly allocated object is returned, otherwise NULL. */
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
robj *o = NULL, *ele, *dec;
uint64_t len;
unsigned int i;
@@ -1444,7 +1442,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
/* Load every single element of the list */
while(len--) {
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) {
decrRefCount(o);
return NULL;
}
dec = getDecodedObject(ele);
size_t len = sdslen(dec->ptr);
quicklistPushTail(o->ptr, dec->ptr, len);
@@ -1471,8 +1472,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
long long llval;
sds sdsele;
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
if (o->encoding == OBJ_ENCODING_INTSET) {
/* Fetch integer value from element. */
@@ -1511,13 +1514,23 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
double score;
zskiplistNode *znode;
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
if (rdbtype == RDB_TYPE_ZSET_2) {
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) return NULL;
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) {
decrRefCount(o);
sdsfree(sdsele);
return NULL;
}
} else {
if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;
if (rdbLoadDoubleValue(rdb,&score) == -1) {
decrRefCount(o);
sdsfree(sdsele);
return NULL;
}
}
/* Don't care about integer-encoded strings. */
@@ -1549,10 +1562,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
while (o->encoding == OBJ_ENCODING_ZIPLIST && len > 0) {
len--;
/* Load raw strings */
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
sdsfree(field);
decrRefCount(o);
return NULL;
}
/* Add pair to ziplist */
o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
@@ -1580,10 +1598,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
while (o->encoding == OBJ_ENCODING_HT && len > 0) {
len--;
/* Load encoded strings */
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
== NULL) return NULL;
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
sdsfree(field);
decrRefCount(o);
return NULL;
}
/* Add pair to hash table */
ret = dictAdd((dict*)o->ptr, field, value);
@@ -1603,7 +1626,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
while (len--) {
unsigned char *zl =
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
if (zl == NULL) return NULL;
if (zl == NULL) {
decrRefCount(o);
return NULL;
}
quicklistAppendZiplist(o->ptr, zl);
}
} else if (rdbtype == RDB_TYPE_HASH_ZIPMAP ||
@@ -1826,8 +1852,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
decrRefCount(o);
return NULL;
}
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
1);
streamConsumer *consumer =
streamLookupConsumer(cgroup,cname,SLC_NONE);
sdsfree(cname);
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
if (rioGetReadError(rdb)) {
@@ -1889,7 +1915,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
exit(1);
}
RedisModuleIO io;
moduleInitIOContext(io,mt,rdb,key);
robj keyobj;
initStaticStringObject(keyobj,key);
moduleInitIOContext(io,mt,rdb,&keyobj);
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
/* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */
@@ -2047,7 +2075,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
long long lru_clock = LRU_CLOCK();
while(1) {
robj *key, *val;
sds key;
robj *val;
/* Read type. */
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
@@ -2219,10 +2248,11 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
}
/* Read key */
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
goto eoferr;
/* Read value */
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
decrRefCount(key);
sdsfree(key);
goto eoferr;
}
@@ -2230,26 +2260,49 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
* 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 (iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
decrRefCount(key);
* snapshot taken by the master may not be reflected on the slave.
* Similarly if the RDB is the preamble of an AOF file, we want to
* load all the keys as they are, since the log of operations later
* assume to work in an exact keyspace state. */
if (iAmMaster() &&
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
expiretime != -1 && expiretime < now)
{
sdsfree(key);
decrRefCount(val);
} else {
robj keyobj;
/* Add the new object in the hash table */
dbAdd(db,key,val);
int added = dbAddRDBLoad(db,key,val);
if (!added) {
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
/* This flag is useful for DEBUG RELOAD special modes.
* When it's set we allow new keys to replace the current
* keys with the same name. */
initStaticStringObject(keyobj,key);
dbSyncDelete(db,&keyobj);
dbAddRDBLoad(db,key,val);
} else {
serverLog(LL_WARNING,
"RDB has duplicated key '%s' in DB %d",key,db->id);
serverPanic("Duplicated key found in RDB file");
}
}
/* Set the expire time if needed */
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
if (expiretime != -1) {
initStaticStringObject(keyobj,key);
setExpire(NULL,db,&keyobj,expiretime);
}
/* Set usage information (for eviction). */
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
/* Decrement the key refcount since dbAdd() will take its
* own reference. */
decrRefCount(key);
}
if (server.key_load_delay)
usleep(server.key_load_delay);
/* Loading the database more slowly is useful in order to test
* certain edge cases. */
if (server.key_load_delay) usleep(server.key_load_delay);
/* Reset the state that is key-specified and is populated by
* opcodes before the key, so that we start from scratch again. */
@@ -2267,7 +2320,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
if (cksum == 0) {
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
} else if (cksum != expected) {
serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
serverLog(LL_WARNING,"Wrong RDB checksum expected: (%llx) but "
"got (%llx). Aborting now.",
(unsigned long long)expected,
(unsigned long long)cksum);
rdbExitReportCorruptRDB("RDB CRC error");
}
}
@@ -2433,6 +2489,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
rioInitWithFd(&rdb,server.rdb_pipe_write);
redisSetProcTitle("redis-rdb-to-slaves");
redisSetCpuAffinity(server.bgsave_cpulist);
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
if (retval == C_OK && rioFlush(&rdb) == 0)
+6 -5
View File
@@ -122,9 +122,10 @@
#define RDB_LOAD_SDS (1<<2)
/* flags on the purpose of rdb save or load */
#define RDBFLAGS_NONE 0
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
#define RDBFLAGS_REPLICATION (1<<1)
#define RDBFLAGS_NONE 0 /* No special RDB loading. */
#define RDBFLAGS_AOF_PREAMBLE (1<<0) /* Load/save the RDB as AOF preamble. */
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
int rdbSaveType(rio *rdb, unsigned char type);
int rdbLoadType(rio *rdb);
@@ -143,8 +144,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
void rdbRemoveTempFile(pid_t childpid);
int rdbSave(char *filename, rdbSaveInfo *rsi);
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
size_t rdbSavedObjectLen(robj *o);
robj *rdbLoadObject(int type, rio *rdb, robj *key);
size_t rdbSavedObjectLen(robj *o, robj *key);
robj *rdbLoadObject(int type, rio *rdb, sds key);
void backgroundSaveDoneHandler(int exitcode, int bysignal);
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
+29 -5
View File
@@ -94,6 +94,7 @@ static struct config {
sds dbnumstr;
char *tests;
char *auth;
const char *user;
int precision;
int num_threads;
struct benchmarkThread **threads;
@@ -258,7 +259,10 @@ static redisConfig *getRedisConfig(const char *ip, int port,
if(config.auth) {
void *authReply = NULL;
redisAppendCommand(c, "AUTH %s", config.auth);
if (config.user == NULL)
redisAppendCommand(c, "AUTH %s", config.auth);
else
redisAppendCommand(c, "AUTH %s %s", config.user, config.auth);
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
if (reply) freeReplyObject(reply);
reply = ((redisReply *) authReply);
@@ -275,7 +279,7 @@ static redisConfig *getRedisConfig(const char *ip, int port,
for (; i < 2; i++) {
int res = redisGetReply(c, &r);
if (reply) freeReplyObject(reply);
reply = ((redisReply *) r);
reply = res == REDIS_OK ? ((redisReply *) r) : NULL;
if (res != REDIS_OK || !r) goto fail;
if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "ERROR: %s\n", reply->str);
@@ -299,7 +303,7 @@ fail:
else fprintf(stderr, "%s\n", hostsocket);
freeReplyObject(reply);
redisFree(c);
zfree(cfg);
freeRedisConfig(cfg);
return NULL;
}
static void freeRedisConfig(redisConfig *cfg) {
@@ -628,7 +632,12 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->prefix_pending = 0;
if (config.auth) {
char *buf = NULL;
int len = redisFormatCommand(&buf, "AUTH %s", config.auth);
int len;
if (config.user == NULL)
len = redisFormatCommand(&buf, "AUTH %s", config.auth);
else
len = redisFormatCommand(&buf, "AUTH %s %s",
config.user, config.auth);
c->obuf = sdscatlen(c->obuf, buf, len);
free(buf);
c->prefix_pending++;
@@ -1269,6 +1278,17 @@ static void updateClusterSlotsConfiguration() {
pthread_mutex_unlock(&config.is_updating_slots_mutex);
}
/* Generate random data for redis benchmark. See #7196. */
static void genBenchmarkRandomData(char *data, int count) {
static uint32_t state = 1234;
int i = 0;
while (count--) {
state = (state*1103515245+12345);
data[i++] = '0'+((state>>16)&63);
}
}
/* Returns number of consumed options. */
int parseOptions(int argc, const char **argv) {
int i;
@@ -1299,6 +1319,9 @@ int parseOptions(int argc, const char **argv) {
} else if (!strcmp(argv[i],"-a") ) {
if (lastarg) goto invalid;
config.auth = strdup(argv[++i]);
} else if (!strcmp(argv[i],"--user")) {
if (lastarg) goto invalid;
config.user = argv[++i];
} else if (!strcmp(argv[i],"-d")) {
if (lastarg) goto invalid;
config.datasize = atoi(argv[++i]);
@@ -1385,6 +1408,7 @@ usage:
" -p <port> Server port (default 6379)\n"
" -s <socket> Server socket (overrides host and port)\n"
" -a <password> Password for Redis Auth\n"
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" -c <clients> Number of parallel connections (default 50)\n"
" -n <requests> Total number of requests (default 100000)\n"
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
@@ -1619,7 +1643,7 @@ int main(int argc, const char **argv) {
/* Run default benchmark suite. */
data = zmalloc(config.datasize+1);
do {
memset(data,'x',config.datasize);
genBenchmarkRandomData(data, config.datasize);
data[config.datasize] = '\0';
if (test_is_selected("ping_inline") || test_is_selected("ping"))
+1 -1
View File
@@ -287,7 +287,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
rdbstate.keys++;
/* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
/* Check if the key already expired. */
if (expiretime != -1 && expiretime < now)
rdbstate.already_expired++;
+100 -36
View File
@@ -123,6 +123,7 @@
#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
#define CLUSTER_MANAGER_OPT_COLD 1 << 1
@@ -698,7 +699,8 @@ static char *hintsCallback(const char *buf, int *color, int *bold) {
for (i = 0; i < helpEntriesLen; i++) {
if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
if (strcasecmp(argv[0],helpEntries[i].full) == 0)
if (strcasecmp(argv[0],helpEntries[i].full) == 0 ||
strcasecmp(buf,helpEntries[i].full) == 0)
{
*color = 90;
*bold = 0;
@@ -1599,6 +1601,9 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
} else if (!strcmp(argv[i],"--cluster-fix-with-unreachable-masters")) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
#ifdef USE_OPENSSL
} else if (!strcmp(argv[i],"--tls")) {
config.tls = 1;
@@ -1792,7 +1797,14 @@ static void usage(void) {
exit(1);
}
static int confirmWithYes(char *msg) {
static int confirmWithYes(char *msg, int force) {
/* if force is true and --cluster-yes option is on,
* do not prompt for an answer */
if (force &&
(config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES)) {
return 1;
}
printf("%s (type 'yes' to accept): ", msg);
fflush(stdout);
char buf[4];
@@ -2146,6 +2158,7 @@ static int evalMode(int argc, char **argv) {
static struct clusterManager {
list *nodes; /* List of nodes in the configuration. */
list *errors;
int unreachable_masters; /* Masters we are not able to reach. */
} cluster_manager;
/* Used by clusterManagerFixSlotsCoverage */
@@ -2288,7 +2301,7 @@ clusterManagerCommandDef clusterManagerCommands[] = {
"search-multiple-owners"},
{"info", clusterManagerCommandInfo, -1, "host:port", NULL},
{"fix", clusterManagerCommandFix, -1, "host:port",
"search-multiple-owners"},
"search-multiple-owners,fix-with-unreachable-masters"},
{"reshard", clusterManagerCommandReshard, -1, "host:port",
"from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
"replace"},
@@ -3410,6 +3423,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
size_t *argv_len = NULL;
int c = (replace ? 8 : 7);
if (config.auth) c += 2;
if (config.user) c += 1;
size_t argc = c + reply->elements;
size_t i, offset = 6; // Keys Offset
argv = zcalloc(argc * sizeof(char *));
@@ -3436,12 +3450,24 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
offset++;
}
if (config.auth) {
argv[offset] = "AUTH";
argv_len[offset] = 4;
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
offset++;
if (config.user) {
argv[offset] = "AUTH2";
argv_len[offset] = 5;
offset++;
argv[offset] = config.user;
argv_len[offset] = strlen(config.user);
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
offset++;
} else {
argv[offset] = "AUTH";
argv_len[offset] = 4;
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
offset++;
}
}
argv[offset] = "KEYS";
argv_len[offset] = 4;
@@ -4013,7 +4039,9 @@ static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts) {
if (friend->flags & (CLUSTER_MANAGER_FLAG_NOADDR |
CLUSTER_MANAGER_FLAG_DISCONNECT |
CLUSTER_MANAGER_FLAG_FAIL))
{
goto invalid_friend;
}
listAddNodeTail(cluster_manager.nodes, friend);
} else {
clusterManagerLogErr("[ERR] Unable to load info for "
@@ -4023,6 +4051,8 @@ static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts) {
}
continue;
invalid_friend:
if (!(friend->flags & CLUSTER_MANAGER_FLAG_SLAVE))
cluster_manager.unreachable_masters++;
freeClusterManagerNode(friend);
}
listRelease(node->friends);
@@ -4295,17 +4325,18 @@ static int clusterManagerGetCoveredSlots(char *all_slots) {
}
static void clusterManagerPrintSlotsList(list *slots) {
clusterManagerNode n = {0};
listIter li;
listNode *ln;
listRewind(slots, &li);
sds first = NULL;
while ((ln = listNext(&li)) != NULL) {
sds slot = ln->value;
if (!first) first = slot;
else printf(", ");
printf("%s", slot);
int slot = atoi(ln->value);
if (slot >= 0 && slot < CLUSTER_MANAGER_SLOTS)
n.slots[slot] = 1;
}
printf("\n");
sds nodeslist = clusterManagerNodeSlotsString(&n);
printf("%s\n", nodeslist);
sdsfree(nodeslist);
}
/* Return the node, among 'nodes' with the greatest number of keys
@@ -4395,18 +4426,21 @@ static clusterManagerNode *clusterManagerNodeMasterRandom() {
}
static int clusterManagerFixSlotsCoverage(char *all_slots) {
int force_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
if (cluster_manager.unreachable_masters > 0 && !force_fix) {
clusterManagerLogWarn("*** Fixing slots coverage with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
exit(1);
}
int i, fixed = 0;
list *none = NULL, *single = NULL, *multi = NULL;
clusterManagerLogInfo(">>> Fixing slots coverage...\n");
printf("List of not covered slots: \n");
int uncovered_count = 0;
sds log = sdsempty();
for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
int covered = all_slots[i];
if (!covered) {
sds key = sdsfromlonglong((long long) i);
if (uncovered_count++ > 0) printf(",");
printf("%s", (char *) key);
sds slot = sdsfromlonglong((long long) i);
list *slot_nodes = listCreate();
sds slot_nodes_str = sdsempty();
listIter li;
@@ -4433,13 +4467,11 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
}
freeReplyObject(reply);
}
log = sdscatfmt(log, "\nSlot %S has keys in %u nodes: %S",
key, listLength(slot_nodes), slot_nodes_str);
sdsfree(slot_nodes_str);
dictAdd(clusterManagerUncoveredSlots, key, slot_nodes);
dictAdd(clusterManagerUncoveredSlots, slot, slot_nodes);
}
}
printf("\n%s\n", log);
/* For every slot, take action depending on the actual condition:
* 1) No node has keys for this slot.
* 2) A single node has keys for this slot.
@@ -4460,12 +4492,16 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
}
dictReleaseIterator(iter);
/* we want explicit manual confirmation from users for all the fix cases */
int force = 0;
/* Handle case "1": keys in no node. */
if (listLength(none) > 0) {
printf("The following uncovered slots have no keys "
"across the cluster:\n");
clusterManagerPrintSlotsList(none);
if (confirmWithYes("Fix these slots by covering with a random node?")){
if (confirmWithYes("Fix these slots by covering with a random node?",
force)) {
listIter li;
listNode *ln;
listRewind(none, &li);
@@ -4491,7 +4527,8 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
if (listLength(single) > 0) {
printf("The following uncovered slots have keys in just one node:\n");
clusterManagerPrintSlotsList(single);
if (confirmWithYes("Fix these slots by covering with those nodes?")){
if (confirmWithYes("Fix these slots by covering with those nodes?",
force)) {
listIter li;
listNode *ln;
listRewind(single, &li);
@@ -4523,7 +4560,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
printf("The following uncovered slots have keys in multiple nodes:\n");
clusterManagerPrintSlotsList(multi);
if (confirmWithYes("Fix these slots by moving keys "
"into a single node?")) {
"into a single node?", force)) {
listIter li;
listNode *ln;
listRewind(multi, &li);
@@ -4581,7 +4618,6 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
}
}
cleanup:
sdsfree(log);
if (none) listRelease(none);
if (single) listRelease(single);
if (multi) listRelease(multi);
@@ -4592,24 +4628,38 @@ cleanup:
* more nodes. This function fixes this condition by migrating keys where
* it seems more sensible. */
static int clusterManagerFixOpenSlot(int slot) {
int force_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
if (cluster_manager.unreachable_masters > 0 && !force_fix) {
clusterManagerLogWarn("*** Fixing open slots with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
exit(1);
}
clusterManagerLogInfo(">>> Fixing open slot %d\n", slot);
/* Try to obtain the current slot owner, according to the current
* nodes configuration. */
int success = 1;
list *owners = listCreate();
list *owners = listCreate(); /* List of nodes claiming some ownership.
it could be stating in the configuration
to have the node ownership, or just
holding keys for such slot. */
list *migrating = listCreate();
list *importing = listCreate();
sds migrating_str = sdsempty();
sds importing_str = sdsempty();
clusterManagerNode *owner = NULL;
clusterManagerNode *owner = NULL; /* The obvious slot owner if any. */
/* Iterate all the nodes, looking for potential owners of this slot. */
listIter li;
listNode *ln;
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
if (n->slots[slot]) listAddNodeTail(owners, n);
else {
if (n->slots[slot]) {
listAddNodeTail(owners, n);
} else {
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER COUNTKEYSINSLOT %d", slot);
success = clusterManagerCheckRedisReply(n, r, NULL);
@@ -4623,7 +4673,14 @@ static int clusterManagerFixOpenSlot(int slot) {
if (!success) goto cleanup;
}
}
/* If we have only a single potential owner for this slot,
* set it as "owner". */
if (listLength(owners) == 1) owner = listFirst(owners)->value;
/* Scan the list of nodes again, in order to populate the
* list of nodes in importing or migrating state for
* this slot. */
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
@@ -4655,6 +4712,7 @@ static int clusterManagerFixOpenSlot(int slot) {
}
}
}
/* If the node is neither migrating nor importing and it's not
* the owner, then is added to the importing list in case
* it has keys in the slot. */
@@ -4679,11 +4737,12 @@ static int clusterManagerFixOpenSlot(int slot) {
printf("Set as migrating in: %s\n", migrating_str);
if (sdslen(importing_str) > 0)
printf("Set as importing in: %s\n", importing_str);
/* If there is no slot owner, set as owner the node with the biggest
* number of keys, among the set of migrating / importing nodes. */
if (owner == NULL) {
clusterManagerLogInfo(">>> Nobody claims ownership, "
"selecting an owner...\n");
clusterManagerLogInfo(">>> No single clear owner for the slot, "
"selecting an owner by # of keys...\n");
owner = clusterManagerGetNodeWithMostKeysInSlot(cluster_manager.nodes,
slot, NULL);
// If we still don't have an owner, we can't fix it.
@@ -4714,6 +4773,7 @@ static int clusterManagerFixOpenSlot(int slot) {
clusterManagerRemoveNodeFromList(migrating, owner);
clusterManagerRemoveNodeFromList(importing, owner);
}
/* If there are multiple owners of the slot, we need to fix it
* so that a single node is the owner and all the other nodes
* are in importing state. Later the fix can be handled by one
@@ -4746,6 +4806,7 @@ static int clusterManagerFixOpenSlot(int slot) {
}
}
int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
/* Case 1: The slot is in migrating state in one node, and in
* importing state in 1 node. That's trivial to address. */
if (listLength(migrating) == 1 && listLength(importing) == 1) {
@@ -4757,6 +4818,7 @@ static int clusterManagerFixOpenSlot(int slot) {
move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
}
/* Case 2: There are multiple nodes that claim the slot as importing,
* they probably got keys about the slot after a restart so opened
* the slot. In this case we just move all the keys to the owner
@@ -4787,6 +4849,7 @@ static int clusterManagerFixOpenSlot(int slot) {
if (!success) goto cleanup;
}
}
/* Case 3: The slot is in migrating state in one node but multiple
* other nodes claim to be in importing state and don't have any key in
* the slot. We search for the importing node having the same ID as
@@ -5460,7 +5523,8 @@ assign_replicas:
}
clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count);
clusterManagerShowNodes();
if (confirmWithYes("Can I set the above configuration?")) {
int force = 1;
if (confirmWithYes("Can I set the above configuration?", force)) {
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *node = ln->value;
+10 -1
View File
@@ -470,7 +470,11 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(Re
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
#ifdef __GNUC__
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#else
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
#endif
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
@@ -554,8 +558,13 @@ void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long do
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
#ifdef __GNUC__
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#else
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
#endif
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
@@ -673,7 +682,7 @@ int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
#endif
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
/* This is included inline inside each Redis module. */
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
+87 -52
View File
@@ -162,7 +162,6 @@ void feedReplicationBacklog(void *ptr, size_t len) {
unsigned char *p = ptr;
server.master_repl_offset += len;
server.master_repl_meaningful_offset = server.master_repl_offset;
/* This is a circular buffer, so write as much data we can at every
* iteration and rewind the "idx" index if we reach the limit. */
@@ -747,6 +746,9 @@ void syncCommand(client *c) {
changeReplicationId();
clearReplicationId2();
createReplicationBacklog();
serverLog(LL_NOTICE,"Replication backlog created, my new "
"replication IDs are '%s' and '%s'",
server.replid, server.replid2);
}
/* CASE 1: BGSAVE is in progress, with disk target. */
@@ -972,10 +974,41 @@ void removeRDBUsedToSyncReplicas(void) {
}
}
#if HAVE_SENDFILE
/* Implements redis_sendfile to transfer data between file descriptors and
* avoid transferring data to and from user space.
*
* The function prototype is just like sendfile(2) on Linux. in_fd is a file
* descriptor opened for reading and out_fd is a descriptor opened for writing.
* offset specifies where to start reading data from in_fd. count is the number
* of bytes to copy between the file descriptors.
*
* The return value is the number of bytes written to out_fd, if the transfer
* was successful. On error, -1 is returned, and errno is set appropriately. */
ssize_t redis_sendfile(int out_fd, int in_fd, off_t offset, size_t count) {
#if defined(__linux__)
#include <sys/sendfile.h>
return sendfile(out_fd, in_fd, &offset, count);
#elif defined(__APPLE__)
off_t len = count;
/* Notice that it may return -1 and errno is set to EAGAIN even if some
* bytes have been sent successfully and the len argument is set correctly
* when using a socket marked for non-blocking I/O. */
if (sendfile(in_fd, out_fd, offset, &len, NULL, 0) == -1 &&
errno != EAGAIN) return -1;
else
return (ssize_t)len;
#endif
errno = ENOSYS;
return -1;
}
#endif
void sendBulkToSlave(connection *conn) {
client *slave = connGetPrivateData(conn);
char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen;
ssize_t nwritten;
/* Before sending the RDB file, we send the preamble as configured by the
* replication process. Currently the preamble is just the bulk count of
@@ -1000,23 +1033,46 @@ void sendBulkToSlave(connection *conn) {
}
}
/* If the preamble was already transferred, send the RDB bulk data. */
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
buflen = read(slave->repldbfd,buf,PROTO_IOBUF_LEN);
if (buflen <= 0) {
serverLog(LL_WARNING,"Read error sending DB to replica: %s",
(buflen == 0) ? "premature EOF" : strerror(errno));
freeClient(slave);
return;
}
if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
/* If the preamble was already transferred, send the RDB bulk data.
* try to use sendfile system call if supported, unless tls is enabled.
* fallback to normal read+write otherwise. */
nwritten = 0;
#if HAVE_SENDFILE
if (!server.tls_replication) {
if ((nwritten = redis_sendfile(conn->fd,slave->repldbfd,
slave->repldboff,PROTO_IOBUF_LEN)) == -1)
{
if (errno != EAGAIN) {
serverLog(LL_WARNING,"Sendfile error sending DB to replica: %s",
strerror(errno));
freeClient(slave);
}
return;
}
return;
}
#endif
if (!nwritten) {
ssize_t buflen;
char buf[PROTO_IOBUF_LEN];
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
buflen = read(slave->repldbfd,buf,PROTO_IOBUF_LEN);
if (buflen <= 0) {
serverLog(LL_WARNING,"Read error sending DB to replica: %s",
(buflen == 0) ? "premature EOF" : strerror(errno));
freeClient(slave);
return;
}
if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
}
return;
}
}
slave->repldboff += nwritten;
server.stat_net_output_bytes += nwritten;
if (slave->repldboff == slave->repldbsize) {
@@ -1525,6 +1581,10 @@ void readSyncBulkPayload(connection *conn) {
nread = connRead(conn,buf,readlen);
if (nread <= 0) {
if (connGetState(conn) == CONN_STATE_CONNECTED) {
/* equivalent to EAGAIN */
return;
}
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost");
cancelReplicationHandshake();
@@ -1769,7 +1829,6 @@ void readSyncBulkPayload(connection *conn) {
* we are starting a new history. */
memcpy(server.replid,server.master->replid,sizeof(server.replid));
server.master_repl_offset = server.master->reploff;
server.master_repl_meaningful_offset = server.master->reploff;
clearReplicationId2();
/* Let's create the replication backlog if needed. Slaves need to
@@ -2479,14 +2538,18 @@ void replicationUnsetMaster(void) {
sdsfree(server.masterhost);
server.masterhost = NULL;
/* When a slave is turned into a master, the current replication ID
* (that was inherited from the master at synchronization time) is
* used as secondary ID up to the current offset, and a new replication
* ID is created to continue with a new replication history. */
shiftReplicationId();
if (server.master) freeClient(server.master);
replicationDiscardCachedMaster();
cancelReplicationHandshake();
/* When a slave is turned into a master, the current replication ID
* (that was inherited from the master at synchronization time) is
* used as secondary ID up to the current offset, and a new replication
* ID is created to continue with a new replication history.
*
* NOTE: this function MUST be called after we call
* freeClient(server.master), since there we adjust the replication
* offset trimming the final PINGs. See Github issue #7320. */
shiftReplicationId();
/* Disconnecting all the slaves is required: we need to inform slaves
* of the replication ID change (see shiftReplicationId() call). However
* the slaves will be able to partially resync with us, so it will be
@@ -2738,26 +2801,6 @@ void replicationCacheMasterUsingMyself(void) {
* offset for PSYNC. */
server.master_initial_offset = server.master_repl_offset;
/* However if the "meaningful" offset, that is the offset without
* the final PINGs in the stream, is different, use this instead:
* often when the master is no longer reachable, replicas will never
* receive the PINGs, however the master will end with an incremented
* offset because of the PINGs and will not be able to incrementally
* PSYNC with the new master. */
if (server.master_repl_offset > server.master_repl_meaningful_offset) {
long long delta = server.master_repl_offset -
server.master_repl_meaningful_offset;
serverLog(LL_NOTICE,
"Using the meaningful offset %lld instead of %lld to exclude "
"the final PINGs (%lld bytes difference)",
server.master_repl_meaningful_offset,
server.master_repl_offset,
delta);
server.master_initial_offset = server.master_repl_meaningful_offset;
server.repl_backlog_histlen -= delta;
if (server.repl_backlog_histlen < 0) server.repl_backlog_histlen = 0;
}
/* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */
replicationCreateMasterClient(NULL,-1);
@@ -3148,18 +3191,10 @@ void replicationCron(void) {
clientsArePaused();
if (!manual_failover_in_progress) {
long long before_ping = server.master_repl_meaningful_offset;
ping_argv[0] = createStringObject("PING",4);
replicationFeedSlaves(server.slaves, server.slaveseldb,
ping_argv, 1);
decrRefCount(ping_argv[0]);
/* The server.master_repl_meaningful_offset variable represents
* the offset of the replication stream without the pending PINGs.
* This is useful to set the right replication offset for PSYNC
* when the master is turned into a replica. Otherwise pending
* PINGs may not allow it to perform an incremental sync with the
* new master. */
server.master_repl_meaningful_offset = before_ping;
}
}
+4 -4
View File
@@ -657,12 +657,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
!server.loading && /* Don't care about mem if loading. */
!server.masterhost && /* Slave must execute the script. */
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
server.lua_oom && /* Detected OOM when script start. */
(cmd->flags & CMD_DENYOOM))
{
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
@@ -960,6 +959,7 @@ int luaLogCommand(lua_State *lua) {
lua_pushstring(lua, "Invalid debug level.");
return lua_error(lua);
}
if (level < server.verbosity) return 0;
/* Glue together all the arguments */
log = sdsempty();
+1 -1
View File
@@ -97,11 +97,11 @@ sds sdsnewlen(const void *init, size_t initlen) {
unsigned char *fp; /* flags pointer. */
sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL;
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
switch(type) {
+1 -1
View File
@@ -869,8 +869,8 @@ void sentinelCollectTerminatedScripts(void) {
}
listDelNode(sentinel.scripts_queue,ln);
sentinelReleaseScriptJob(sj);
sentinel.running_scripts--;
}
sentinel.running_scripts--;
}
}
+154 -179
View File
@@ -672,15 +672,15 @@ struct redisCommand redisCommandTable[] = {
0,NULL,1,1,1,0,0,0},
{"multi",multiCommand,1,
"no-script fast @transaction",
"no-script fast ok-loading ok-stale @transaction",
0,NULL,0,0,0,0,0,0},
{"exec",execCommand,1,
"no-script no-monitor no-slowlog @transaction",
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
0,NULL,0,0,0,0,0,0},
{"discard",discardCommand,1,
"no-script fast @transaction",
"no-script fast ok-loading ok-stale @transaction",
0,NULL,0,0,0,0,0,0},
{"sync",syncCommand,1,
@@ -947,11 +947,11 @@ struct redisCommand redisCommandTable[] = {
0,NULL,1,1,1,0,0,0},
{"xread",xreadCommand,-4,
"read-only no-script @stream @blocking",
"read-only @stream @blocking",
0,xreadGetKeys,1,1,1,0,0,0},
{"xreadgroup",xreadCommand,-7,
"write no-script @stream @blocking",
"write @stream @blocking",
0,xreadGetKeys,1,1,1,0,0,0},
{"xgroup",xgroupCommand,-2,
@@ -1004,7 +1004,11 @@ struct redisCommand redisCommandTable[] = {
{"acl",aclCommand,-2,
"admin no-script no-slowlog ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}
0,NULL,0,0,0,0,0,0},
{"stralgo",stralgoCommand,-2,
"read-only @string",
0,lcsGetKeys,0,0,0,0,0,0}
};
/*============================ Utility functions ============================ */
@@ -1215,11 +1219,15 @@ int dictEncObjKeyCompare(void *privdata, const void *key1,
o2->encoding == OBJ_ENCODING_INT)
return o1->ptr == o2->ptr;
o1 = getDecodedObject(o1);
o2 = getDecodedObject(o2);
/* Due to OBJ_STATIC_REFCOUNT, we avoid calling getDecodedObject() without
* good reasons, because it would incrRefCount() the object, which
* is invalid. So we check to make sure dictFind() works with static
* objects as well. */
if (o1->refcount != OBJ_STATIC_REFCOUNT) o1 = getDecodedObject(o1);
if (o2->refcount != OBJ_STATIC_REFCOUNT) o2 = getDecodedObject(o2);
cmp = dictSdsKeyCompare(privdata,o1->ptr,o2->ptr);
decrRefCount(o1);
decrRefCount(o2);
if (o1->refcount != OBJ_STATIC_REFCOUNT) decrRefCount(o1);
if (o2->refcount != OBJ_STATIC_REFCOUNT) decrRefCount(o2);
return cmp;
}
@@ -1473,135 +1481,6 @@ int allPersistenceDisabled(void) {
return server.saveparamslen == 0 && server.aof_state == AOF_OFF;
}
/* ========================== Clients timeouts ============================= */
/* Check if this blocked client timedout (does nothing if the client is
* not blocked right now). If so send a reply, unblock it, and return 1.
* Otherwise 0 is returned and no operation is performed. */
int checkBlockedClientTimeout(client *c, mstime_t now) {
if (c->flags & CLIENT_BLOCKED &&
c->bpop.timeout != 0
&& c->bpop.timeout < now)
{
/* Handle blocking operation specific timeout. */
replyToBlockedClientTimedOut(c);
unblockClient(c);
return 1;
} else {
return 0;
}
}
/* Check for timeouts. Returns non-zero if the client was terminated.
* The function gets the current time in milliseconds as argument since
* it gets called multiple times in a loop, so calling gettimeofday() for
* each iteration would be costly without any actual gain. */
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (server.maxidletime &&
/* This handles the idle clients connection timeout if set. */
!(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
!(c->flags & CLIENT_MASTER) && /* No timeout for masters */
!(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
!(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
(now - c->lastinteraction > server.maxidletime))
{
serverLog(LL_VERBOSE,"Closing idle client");
freeClient(c);
return 1;
} else if (c->flags & CLIENT_BLOCKED) {
/* Blocked OPS timeout is handled with milliseconds resolution.
* However note that the actual resolution is limited by
* server.hz. So for short timeouts (less than SERVER_SHORT_TIMEOUT
* milliseconds) we populate a Radix tree and handle such timeouts
* in clientsHandleShortTimeout(). */
if (checkBlockedClientTimeout(c,now_ms)) return 0;
/* Cluster: handle unblock & redirect of clients blocked
* into keys no longer served by this server. */
if (server.cluster_enabled) {
if (clusterRedirectBlockedClientIfNeeded(c))
unblockClient(c);
}
}
return 0;
}
/* For shor timeouts, less than < CLIENT_SHORT_TIMEOUT milliseconds, we
* populate a radix tree of 128 bit keys composed as such:
*
* [8 byte big endian expire time]+[8 byte client ID]
*
* We don't do any cleanup in the Radix tree: when we run the clients that
* reached the timeout already, if they are no longer existing or no longer
* blocked with such timeout, we just go forward.
*
* Every time a client blocks with a short timeout, we add the client in
* the tree. In beforeSleep() we call clientsHandleShortTimeout() to run
* the tree and unblock the clients.
*
* Design hint: why we block only clients with short timeouts? For frugality:
* Clients blocking for 30 seconds usually don't need to be unblocked
* precisely, and anyway for the nature of Redis to *guarantee* unblock time
* precision is hard, so we can avoid putting a large number of clients in
* the radix tree without a good reason. This idea also has a role in memory
* usage as well given that we don't do cleanup, the shorter a client timeout,
* the less time it will stay in the radix tree. */
#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
/* Given client ID and timeout, write the resulting radix tree key in buf. */
void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, uint64_t id) {
timeout = htonu64(timeout);
memcpy(buf,&timeout,sizeof(timeout));
memcpy(buf+8,&id,sizeof(id));
}
/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
* the timeout into *toptr and the client ID into *idptr. */
void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, uint64_t *idptr) {
memcpy(toptr,buf,sizeof(*toptr));
*toptr = ntohu64(*toptr);
memcpy(idptr,buf+8,sizeof(*idptr));
}
/* Add the specified client id / timeout as a key in the radix tree we use
* to handle short timeouts. The client is not added to the list if its
* timeout is longer than CLIENT_SHORT_TIMEOUT milliseconds. */
void addClientToShortTimeoutTable(client *c) {
if (c->bpop.timeout == 0 ||
c->bpop.timeout - mstime() > CLIENT_SHORT_TIMEOUT)
{
return;
}
uint64_t timeout = c->bpop.timeout;
uint64_t id = c->id;
unsigned char buf[CLIENT_ST_KEYLEN];
encodeTimeoutKey(buf,timeout,id);
raxTryInsert(server.clients_timeout_table,buf,sizeof(buf),NULL,NULL);
}
/* This function is called in beforeSleep() in order to unblock ASAP clients
* that are waiting in blocking operations with a short timeout set. */
void clientsHandleShortTimeout(void) {
if (raxSize(server.clients_timeout_table) == 0) return;
uint64_t now = mstime();
raxIterator ri;
raxStart(&ri,server.clients_timeout_table);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
uint64_t id, timeout;
decodeTimeoutKey(ri.key,&timeout,&id);
if (timeout >= now) break; /* All the timeouts are in the future. */
client *c = lookupClientByID(id);
if (c) checkBlockedClientTimeout(c,now);
raxRemove(server.clients_timeout_table,ri.key,ri.key_len,NULL);
raxSeek(&ri,"^",NULL,0);
}
}
/* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */
@@ -1718,6 +1597,28 @@ int clientsCronTrackExpansiveClients(client *c) {
return 0; /* This function never terminates the client. */
}
/* Iterating all the clients in getMemoryOverheadData() is too slow and
* in turn would make the INFO command too slow. So we perform this
* computation incrementally and track the (not instantaneous but updated
* to the second) total memory used by clients using clinetsCron() in
* a more incremental way (depending on server.hz). */
int clientsCronTrackClientsMemUsage(client *c) {
size_t mem = 0;
int type = getClientType(c);
mem += getClientOutputBufferMemoryUsage(c);
mem += sdsAllocSize(c->querybuf);
mem += sizeof(client);
/* Now that we have the memory used by the client, remove the old
* value from the old categoty, and add it back. */
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
c->client_cron_last_memory_usage;
server.stat_clients_type_memory[type] += mem;
/* Remember what we added and where, to remove it next time. */
c->client_cron_last_memory_usage = mem;
c->client_cron_last_memory_type = type;
return 0;
}
/* 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) {
@@ -1747,9 +1648,6 @@ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
*/
#define CLIENTS_CRON_MIN_ITERATIONS 5
void clientsCron(void) {
/* Unblock short timeout clients ASAP. */
clientsHandleShortTimeout();
/* 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
@@ -1772,7 +1670,7 @@ void clientsCron(void) {
/* Rotate the list, take the current head, process.
* This way if the client must be removed from the list it's the
* first element and we don't incur into O(N) computation. */
listRotate(server.clients);
listRotateTailToHead(server.clients);
head = listFirst(server.clients);
c = listNodeValue(head);
/* The following functions do different service checks on the client.
@@ -1781,6 +1679,7 @@ void clientsCron(void) {
if (clientsCronHandleTimeout(c,now)) continue;
if (clientsCronResizeQueryBuffer(c)) continue;
if (clientsCronTrackExpansiveClients(c)) continue;
if (clientsCronTrackClientsMemUsage(c)) continue;
}
}
@@ -2154,6 +2053,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Stop the I/O threads if we don't have enough pending work. */
stopThreadedIOIfNeeded();
/* Resize tracking keys table if needed. This is also done at every
* command execution, but we want to be sure that if the last command
* executed changes the value via CONFIG SET, the server will perform
* the operation even if completely idle. */
if (server.tracking_clients) trackingLimitUsedSlots();
/* Start a scheduled BGSAVE if the corresponding flag is set. This is
* useful when we are forced to postpone a BGSAVE because an AOF
* rewrite is in progress.
@@ -2182,14 +2087,42 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
return 1000/server.hz;
}
extern int ProcessingEventsWhileBlocked;
/* This function gets called every time Redis is entering the
* main loop of the event driven library, that is, before to sleep
* for ready file descriptors. */
* for ready file descriptors.
*
* Note: This function is (currently) called from two functions:
* 1. aeMain - The main server loop
* 2. processEventsWhileBlocked - Process clients during RDB/AOF load
*
* If it was called from processEventsWhileBlocked we don't want
* to perform all actions (For example, we don't want to expire
* keys), but we do need to perform some actions.
*
* The most important is freeClientsInAsyncFreeQueue but we also
* call some other low-risk functions. */
void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
/* Just call a subset of vital functions in case we are re-entering
* the event loop from processEventsWhileBlocked(). Note that in this
* case we keep track of the number of events we are processing, since
* processEventsWhileBlocked() wants to stop ASAP if there are no longer
* events to handle. */
if (ProcessingEventsWhileBlocked) {
uint64_t processed = 0;
processed += handleClientsWithPendingReadsUsingThreads();
processed += tlsProcessPendingData();
processed += handleClientsWithPendingWrites();
processed += freeClientsInAsyncFreeQueue();
server.events_processed_while_blocked += processed;
return;
}
/* Handle precise timeouts of blocked clients. */
clientsHandleShortTimeout();
handleBlockedClientsTimeout();
/* We should handle pending reads clients ASAP after event loop. */
handleClientsWithPendingReadsUsingThreads();
@@ -2211,21 +2144,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
if (server.active_expire_enabled && server.masterhost == NULL)
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
/* Send all the slaves an ACK request if at least one client blocked
* during the previous event loop iteration. */
if (server.get_ack_from_slaves) {
robj *argv[3];
argv[0] = createStringObject("REPLCONF",8);
argv[1] = createStringObject("GETACK",6);
argv[2] = createStringObject("*",1); /* Not used argument. */
replicationFeedSlaves(server.slaves, server.slaveseldb, argv, 3);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
decrRefCount(argv[2]);
server.get_ack_from_slaves = 0;
}
/* Unblock all the clients blocked for synchronous replication
* in WAIT. */
if (listLength(server.clients_waiting_acks))
@@ -2239,6 +2157,24 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
if (listLength(server.unblocked_clients))
processUnblockedClients();
/* Send all the slaves an ACK request if at least one client blocked
* during the previous event loop iteration. Note that we do this after
* processUnblockedClients(), so if there are multiple pipelined WAITs
* and the just unblocked WAIT gets blocked again, we don't have to wait
* a server cron cycle in absence of other event loop events. See #6623. */
if (server.get_ack_from_slaves) {
robj *argv[3];
argv[0] = createStringObject("REPLCONF",8);
argv[1] = createStringObject("GETACK",6);
argv[2] = createStringObject("*",1); /* Not used argument. */
replicationFeedSlaves(server.slaves, server.slaveseldb, argv, 3);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
decrRefCount(argv[2]);
server.get_ack_from_slaves = 0;
}
/* Send the invalidation messages to clients participating to the
* client side caching protocol in broadcasting (BCAST) mode. */
trackingBroadcastInvalidationMessages();
@@ -2263,7 +2199,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
* the different events callbacks. */
void afterSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
if (moduleCount()) moduleAcquireGIL();
if (!ProcessingEventsWhileBlocked) {
if (moduleCount()) moduleAcquireGIL();
}
}
/* =========================== Server initialization ======================== */
@@ -2455,7 +2394,6 @@ void initServerConfig(void) {
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
server.master_repl_offset = 0;
server.master_repl_meaningful_offset = 0;
/* Replication partial resync backlog */
server.repl_backlog = NULL;
@@ -2793,6 +2731,7 @@ void resetServerStats(void) {
}
server.stat_net_input_bytes = 0;
server.stat_net_output_bytes = 0;
server.stat_unexpected_error_replies = 0;
server.aof_delayed_fsync = 0;
}
@@ -2828,6 +2767,7 @@ void initServer(void) {
server.clients_waiting_acks = listCreate();
server.get_ack_from_slaves = 0;
server.clients_paused = 0;
server.events_processed_while_blocked = 0;
server.system_memory_size = zmalloc_get_memory_size();
if (server.tls_port && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
@@ -2888,6 +2828,7 @@ void initServer(void) {
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
server.pubsub_patterns = listCreate();
server.pubsub_patterns_dict = dictCreate(&keylistDictType,NULL);
listSetFreeMethod(server.pubsub_patterns,freePubsubPattern);
listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern);
server.cronloops = 0;
@@ -2918,6 +2859,8 @@ void initServer(void) {
server.stat_rdb_cow_bytes = 0;
server.stat_aof_cow_bytes = 0;
server.stat_module_cow_bytes = 0;
for (int j = 0; j < CLIENT_TYPE_COUNT; j++)
server.stat_clients_type_memory[j] = 0;
server.cron_malloc_stats.zmalloc_used = 0;
server.cron_malloc_stats.process_rss = 0;
server.cron_malloc_stats.allocator_allocated = 0;
@@ -2967,6 +2910,11 @@ void initServer(void) {
"blocked clients subsystem.");
}
/* Register before and after sleep handlers (note this needs to be done
* before loading persistence since it is used by processEventsWhileBlocked. */
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
/* Open the AOF file if needed. */
if (server.aof_state == AOF_ON) {
server.aof_fd = open(server.aof_filename,
@@ -3183,8 +3131,13 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
* + PROPAGATE_REPL (propagate into the replication link)
*
* This should not be used inside commands implementation. Use instead
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
* This should not be used inside commands implementation since it will not
* wrap the resulting commands in MULTI/EXEC. Use instead alsoPropagate(),
* preventCommandPropagation(), forceCommandPropagation().
*
* However for functions that need to (also) propagate out of the context of a
* command execution, for example when serving a blocked client, you
* want to use propagate().
*/
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags)
@@ -3293,8 +3246,8 @@ void call(client *c, int flags) {
server.fixed_time_expire++;
/* Sent the command to clients in MONITOR mode, only if the commands are
* not generated from reading an AOF. */
/* Send the command to clients in MONITOR mode if applicable.
* Administrative commands are considered too dangerous to be shown. */
if (listLength(server.monitors) &&
!server.loading &&
!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
@@ -3397,12 +3350,16 @@ void call(client *c, int flags) {
if (flags & CMD_CALL_PROPAGATE) {
int multi_emitted = 0;
/* Wrap the commands in server.also_propagate array,
* but don't wrap it if we are already in MULIT context,
* in case the nested MULIT/EXEC.
* but don't wrap it if we are already in MULTI context,
* in case the nested MULTI/EXEC.
*
* And if the array contains only one command, no need to
* wrap it, since the single command is atomic. */
if (server.also_propagate.numops > 1 && !(c->flags & CLIENT_MULTI)) {
if (server.also_propagate.numops > 1 &&
!(c->cmd->flags & CMD_MODULE) &&
!(c->flags & CLIENT_MULTI) &&
!(flags & CMD_CALL_NOWRAP))
{
execCommandPropagateMulti(c);
multi_emitted = 1;
}
@@ -3567,6 +3524,13 @@ int processCommand(client *c) {
addReply(c, shared.oomerr);
return C_OK;
}
/* Save out_of_memory result at script start, otherwise if we check OOM
* untill first write within script, memory used by lua stack and
* arguments might interfere. */
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
server.lua_oom = out_of_memory;
}
}
/* Make sure to use a reasonable amount of memory for client side
@@ -4101,11 +4065,13 @@ sds genRedisInfoString(const char *section) {
"client_recent_max_input_buffer:%zu\r\n"
"client_recent_max_output_buffer:%zu\r\n"
"blocked_clients:%d\r\n"
"tracking_clients:%d\r\n",
"tracking_clients:%d\r\n"
"clients_in_timeout_table:%llu\r\n",
listLength(server.clients)-listLength(server.slaves),
maxin, maxout,
server.blocked_clients,
server.tracking_clients);
server.tracking_clients,
(unsigned long long) raxSize(server.clients_timeout_table));
}
/* Memory */
@@ -4120,7 +4086,7 @@ sds genRedisInfoString(const char *section) {
size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size;
const char *evict_policy = evictPolicyToString();
long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024;
long long memory_lua = server.lua ? (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024 : 0;
struct redisMemOverhead *mh = getMemoryOverheadData();
/* Peak memory is updated from time to time by serverCron() so it
@@ -4355,7 +4321,9 @@ sds genRedisInfoString(const char *section) {
"active_defrag_key_hits:%lld\r\n"
"active_defrag_key_misses:%lld\r\n"
"tracking_total_keys:%lld\r\n"
"tracking_total_items:%lld\r\n",
"tracking_total_items:%lld\r\n"
"tracking_total_prefixes:%lld\r\n"
"unexpected_error_replies:%lld\r\n",
server.stat_numconnections,
server.stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND),
@@ -4384,7 +4352,9 @@ sds genRedisInfoString(const char *section) {
server.stat_active_defrag_key_hits,
server.stat_active_defrag_key_misses,
(unsigned long long) trackingGetTotalKeys(),
(unsigned long long) trackingGetTotalItems());
(unsigned long long) trackingGetTotalItems(),
(unsigned long long) trackingGetTotalPrefixes(),
server.stat_unexpected_error_replies);
}
/* Replication */
@@ -4500,7 +4470,6 @@ sds genRedisInfoString(const char *section) {
"master_replid:%s\r\n"
"master_replid2:%s\r\n"
"master_repl_offset:%lld\r\n"
"master_repl_meaningful_offset:%lld\r\n"
"second_repl_offset:%lld\r\n"
"repl_backlog_active:%d\r\n"
"repl_backlog_size:%lld\r\n"
@@ -4509,7 +4478,6 @@ sds genRedisInfoString(const char *section) {
server.replid,
server.replid2,
server.master_repl_offset,
server.master_repl_meaningful_offset,
server.second_replid_offset,
server.repl_backlog != NULL,
server.repl_backlog_size,
@@ -4887,7 +4855,6 @@ void loadDataFromDisk(void) {
{
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
server.master_repl_offset = rsi.repl_offset;
server.master_repl_meaningful_offset = rsi.repl_offset;
/* If we are a slave, create a cached master from this
* information, in order to allow partial resynchronizations
* with masters. */
@@ -4923,6 +4890,14 @@ void redisSetProcTitle(char *title) {
#endif
}
void redisSetCpuAffinity(const char *cpulist) {
#ifdef USE_SETCPUAFFINITY
setcpuaffinity(cpulist);
#else
UNUSED(cpulist);
#endif
}
/*
* Check whether systemd or upstart have been used to start redis.
*/
@@ -5026,6 +5001,7 @@ int main(int argc, char **argv) {
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
crc64_init();
uint8_t hashseed[16];
getRandomBytes(hashseed,sizeof(hashseed));
@@ -5191,8 +5167,7 @@ int main(int argc, char **argv) {
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
redisSetCpuAffinity(server.server_cpulist);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
+52 -20
View File
@@ -252,6 +252,10 @@ typedef long long ustime_t; /* microsecond time type. */
#define CLIENT_TRACKING_OPTOUT (1ULL<<35) /* Tracking in opt-out mode. */
#define CLIENT_TRACKING_CACHING (1ULL<<36) /* CACHING yes/no was given,
depending on optin/optout mode. */
#define CLIENT_TRACKING_NOLOOP (1ULL<<37) /* Don't send invalidation messages
about writes performed by myself.*/
#define CLIENT_IN_TO_TABLE (1ULL<<38) /* This client is in the timeout table. */
#define CLIENT_PROTOCOL_ERROR (1ULL<<39) /* Protocol error chatting with it. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
@@ -273,13 +277,11 @@ typedef long long ustime_t; /* microsecond time type. */
#define CLIENT_TYPE_SLAVE 1 /* Slaves. */
#define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
#define CLIENT_TYPE_MASTER 3 /* Master. */
#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
buffer configuration. Just the first
three: normal, slave, pubsub. */
/* Other client related defines. */
#define CLIENT_SHORT_TIMEOUT 2000 /* See clientsHandleShortTimeout(). */
/* Slave replication state. Used in server.repl_state for slaves to remember
* what to do next. */
#define REPL_STATE_NONE 0 /* No active replication */
@@ -397,6 +399,8 @@ typedef long long ustime_t; /* microsecond time type. */
#define CMD_CALL_PROPAGATE_REPL (1<<3)
#define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL)
#define CMD_CALL_FULL (CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_PROPAGATE)
#define CMD_CALL_NOWRAP (1<<4) /* Don't wrap also propagate array into
MULTI/EXEC: the caller will handle it. */
/* Command propagation flags, see propagate() function */
#define PROPAGATE_NONE 0
@@ -596,7 +600,9 @@ typedef struct RedisModuleDigest {
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
#define OBJ_SHARED_REFCOUNT INT_MAX
#define OBJ_SHARED_REFCOUNT INT_MAX /* Global object never destroyed. */
#define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */
#define OBJ_FIRST_SPECIAL_REFCOUNT OBJ_STATIC_REFCOUNT
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
@@ -617,7 +623,7 @@ char *getObjectTypeName(robj*);
* we'll update it when the structure is changed, to avoid bugs like
* bug #85 introduced exactly in this way. */
#define initStaticStringObject(_var,_ptr) do { \
_var.refcount = 1; \
_var.refcount = OBJ_STATIC_REFCOUNT; \
_var.type = OBJ_STRING; \
_var.encoding = OBJ_ENCODING_RAW; \
_var.ptr = _ptr; \
@@ -727,7 +733,7 @@ typedef struct readyList {
no AUTH is needed, and every
connection is immediately
authenticated. */
typedef struct user {
typedef struct {
sds name; /* The username as an SDS string. */
uint64_t flags; /* See USER_FLAG_* */
@@ -820,10 +826,10 @@ typedef struct client {
* when the authenticated user
* changes. */
void *auth_callback_privdata; /* Private data that is passed when the auth
* changed callback is executed. Opaque for
* changed callback is executed. Opaque for
* Redis Core. */
void *auth_module; /* The module that owns the callback, which is used
* to disconnect the client if the module is
* to disconnect the client if the module is
* unloaded for cleanup. Opaque for Redis Core.*/
/* If this client is in tracking mode and this field is non zero,
@@ -833,6 +839,13 @@ typedef struct client {
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
subscribed to in BCAST mode, in the
context of client side caching. */
/* In clientsCronTrackClientsMemUsage() we track the memory usage of
* each client and add it to the sum of all the clients of a given type,
* however we need to remember what was the old contribution of each
* client, and in which categoty the client was, in order to remove it
* before adding it the new value. */
uint64_t client_cron_last_memory_usage;
int client_cron_last_memory_type;
/* Response buffer */
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
@@ -1070,7 +1083,7 @@ struct redisServer {
list *clients_pending_read; /* Client has pending read socket buffers. */
list *slaves, *monitors; /* List of slaves and MONITORs */
client *current_client; /* Current client executing the command. */
rax *clients_timeout_table; /* Radix tree for clients with short timeout. */
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
rax *clients_index; /* Active clients dictionary by client ID. */
int clients_paused; /* True if clients are currently paused */
@@ -1083,6 +1096,7 @@ struct redisServer {
queries. Will still serve RESP2 queries. */
int io_threads_num; /* Number of IO threads to use. */
int io_threads_do_reads; /* Read and parse from IO threads? */
long long events_processed_while_blocked; /* processEventsWhileBlocked() */
/* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */
@@ -1129,6 +1143,8 @@ struct redisServer {
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
/* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */
struct {
@@ -1245,7 +1261,6 @@ struct redisServer {
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
long long master_repl_offset; /* My current replication offset */
long long master_repl_meaningful_offset; /* Offset minus latest PINGs. */
long long second_replid_offset; /* Accept offsets up to this for replid2. */
int slaveseldb; /* Last SELECTed DB in replication output */
int repl_ping_slave_period; /* Master pings the slave every N seconds */
@@ -1348,6 +1363,7 @@ struct redisServer {
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */
dict *pubsub_patterns_dict; /* A dict of pubsub_patterns */
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of NOTIFY_... flags. */
/* Cluster */
@@ -1390,10 +1406,12 @@ struct redisServer {
execution. */
int lua_kill; /* Kill the script if true. */
int lua_always_replicate_commands; /* Default replication type. */
int lua_oom; /* OOM detected when script start? */
/* Lazy free */
int lazyfree_lazy_eviction;
int lazyfree_lazy_expire;
int lazyfree_lazy_server_del;
int lazyfree_lazy_user_del;
/* Latency monitor */
long long latency_monitor_threshold;
dict *latency_events;
@@ -1416,6 +1434,11 @@ struct redisServer {
int tls_replication;
int tls_auth_clients;
redisTLSContextConfig tls_ctx_config;
/* cpu affinity */
char *server_cpulist; /* cpu affinity list of redis server main/io thread. */
char *bio_cpulist; /* cpu affinity list of bio thread. */
char *aof_rewrite_cpulist; /* cpu affinity list of aof rewrite process. */
char *bgsave_cpulist; /* cpu affinity list of bgsave process. */
};
typedef struct pubsubPattern {
@@ -1568,6 +1591,7 @@ void exitFromChild(int retcode);
size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title);
int redisCommunicateSystemd(const char *sd_notify_msg);
void redisSetCpuAffinity(const char *cpulist);
/* networking.c -- Networking and Client related operations */
client *createClient(connection *conn);
@@ -1583,7 +1607,6 @@ void setDeferredSetLen(client *c, void *node, long length);
void setDeferredAttributeLen(client *c, void *node, long length);
void setDeferredPushLen(client *c, void *node, long length);
void processInputBuffer(client *c);
void processInputBufferAndReplicate(client *c);
void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
@@ -1630,7 +1653,7 @@ void rewriteClientCommandVector(client *c, int argc, ...);
void rewriteClientCommandArgument(client *c, int i, robj *newval);
void replaceClientCommandVector(client *c, int argc, robj **argv);
unsigned long getClientOutputBufferMemoryUsage(client *c);
void freeClientsInAsyncFreeQueue(void);
int freeClientsInAsyncFreeQueue(void);
void asyncCloseClientOnOutputBufferLimitReached(client *c);
int getClientType(client *c);
int getClientTypeByName(char *name);
@@ -1640,7 +1663,7 @@ void disconnectSlaves(void);
int listenToPort(int port, int *fds, int *count);
void pauseClients(mstime_t duration);
int clientsArePaused(void);
int processEventsWhileBlocked(void);
void processEventsWhileBlocked(void);
int handleClientsWithPendingWrites(void);
int handleClientsWithPendingWritesUsingThreads(void);
int handleClientsWithPendingReadsUsingThreads(void);
@@ -1668,11 +1691,12 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
void disableTracking(client *c);
void trackingRememberKeys(client *c);
void trackingInvalidateKey(robj *keyobj);
void trackingInvalidateKey(client *c, robj *keyobj);
void trackingInvalidateKeysOnFlush(int dbid);
void trackingLimitUsedSlots(void);
uint64_t trackingGetTotalItems(void);
uint64_t trackingGetTotalKeys(void);
uint64_t trackingGetTotalPrefixes(void);
void trackingBroadcastInvalidationMessages(void);
/* List data type */
@@ -2054,9 +2078,10 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
#define LOOKUP_NONE 0
#define LOOKUP_NOTOUCH (1<<0)
void dbAdd(redisDb *db, robj *key, robj *val);
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
void dbOverwrite(redisDb *db, robj *key, robj *val);
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl);
void setKey(redisDb *db, robj *key, robj *val);
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal);
void setKey(client *c, redisDb *db, robj *key, robj *val);
int dbExists(redisDb *db, robj *key);
robj *dbRandomKey(redisDb *db);
int dbSyncDelete(redisDb *db, robj *key);
@@ -2072,7 +2097,7 @@ void flushAllDataAndResetRDB(int flags);
long long dbTotalServerKeyCount();
int selectDb(client *c, int id);
void signalModifiedKey(redisDb *db, robj *key);
void signalModifiedKey(client *c, redisDb *db, robj *key);
void signalFlushedDb(int dbid);
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
unsigned int countKeysInSlot(unsigned int hashslot);
@@ -2080,8 +2105,8 @@ unsigned int delKeysInSlot(unsigned int hashslot);
int verifyClusterConfigWithData(void);
void scanGenericCommand(client *c, robj *o, unsigned long cursor);
int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor);
void slotToKeyAdd(robj *key);
void slotToKeyDel(robj *key);
void slotToKeyAdd(sds key);
void slotToKeyDel(sds key);
void slotToKeyFlush(void);
int dbAsyncDelete(redisDb *db, robj *key);
void emptyDbAsync(redisDb *db);
@@ -2099,6 +2124,7 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
/* Cluster */
void clusterInit(void);
@@ -2140,7 +2166,12 @@ void disconnectAllBlockedClients(void);
void handleClientsBlockedOnKeys(void);
void signalKeyAsReady(redisDb *db, robj *key);
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids);
void addClientToShortTimeoutTable(client *c);
/* timeout.c -- Blocked clients timeout and connections timeout. */
void addClientToTimeoutTable(client *c);
void removeClientFromTimeoutTable(client *c);
void handleBlockedClientsTimeout(void);
int clientsCronHandleTimeout(client *c, mstime_t now_ms);
/* expire.c -- Handling of expired keys */
void activeExpireCycle(int type);
@@ -2365,6 +2396,7 @@ void xdelCommand(client *c);
void xtrimCommand(client *c);
void lolwutCommand(client *c);
void aclCommand(client *c);
void stralgoCommand(client *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+148
View File
@@ -0,0 +1,148 @@
/* ==========================================================================
* setcpuaffinity.c - Linux/BSD setcpuaffinity.
* --------------------------------------------------------------------------
* Copyright (C) 2020 zhenwei pi
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef __linux__
#include <sched.h>
#endif
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/cpuset.h>
#endif
#ifdef __NetBSD__
#include <pthread.h>
#include <sched.h>
#endif
#include "config.h"
#ifdef USE_SETCPUAFFINITY
static const char *next_token(const char *q, int sep) {
if (q)
q = strchr(q, sep);
if (q)
q++;
return q;
}
static int next_num(const char *str, char **end, int *result) {
if (!str || *str == '\0' || !isdigit(*str))
return -1;
*result = strtoul(str, end, 10);
if (str == *end)
return -1;
return 0;
}
/* set current thread cpu affinity to cpu list, this function works like
* taskset command (actually cpulist parsing logic reference to util-linux).
* example of this function: "0,2,3", "0,2-3", "0-20:2". */
void setcpuaffinity(const char *cpulist) {
const char *p, *q;
char *end = NULL;
#ifdef __linux__
cpu_set_t cpuset;
#endif
#ifdef __FreeBSD__
cpuset_t cpuset;
#endif
#ifdef __NetBSD__
cpuset_t *cpuset;
#endif
if (!cpulist)
return;
#ifndef __NetBSD__
CPU_ZERO(&cpuset);
#else
cpuset = cpuset_create();
#endif
q = cpulist;
while (p = q, q = next_token(q, ','), p) {
int a, b, s;
const char *c1, *c2;
if (next_num(p, &end, &a) != 0)
return;
b = a;
s = 1;
p = end;
c1 = next_token(p, '-');
c2 = next_token(p, ',');
if (c1 != NULL && (c2 == NULL || c1 < c2)) {
if (next_num(c1, &end, &b) != 0)
return;
c1 = end && *end ? next_token(end, ':') : NULL;
if (c1 != NULL && (c2 == NULL || c1 < c2)) {
if (next_num(c1, &end, &s) != 0)
return;
if (s == 0)
return;
}
}
if ((a > b))
return;
while (a <= b) {
#ifndef __NetBSD__
CPU_SET(a, &cpuset);
#else
cpuset_set(a, cpuset);
#endif
a += s;
}
}
if (end && *end)
return;
#ifdef __linux__
sched_setaffinity(0, sizeof(cpuset), &cpuset);
#endif
#ifdef __FreeBSD__
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset), &cpuset);
#endif
#ifdef __NetBSD__
pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset);
cpuset_destroy(cpuset);
#endif
}
#endif /* USE_SETCPUAFFINITY */
+2 -2
View File
@@ -570,12 +570,12 @@ void sortCommand(client *c) {
}
}
if (outputlen) {
setKey(c->db,storekey,sobj);
setKey(c,c->db,storekey,sobj);
notifyKeyspaceEvent(NOTIFY_LIST,"sortstore",storekey,
c->db->id);
server.dirty += outputlen;
} else if (dbDelete(c->db,storekey)) {
signalModifiedKey(c->db,storekey);
signalModifiedKey(c,c->db,storekey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
server.dirty++;
}
+6 -1
View File
@@ -96,6 +96,11 @@ typedef struct streamPropInfo {
/* Prototypes of exported APIs. */
struct client;
/* Flags for streamLookupConsumer */
#define SLC_NONE 0
#define SLC_NOCREAT (1<<0) /* Do not create the consumer if it doesn't exist */
#define SLC_NOREFRESH (1<<1) /* Do not update consumer's seen-time */
stream *streamNew(void);
void freeStream(stream *s);
unsigned long streamLength(const robj *subject);
@@ -105,7 +110,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen);
void streamIteratorStop(streamIterator *si);
streamCG *streamLookupCG(stream *s, sds groupname);
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags);
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
streamNACK *streamCreateNACK(streamConsumer *consumer);
void streamDecodeID(void *buf, streamID *id);
+5 -5
View File
@@ -521,7 +521,7 @@ void hsetnxCommand(client *c) {
} else {
hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
addReply(c, shared.cone);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
@@ -551,7 +551,7 @@ void hsetCommand(client *c) {
/* HMSET */
addReply(c, shared.ok);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
@@ -586,7 +586,7 @@ void hincrbyCommand(client *c) {
new = sdsfromlonglong(value);
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
addReplyLongLong(c,value);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
server.dirty++;
}
@@ -625,7 +625,7 @@ void hincrbyfloatCommand(client *c) {
new = sdsnewlen(buf,len);
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
addReplyBulkCBuffer(c,buf,len);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
server.dirty++;
@@ -721,7 +721,7 @@ void hdelCommand(client *c) {
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
+10 -10
View File
@@ -217,7 +217,7 @@ void pushGenericCommand(client *c, int where) {
if (pushed) {
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
@@ -247,7 +247,7 @@ void pushxGenericCommand(client *c, int where) {
if (pushed) {
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
@@ -292,7 +292,7 @@ void linsertCommand(client *c) {
listTypeReleaseIterator(iter);
if (inserted) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
server.dirty++;
@@ -355,7 +355,7 @@ void lsetCommand(client *c) {
addReply(c,shared.outofrangeerr);
} else {
addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,"lset",c->argv[1],c->db->id);
server.dirty++;
}
@@ -382,7 +382,7 @@ void popGenericCommand(client *c, int where) {
c->argv[1],c->db->id);
dbDelete(c->db,c->argv[1]);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
}
}
@@ -482,7 +482,7 @@ void ltrimCommand(client *c) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.ok);
}
@@ -519,7 +519,7 @@ void lremCommand(client *c) {
listTypeReleaseIterator(li);
if (removed) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
}
@@ -555,7 +555,7 @@ void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) {
server.list_compress_depth);
dbAdd(c->db,dstkey,dstobj);
}
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
listTypePush(dstobj,value,LIST_HEAD);
notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id);
/* Always send the pushed value to the client. */
@@ -593,7 +593,7 @@ void rpoplpushCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
touchedkey,c->db->id);
}
signalModifiedKey(c->db,touchedkey);
signalModifiedKey(c,c->db,touchedkey);
decrRefCount(touchedkey);
server.dirty++;
if (c->cmd->proc == brpoplpushCommand) {
@@ -708,7 +708,7 @@ void blockingPopGenericCommand(client *c, int where) {
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
c->argv[j],c->db->id);
}
signalModifiedKey(c->db,c->argv[j]);
signalModifiedKey(c,c->db,c->argv[j]);
server.dirty++;
/* Replicate it as an [LR]POP instead of B[LR]POP. */
+10 -10
View File
@@ -280,7 +280,7 @@ void saddCommand(client *c) {
if (setTypeAdd(set,c->argv[j]->ptr)) added++;
}
if (added) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
server.dirty += added;
@@ -305,7 +305,7 @@ void sremCommand(client *c) {
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
@@ -358,8 +358,8 @@ void smoveCommand(client *c) {
dbAdd(c->db,c->argv[2],dstset);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]);
signalModifiedKey(c,c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[2]);
server.dirty++;
/* An extra key has changed when ele was successfully added to dstset */
@@ -444,7 +444,7 @@ void spopWithCountCommand(client *c) {
/* Propagate this command as an DEL operation */
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
return;
}
@@ -546,7 +546,7 @@ void spopWithCountCommand(client *c) {
* the alsoPropagate() API. */
decrRefCount(propargv[0]);
preventCommandPropagation(c);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
}
@@ -599,7 +599,7 @@ void spopCommand(client *c) {
}
/* Set has been modified */
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty++;
}
@@ -808,7 +808,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
zfree(sets);
if (dstkey) {
if (dbDelete(c->db,dstkey)) {
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
server.dirty++;
}
addReply(c,shared.czero);
@@ -908,7 +908,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
server.dirty++;
} else {
setDeferredSetLen(c,replylen,cardinality);
@@ -1083,7 +1083,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
server.dirty++;
}
zfree(sets);
+258 -59
View File
@@ -848,7 +848,12 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
argv[11] = createStringObject("JUSTID",6);
argv[12] = createStringObject("LASTID",6);
argv[13] = createObjectFromStreamID(&group->last_id);
alsoPropagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
/* We use progagate() because this code path is not always called from
* the command execution context. Moreover this will just alter the
* consumer group state, and we don't need MULTI/EXEC wrapping because
* there is no message state cross-message atomicity required. */
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[3]);
decrRefCount(argv[4]);
@@ -875,7 +880,12 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
argv[2] = key;
argv[3] = groupname;
argv[4] = createObjectFromStreamID(&group->last_id);
alsoPropagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
/* We use progagate() because this code path is not always called from
* the command execution context. Moreover this will just alter the
* consumer group state, and we don't need MULTI/EXEC wrapping because
* there is no message state cross-message atomicity required. */
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
decrRefCount(argv[4]);
@@ -936,6 +946,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
int64_t numfields;
streamID id;
int propagate_last_id = 0;
int noack = flags & STREAM_RWR_NOACK;
/* If the client is asking for some history, we serve it using a
* different function, so that we return entries *solely* from its
@@ -954,7 +965,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
/* Update the group last_id if needed. */
if (group && streamCompareID(&id,&group->last_id) > 0) {
group->last_id = id;
propagate_last_id = 1;
/* Group last ID should be propagated only if NOACK was
* specified, otherwise the last id will be included
* in the propagation of XCLAIM itself. */
if (noack) propagate_last_id = 1;
}
/* Emit a two elements array for each item. The first is
@@ -962,7 +976,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
addReplyArrayLen(c,2);
addReplyStreamID(c,&id);
addReplyMapLen(c,numfields);
addReplyArrayLen(c,numfields*2);
/* Emit the field-value pairs. */
while(numfields--) {
@@ -982,7 +996,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
* XGROUP SETID command. So if we find that there is already
* a NACK for the entry, we need to associate it to the new
* consumer. */
if (group && !(flags & STREAM_RWR_NOACK)) {
if (group && !noack) {
unsigned char buf[sizeof(streamID)];
streamEncodeID(buf,&id);
@@ -1019,14 +1033,15 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
streamPropagateXCLAIM(c,spi->keyname,group,spi->groupname,idarg,nack);
decrRefCount(idarg);
}
} else {
if (propagate_last_id)
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
}
arraylen++;
if (count && count == arraylen) break;
}
if (spi && propagate_last_id)
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
streamIteratorStop(&si);
if (arraylen_ptr) setDeferredArrayLen(c,arraylen_ptr,arraylen);
return arraylen;
@@ -1261,7 +1276,7 @@ void xaddCommand(client *c) {
}
addReplyStreamID(c,&id);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STREAM,"xadd",c->argv[1],c->db->id);
server.dirty++;
@@ -1373,6 +1388,11 @@ void xreadCommand(client *c) {
int moreargs = c->argc-i-1;
char *o = c->argv[i]->ptr;
if (!strcasecmp(o,"BLOCK") && moreargs) {
if (c->flags & CLIENT_LUA) {
/* There is no sense to use BLOCK option within LUA */
addReplyErrorFormat(c, "%s command is not allowed with BLOCK option from scripts", (char *)c->argv[0]->ptr);
return;
}
i++;
if (getTimeoutFromObjectOrReply(c,c->argv[i],&timeout,
UNIT_MILLISECONDS) != C_OK) return;
@@ -1550,7 +1570,8 @@ void xreadCommand(client *c) {
addReplyBulk(c,c->argv[streams_arg+i]);
streamConsumer *consumer = NULL;
if (groups) consumer = streamLookupConsumer(groups[i],
consumername->ptr,1);
consumername->ptr,
SLC_NONE);
streamPropInfo spi = {c->argv[i+streams_arg],groupname};
int flags = 0;
if (noack) flags |= STREAM_RWR_NOACK;
@@ -1686,7 +1707,9 @@ streamCG *streamLookupCG(stream *s, sds groupname) {
* consumer does not exist it is automatically created as a side effect
* of calling this function, otherwise its last seen time is updated and
* the existing consumer reference returned. */
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags) {
int create = !(flags & SLC_NOCREAT);
int refresh = !(flags & SLC_NOREFRESH);
streamConsumer *consumer = raxFind(cg->consumers,(unsigned char*)name,
sdslen(name));
if (consumer == raxNotFound) {
@@ -1697,7 +1720,7 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
raxInsert(cg->consumers,(unsigned char*)name,sdslen(name),
consumer,NULL);
}
consumer->seen_time = mstime();
if (refresh) consumer->seen_time = mstime();
return consumer;
}
@@ -1705,7 +1728,8 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
* may have pending messages: they are removed from the PEL, and the number
* of pending messages "lost" is returned. */
uint64_t streamDelConsumer(streamCG *cg, sds name) {
streamConsumer *consumer = streamLookupConsumer(cg,name,0);
streamConsumer *consumer =
streamLookupConsumer(cg,name,SLC_NOCREAT|SLC_NOREFRESH);
if (consumer == NULL) return 0;
uint64_t retval = raxSize(consumer->pel);
@@ -1921,11 +1945,21 @@ void xackCommand(client *c) {
return;
}
/* Start parsing the IDs, so that we abort ASAP if there is a syntax
* error: the return value of this command cannot be an error in case
* the client successfully acknowledged some messages, so it should be
* executed in a "all or nothing" fashion. */
for (int j = 3; j < c->argc; j++) {
streamID id;
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
}
int acknowledged = 0;
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 (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
serverPanic("StreamID invalid after check. Should not be possible.");
streamEncodeID(buf,&id);
/* Lookup the ID in the group PEL: it will have a reference to the
@@ -2038,15 +2072,18 @@ void xpendingCommand(client *c) {
}
/* XPENDING <key> <group> <start> <stop> <count> [<consumer>] variant. */
else {
streamConsumer *consumer = consumername ?
streamLookupConsumer(group,consumername->ptr,0):
NULL;
streamConsumer *consumer = NULL;
if (consumername) {
consumer = streamLookupConsumer(group,
consumername->ptr,
SLC_NOCREAT|SLC_NOREFRESH);
/* If a consumer name was mentioned but it does not exist, we can
* just return an empty array. */
if (consumername && consumer == NULL) {
addReplyArrayLen(c,0);
return;
/* If a consumer name was mentioned but it does not exist, we can
* just return an empty array. */
if (consumer == NULL) {
addReplyArrayLen(c,0);
return;
}
}
rax *pel = consumer ? consumer->pel : group->pel;
@@ -2308,7 +2345,7 @@ void xclaimCommand(client *c) {
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
/* Update the consumer and idle time. */
if (consumer == NULL)
consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
consumer = streamLookupConsumer(group,c->argv[3]->ptr,SLC_NONE);
nack->consumer = consumer;
nack->delivery_time = deliverytime;
/* Set the delivery attempts counter if given, otherwise
@@ -2374,7 +2411,7 @@ void xdelCommand(client *c) {
/* Propagate the write if needed. */
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
server.dirty += deleted;
}
@@ -2451,7 +2488,7 @@ void xtrimCommand(client *c) {
/* Propagate the write if needed. */
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
server.dirty += deleted;
if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
@@ -2459,16 +2496,204 @@ void xtrimCommand(client *c) {
addReplyLongLong(c,deleted);
}
/* Helper function for xinfoCommand.
* Handles the variants of XINFO STREAM */
void xinfoReplyWithStreamInfo(client *c, stream *s) {
int full = 1;
long long count = 10; /* Default COUNT is 10 so we don't block the server */
robj **optv = c->argv + 3; /* Options start after XINFO STREAM <key> */
int optc = c->argc - 3;
/* Parse options. */
if (optc == 0) {
full = 0;
} else {
/* Valid options are [FULL] or [FULL COUNT <count>] */
if (optc != 1 && optc != 3) {
addReplySubcommandSyntaxError(c);
return;
}
/* First option must be "FULL" */
if (strcasecmp(optv[0]->ptr,"full")) {
addReplySubcommandSyntaxError(c);
return;
}
if (optc == 3) {
/* First option must be "FULL" */
if (strcasecmp(optv[1]->ptr,"count")) {
addReplySubcommandSyntaxError(c);
return;
}
if (getLongLongFromObjectOrReply(c,optv[2],&count,NULL) == C_ERR)
return;
if (count < 0) count = 10;
}
}
addReplyMapLen(c,full ? 6 : 7);
addReplyBulkCString(c,"length");
addReplyLongLong(c,s->length);
addReplyBulkCString(c,"radix-tree-keys");
addReplyLongLong(c,raxSize(s->rax));
addReplyBulkCString(c,"radix-tree-nodes");
addReplyLongLong(c,s->rax->numnodes);
addReplyBulkCString(c,"last-generated-id");
addReplyStreamID(c,&s->last_id);
if (!full) {
/* XINFO STREAM <key> */
addReplyBulkCString(c,"groups");
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
/* To emit the first/last entry we use streamReplyWithRange(). */
int emitted;
streamID start, end;
start.ms = start.seq = 0;
end.ms = end.seq = UINT64_MAX;
addReplyBulkCString(c,"first-entry");
emitted = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!emitted) addReplyNull(c);
addReplyBulkCString(c,"last-entry");
emitted = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!emitted) addReplyNull(c);
} else {
/* XINFO STREAM <key> FULL [COUNT <count>] */
/* Stream entries */
addReplyBulkCString(c,"entries");
streamReplyWithRange(c,s,NULL,NULL,count,0,NULL,NULL,0,NULL);
/* Consumer groups */
addReplyBulkCString(c,"groups");
if (s->cgroups == NULL) {
addReplyArrayLen(c,0);
} else {
addReplyArrayLen(c,raxSize(s->cgroups));
raxIterator ri_cgroups;
raxStart(&ri_cgroups,s->cgroups);
raxSeek(&ri_cgroups,"^",NULL,0);
while(raxNext(&ri_cgroups)) {
streamCG *cg = ri_cgroups.data;
addReplyMapLen(c,5);
/* Name */
addReplyBulkCString(c,"name");
addReplyBulkCBuffer(c,ri_cgroups.key,ri_cgroups.key_len);
/* Last delivered ID */
addReplyBulkCString(c,"last-delivered-id");
addReplyStreamID(c,&cg->last_id);
/* Group PEL count */
addReplyBulkCString(c,"pel-count");
addReplyLongLong(c,raxSize(cg->pel));
/* Group PEL */
addReplyBulkCString(c,"pending");
long long arraylen_cg_pel = 0;
void *arrayptr_cg_pel = addReplyDeferredLen(c);
raxIterator ri_cg_pel;
raxStart(&ri_cg_pel,cg->pel);
raxSeek(&ri_cg_pel,"^",NULL,0);
while(raxNext(&ri_cg_pel) && (!count || arraylen_cg_pel < count)) {
streamNACK *nack = ri_cg_pel.data;
addReplyArrayLen(c,4);
/* Entry ID. */
streamID id;
streamDecodeID(ri_cg_pel.key,&id);
addReplyStreamID(c,&id);
/* Consumer name. */
addReplyBulkCBuffer(c,nack->consumer->name,
sdslen(nack->consumer->name));
/* Last delivery. */
addReplyLongLong(c,nack->delivery_time);
/* Number of deliveries. */
addReplyLongLong(c,nack->delivery_count);
arraylen_cg_pel++;
}
setDeferredArrayLen(c,arrayptr_cg_pel,arraylen_cg_pel);
raxStop(&ri_cg_pel);
/* Consumers */
addReplyBulkCString(c,"consumers");
addReplyArrayLen(c,raxSize(cg->consumers));
raxIterator ri_consumers;
raxStart(&ri_consumers,cg->consumers);
raxSeek(&ri_consumers,"^",NULL,0);
while(raxNext(&ri_consumers)) {
streamConsumer *consumer = ri_consumers.data;
addReplyMapLen(c,4);
/* Consumer name */
addReplyBulkCString(c,"name");
addReplyBulkCBuffer(c,consumer->name,sdslen(consumer->name));
/* Seen-time */
addReplyBulkCString(c,"seen-time");
addReplyLongLong(c,consumer->seen_time);
/* Consumer PEL count */
addReplyBulkCString(c,"pel-count");
addReplyLongLong(c,raxSize(consumer->pel));
/* Consumer PEL */
addReplyBulkCString(c,"pending");
long long arraylen_cpel = 0;
void *arrayptr_cpel = addReplyDeferredLen(c);
raxIterator ri_cpel;
raxStart(&ri_cpel,consumer->pel);
raxSeek(&ri_cpel,"^",NULL,0);
while(raxNext(&ri_cpel) && (!count || arraylen_cpel < count)) {
streamNACK *nack = ri_cpel.data;
addReplyArrayLen(c,3);
/* Entry ID. */
streamID id;
streamDecodeID(ri_cpel.key,&id);
addReplyStreamID(c,&id);
/* Last delivery. */
addReplyLongLong(c,nack->delivery_time);
/* Number of deliveries. */
addReplyLongLong(c,nack->delivery_count);
arraylen_cpel++;
}
setDeferredArrayLen(c,arrayptr_cpel,arraylen_cpel);
raxStop(&ri_cpel);
}
raxStop(&ri_consumers);
}
raxStop(&ri_cgroups);
}
}
}
/* XINFO CONSUMERS <key> <group>
* XINFO GROUPS <key>
* XINFO STREAM <key>
* XINFO STREAM <key> [FULL [COUNT <count>]]
* XINFO HELP. */
void xinfoCommand(client *c) {
const char *help[] = {
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
"GROUPS <key> -- Show the stream consumer groups.",
"STREAM <key> -- Show information about the stream.",
"HELP -- Print this help.",
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
"GROUPS <key> -- Show the stream consumer groups.",
"STREAM <key> [FULL [COUNT <count>]] -- Show information about the stream.",
" FULL will return the full state of the stream,",
" including all entries, groups, consumers and PELs.",
" It's possible to show only the first stream/PEL entries",
" by using the COUNT modifier (Default is 10)",
"HELP -- Print this help.",
NULL
};
stream *s = NULL;
@@ -2548,36 +2773,10 @@ NULL
addReplyStreamID(c,&cg->last_id);
}
raxStop(&ri);
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
/* XINFO STREAM <key> (or the alias XINFO <key>). */
addReplyMapLen(c,7);
addReplyBulkCString(c,"length");
addReplyLongLong(c,s->length);
addReplyBulkCString(c,"radix-tree-keys");
addReplyLongLong(c,raxSize(s->rax));
addReplyBulkCString(c,"radix-tree-nodes");
addReplyLongLong(c,s->rax->numnodes);
addReplyBulkCString(c,"groups");
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
addReplyBulkCString(c,"last-generated-id");
addReplyStreamID(c,&s->last_id);
/* To emit the first/last entry we us the streamReplyWithRange()
* API. */
int count;
streamID start, end;
start.ms = start.seq = 0;
end.ms = end.seq = UINT64_MAX;
addReplyBulkCString(c,"first-entry");
count = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!count) addReplyNull(c);
addReplyBulkCString(c,"last-entry");
count = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!count) addReplyNull(c);
} else if (!strcasecmp(opt,"STREAM")) {
/* XINFO STREAM <key> [FULL [COUNT <count>]]. */
xinfoReplyWithStreamInfo(c,s);
} else {
addReplySubcommandSyntaxError(c);
}
}
+224 -7
View File
@@ -84,7 +84,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
return;
}
genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL);
genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
server.dirty++;
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
@@ -183,7 +183,7 @@ void getCommand(client *c) {
void getsetCommand(client *c) {
if (getGenericCommand(c) == C_ERR) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
setKey(c->db,c->argv[1],c->argv[2]);
setKey(c,c->db,c->argv[1],c->argv[2]);
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
server.dirty++;
}
@@ -240,7 +240,7 @@ void setrangeCommand(client *c) {
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,
"setrange",c->argv[1],c->db->id);
server.dirty++;
@@ -328,7 +328,7 @@ void msetGenericCommand(client *c, int nx) {
for (j = 1; j < c->argc; j += 2) {
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
setKey(c->db,c->argv[j],c->argv[j+1]);
setKey(c,c->db,c->argv[j],c->argv[j+1]);
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
}
server.dirty += (c->argc-1)/2;
@@ -373,7 +373,7 @@ void incrDecrCommand(client *c, long long incr) {
dbAdd(c->db,c->argv[1],new);
}
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReply(c,shared.colon);
@@ -423,7 +423,7 @@ void incrbyfloatCommand(client *c) {
dbOverwrite(c->db,c->argv[1],new);
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
server.dirty++;
addReplyBulk(c,new);
@@ -467,7 +467,7 @@ void appendCommand(client *c) {
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
server.dirty++;
addReplyLongLong(c,totlen);
@@ -479,3 +479,220 @@ void strlenCommand(client *c) {
checkType(c,o,OBJ_STRING)) return;
addReplyLongLong(c,stringObjectLen(o));
}
/* STRALGO -- Implement complex algorithms on strings.
*
* STRALGO <algorithm> ... arguments ... */
void stralgoLCS(client *c); /* This implements the LCS algorithm. */
void stralgoCommand(client *c) {
/* Select the algorithm. */
if (!strcasecmp(c->argv[1]->ptr,"lcs")) {
stralgoLCS(c);
} else {
addReply(c,shared.syntaxerr);
}
}
/* STRALGO <algo> [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN]
* STRINGS <string> <string> | KEYS <keya> <keyb>
*/
void stralgoLCS(client *c) {
uint32_t i, j;
long long minmatchlen = 0;
sds a = NULL, b = NULL;
int getlen = 0, getidx = 0, withmatchlen = 0;
robj *obja = NULL, *objb = NULL;
for (j = 2; j < (uint32_t)c->argc; j++) {
char *opt = c->argv[j]->ptr;
int moreargs = (c->argc-1) - j;
if (!strcasecmp(opt,"IDX")) {
getidx = 1;
} else if (!strcasecmp(opt,"LEN")) {
getlen = 1;
} else if (!strcasecmp(opt,"WITHMATCHLEN")) {
withmatchlen = 1;
} else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) {
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL)
!= C_OK) return;
if (minmatchlen < 0) minmatchlen = 0;
j++;
} else if (!strcasecmp(opt,"STRINGS") && moreargs > 1) {
if (a != NULL) {
addReplyError(c,"Either use STRINGS or KEYS");
return;
}
a = c->argv[j+1]->ptr;
b = c->argv[j+2]->ptr;
j += 2;
} else if (!strcasecmp(opt,"KEYS") && moreargs > 1) {
if (a != NULL) {
addReplyError(c,"Either use STRINGS or KEYS");
return;
}
obja = lookupKeyRead(c->db,c->argv[j+1]);
objb = lookupKeyRead(c->db,c->argv[j+2]);
obja = obja ? getDecodedObject(obja) : createStringObject("",0);
objb = objb ? getDecodedObject(objb) : createStringObject("",0);
a = obja->ptr;
b = objb->ptr;
j += 2;
} else {
addReply(c,shared.syntaxerr);
return;
}
}
/* Complain if the user passed ambiguous parameters. */
if (a == NULL) {
addReplyError(c,"Please specify two strings: "
"STRINGS or KEYS options are mandatory");
return;
} else if (getlen && getidx) {
addReplyError(c,
"If you want both the length and indexes, please "
"just use IDX.");
return;
}
/* Compute the LCS using the vanilla dynamic programming technique of
* building a table of LCS(x,y) substrings. */
uint32_t alen = sdslen(a);
uint32_t blen = sdslen(b);
/* Setup an uint32_t array to store at LCS[i,j] the length of the
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
* we index it as LCS[j+(blen+1)*j] */
uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
/* Start building the LCS table. */
for (uint32_t i = 0; i <= alen; i++) {
for (uint32_t j = 0; j <= blen; j++) {
if (i == 0 || j == 0) {
/* If one substring has length of zero, the
* LCS length is zero. */
LCS(i,j) = 0;
} else if (a[i-1] == b[j-1]) {
/* The len LCS (and the LCS itself) of two
* sequences with the same final character, is the
* LCS of the two sequences without the last char
* plus that last char. */
LCS(i,j) = LCS(i-1,j-1)+1;
} else {
/* If the last character is different, take the longest
* between the LCS of the first string and the second
* minus the last char, and the reverse. */
uint32_t lcs1 = LCS(i-1,j);
uint32_t lcs2 = LCS(i,j-1);
LCS(i,j) = lcs1 > lcs2 ? lcs1 : lcs2;
}
}
}
/* Store the actual LCS string in "result" if needed. We create
* it backward, but the length is already known, we store it into idx. */
uint32_t idx = LCS(alen,blen);
sds result = NULL; /* Resulting LCS string. */
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */
uint32_t arange_start = alen, /* alen signals that values are not set. */
arange_end = 0,
brange_start = 0,
brange_end = 0;
/* Do we need to compute the actual LCS string? Allocate it in that case. */
int computelcs = getidx || !getlen;
if (computelcs) result = sdsnewlen(SDS_NOINIT,idx);
/* Start with a deferred array if we have to emit the ranges. */
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */
if (getidx) {
addReplyMapLen(c,2);
addReplyBulkCString(c,"matches");
arraylenptr = addReplyDeferredLen(c);
}
i = alen, j = blen;
while (computelcs && i > 0 && j > 0) {
int emit_range = 0;
if (a[i-1] == b[j-1]) {
/* If there is a match, store the character and reduce
* the indexes to look for a new match. */
result[idx-1] = a[i-1];
/* Track the current range. */
if (arange_start == alen) {
arange_start = i-1;
arange_end = i-1;
brange_start = j-1;
brange_end = j-1;
} else {
/* Let's see if we can extend the range backward since
* it is contiguous. */
if (arange_start == i && brange_start == j) {
arange_start--;
brange_start--;
} else {
emit_range = 1;
}
}
/* Emit the range if we matched with the first byte of
* one of the two strings. We'll exit the loop ASAP. */
if (arange_start == 0 || brange_start == 0) emit_range = 1;
idx--; i--; j--;
} else {
/* Otherwise reduce i and j depending on the largest
* LCS between, to understand what direction we need to go. */
uint32_t lcs1 = LCS(i-1,j);
uint32_t lcs2 = LCS(i,j-1);
if (lcs1 > lcs2)
i--;
else
j--;
if (arange_start != alen) emit_range = 1;
}
/* Emit the current range if needed. */
uint32_t match_len = arange_end - arange_start + 1;
if (emit_range) {
if (minmatchlen == 0 || match_len >= minmatchlen) {
if (arraylenptr) {
addReplyArrayLen(c,2+withmatchlen);
addReplyArrayLen(c,2);
addReplyLongLong(c,arange_start);
addReplyLongLong(c,arange_end);
addReplyArrayLen(c,2);
addReplyLongLong(c,brange_start);
addReplyLongLong(c,brange_end);
if (withmatchlen) addReplyLongLong(c,match_len);
arraylen++;
}
}
arange_start = alen; /* Restart at the next match. */
}
}
/* Signal modified key, increment dirty, ... */
/* Reply depending on the given options. */
if (arraylenptr) {
addReplyBulkCString(c,"len");
addReplyLongLong(c,LCS(alen,blen));
setDeferredArrayLen(c,arraylenptr,arraylen);
} else if (getlen) {
addReplyLongLong(c,LCS(alen,blen));
} else {
addReplyBulkSds(c,result);
result = NULL;
}
/* Cleanup. */
if (obja) decrRefCount(obja);
if (objb) decrRefCount(objb);
sdsfree(result);
zfree(lcs);
return;
}
+11 -10
View File
@@ -186,7 +186,8 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
return x;
}
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
/* Internal function used by zslDelete, zslDeleteRangeByScore and
* zslDeleteRangeByRank. */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
for (i = 0; i < zsl->level; i++) {
@@ -1300,14 +1301,14 @@ int zsetScore(robj *zobj, sds member, double *score) {
* none could be set if we re-added an element using the same score it used
* to have, or in the case a zero increment is used).
*
* The function returns 0 on erorr, currently only when the increment
* The function returns 0 on error, currently only when the increment
* produces a NAN condition, or when the 'score' value is NAN since the
* start.
*
* The commad as a side effect of adding a new element may convert the sorted
* The command as a side effect of adding a new element may convert the sorted
* set internal encoding from ziplist to hashtable+skiplist.
*
* Memory managemnet of 'ele':
* Memory management of 'ele':
*
* The function does not take ownership of the 'ele' SDS string, but copies
* it if needed. */
@@ -1645,7 +1646,7 @@ reply_to_client:
cleanup:
zfree(scores);
if (added || updated) {
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_ZSET,
incr ? "zincr" : "zadd", key, c->db->id);
}
@@ -1680,7 +1681,7 @@ void zremCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_ZSET,"zrem",key,c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
@@ -1778,7 +1779,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
/* Step 4: Notifications and reply. */
if (deleted) {
char *event[3] = {"zremrangebyrank","zremrangebyscore","zremrangebylex"};
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_ZSET,event[rangetype],key,c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
@@ -2382,7 +2383,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
dbAdd(c->db,dstkey,dstobj);
addReplyLongLong(c,zsetLength(dstobj));
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
notifyKeyspaceEvent(NOTIFY_ZSET,
(op == SET_OP_UNION) ? "zunionstore" : "zinterstore",
dstkey,c->db->id);
@@ -2391,7 +2392,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
decrRefCount(dstobj);
addReply(c,shared.czero);
if (touched) {
signalModifiedKey(c->db,dstkey);
signalModifiedKey(c,c->db,dstkey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
server.dirty++;
}
@@ -3215,7 +3216,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
if (arraylen == 0) { /* Do this only for the first iteration. */
char *events[2] = {"zpopmin","zpopmax"};
notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
signalModifiedKey(c->db,key);
signalModifiedKey(c,c->db,key);
}
addReplyBulkCBuffer(c,ele,sdslen(ele));
+189
View File
@@ -0,0 +1,189 @@
/* Copyright (c) 2009-2020, 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 "server.h"
#include "cluster.h"
/* ========================== Clients timeouts ============================= */
/* Check if this blocked client timedout (does nothing if the client is
* not blocked right now). If so send a reply, unblock it, and return 1.
* Otherwise 0 is returned and no operation is performed. */
int checkBlockedClientTimeout(client *c, mstime_t now) {
if (c->flags & CLIENT_BLOCKED &&
c->bpop.timeout != 0
&& c->bpop.timeout < now)
{
/* Handle blocking operation specific timeout. */
replyToBlockedClientTimedOut(c);
unblockClient(c);
return 1;
} else {
return 0;
}
}
/* Check for timeouts. Returns non-zero if the client was terminated.
* The function gets the current time in milliseconds as argument since
* it gets called multiple times in a loop, so calling gettimeofday() for
* each iteration would be costly without any actual gain. */
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (server.maxidletime &&
/* This handles the idle clients connection timeout if set. */
!(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
!(c->flags & CLIENT_MASTER) && /* No timeout for masters */
!(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
!(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
(now - c->lastinteraction > server.maxidletime))
{
serverLog(LL_VERBOSE,"Closing idle client");
freeClient(c);
return 1;
} else if (c->flags & CLIENT_BLOCKED) {
/* Cluster: handle unblock & redirect of clients blocked
* into keys no longer served by this server. */
if (server.cluster_enabled) {
if (clusterRedirectBlockedClientIfNeeded(c))
unblockClient(c);
}
}
return 0;
}
/* For blocked clients timeouts we populate a radix tree of 128 bit keys
* composed as such:
*
* [8 byte big endian expire time]+[8 byte client ID]
*
* We don't do any cleanup in the Radix tree: when we run the clients that
* reached the timeout already, if they are no longer existing or no longer
* blocked with such timeout, we just go forward.
*
* Every time a client blocks with a timeout, we add the client in
* the tree. In beforeSleep() we call handleBlockedClientsTimeout() to run
* the tree and unblock the clients. */
#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
/* Given client ID and timeout, write the resulting radix tree key in buf. */
void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, client *c) {
timeout = htonu64(timeout);
memcpy(buf,&timeout,sizeof(timeout));
memcpy(buf+8,&c,sizeof(c));
if (sizeof(c) == 4) memset(buf+12,0,4); /* Zero padding for 32bit target. */
}
/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
* the timeout into *toptr and the client pointer into *cptr. */
void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, client **cptr) {
memcpy(toptr,buf,sizeof(*toptr));
*toptr = ntohu64(*toptr);
memcpy(cptr,buf+8,sizeof(*cptr));
}
/* Add the specified client id / timeout as a key in the radix tree we use
* to handle blocked clients timeouts. The client is not added to the list
* if its timeout is zero (block forever). */
void addClientToTimeoutTable(client *c) {
if (c->bpop.timeout == 0) return;
uint64_t timeout = c->bpop.timeout;
unsigned char buf[CLIENT_ST_KEYLEN];
encodeTimeoutKey(buf,timeout,c);
if (raxTryInsert(server.clients_timeout_table,buf,sizeof(buf),NULL,NULL))
c->flags |= CLIENT_IN_TO_TABLE;
}
/* Remove the client from the table when it is unblocked for reasons
* different than timing out. */
void removeClientFromTimeoutTable(client *c) {
if (!(c->flags & CLIENT_IN_TO_TABLE)) return;
c->flags &= ~CLIENT_IN_TO_TABLE;
uint64_t timeout = c->bpop.timeout;
unsigned char buf[CLIENT_ST_KEYLEN];
encodeTimeoutKey(buf,timeout,c);
raxRemove(server.clients_timeout_table,buf,sizeof(buf),NULL);
}
/* This function is called in beforeSleep() in order to unblock clients
* that are waiting in blocking operations with a timeout set. */
void handleBlockedClientsTimeout(void) {
if (raxSize(server.clients_timeout_table) == 0) return;
uint64_t now = mstime();
raxIterator ri;
raxStart(&ri,server.clients_timeout_table);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
uint64_t timeout;
client *c;
decodeTimeoutKey(ri.key,&timeout,&c);
if (timeout >= now) break; /* All the timeouts are in the future. */
c->flags &= ~CLIENT_IN_TO_TABLE;
checkBlockedClientTimeout(c,now);
raxRemove(server.clients_timeout_table,ri.key,ri.key_len,NULL);
raxSeek(&ri,"^",NULL,0);
}
}
/* Get a timeout value from an object and store it into 'timeout'.
* The final timeout is always stored as milliseconds as a time where the
* timeout will expire, however the parsing is performed according to
* the 'unit' that can be seconds or milliseconds.
*
* Note that if the timeout is zero (usually from the point of view of
* commands API this means no timeout) the value stored into 'timeout'
* is zero. */
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
long long tval;
long double ftval;
if (unit == UNIT_SECONDS) {
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
"timeout is not a float or out of range") != C_OK)
return C_ERR;
tval = (long long) (ftval * 1000.0);
} else {
if (getLongLongFromObjectOrReply(c,object,&tval,
"timeout is not an integer or out of range") != C_OK)
return C_ERR;
}
if (tval < 0) {
addReplyError(c,"timeout is negative");
return C_ERR;
}
if (tval > 0) {
tval += mstime();
}
*timeout = tval;
return C_OK;
}
+53 -3
View File
@@ -93,11 +93,56 @@ static int parseProtocolsConfig(const char *str) {
* served to the reader yet. */
static list *pending_list = NULL;
/**
* OpenSSL global initialization and locking handling callbacks.
* Note that this is only required for OpenSSL < 1.1.0.
*/
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define USE_CRYPTO_LOCKS
#endif
#ifdef USE_CRYPTO_LOCKS
static pthread_mutex_t *openssl_locks;
static void sslLockingCallback(int mode, int lock_id, const char *f, int line) {
pthread_mutex_t *mt = openssl_locks + lock_id;
if (mode & CRYPTO_LOCK) {
pthread_mutex_lock(mt);
} else {
pthread_mutex_unlock(mt);
}
(void)f;
(void)line;
}
static void initCryptoLocks(void) {
unsigned i, nlocks;
if (CRYPTO_get_locking_callback() != NULL) {
/* Someone already set the callback before us. Don't destroy it! */
return;
}
nlocks = CRYPTO_num_locks();
openssl_locks = zmalloc(sizeof(*openssl_locks) * nlocks);
for (i = 0; i < nlocks; i++) {
pthread_mutex_init(openssl_locks + i, NULL);
}
CRYPTO_set_locking_callback(sslLockingCallback);
}
#endif /* USE_CRYPTO_LOCKS */
void tlsInit(void) {
ERR_load_crypto_strings();
SSL_load_error_strings();
SSL_library_init();
#ifdef USE_CRYPTO_LOCKS
initCryptoLocks();
#endif
if (!RAND_poll()) {
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
}
@@ -160,7 +205,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
#endif
if (ctx_config->prefer_server_ciphers)
@@ -168,7 +213,9 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
#if defined(SSL_CTX_set_ecdh_auto)
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
@@ -766,15 +813,17 @@ int tlsHasPendingData() {
return listLength(pending_list) > 0;
}
void tlsProcessPendingData() {
int tlsProcessPendingData() {
listIter li;
listNode *ln;
int processed = listLength(pending_list);
listRewind(pending_list,&li);
while((ln = listNext(&li))) {
tls_connection *conn = listNodeValue(ln);
tlsHandleEvent(conn, AE_READABLE);
}
return processed;
}
#else /* USE_OPENSSL */
@@ -802,7 +851,8 @@ int tlsHasPendingData() {
return 0;
}
void tlsProcessPendingData() {
int tlsProcessPendingData() {
return 0;
}
#endif
+128 -42
View File
@@ -94,7 +94,8 @@ void disableTracking(client *c) {
server.tracking_clients--;
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING|
CLIENT_TRACKING_NOLOOP);
}
}
@@ -129,14 +130,19 @@ void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **pr
if (!(c->flags & CLIENT_TRACKING)) server.tracking_clients++;
c->flags |= CLIENT_TRACKING;
c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST|
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT|
CLIENT_TRACKING_NOLOOP);
c->client_tracking_redirection = redirect_to;
/* This may be the first client we ever enable. Crete the tracking
* table if it does not exist. */
if (TrackingTable == NULL) {
TrackingTable = raxNew();
PrefixTable = raxNew();
TrackingChannelName = createStringObject("__redis__:invalidate",20);
}
/* For broadcasting, set the list of prefixes in the client. */
if (options & CLIENT_TRACKING_BCAST) {
c->flags |= CLIENT_TRACKING_BCAST;
if (numprefix == 0) enableBcastTrackingForPrefix(c,"",0);
@@ -145,7 +151,10 @@ void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **pr
enableBcastTrackingForPrefix(c,sdsprefix,sdslen(sdsprefix));
}
}
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
/* Set the remaining flags that don't need any special handling. */
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT|
CLIENT_TRACKING_NOLOOP);
}
/* This function is called after the execution of a readonly command in the
@@ -197,6 +206,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
if (c->client_tracking_redirection) {
client *redir = lookupClientByID(c->client_tracking_redirection);
if (!redir) {
c->flags |= CLIENT_TRACKING_BROKEN_REDIR;
/* We need to signal to the original connection that we
* are unable to send invalidation messages to the redirected
* connection, because the client no longer exist. */
@@ -245,7 +255,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
* matches one or more prefixes in the prefix table. Later when we
* return to the event loop, we'll send invalidation messages to the
* clients subscribed to each prefix. */
void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
raxIterator ri;
raxStart(&ri,PrefixTable);
raxSeek(&ri,"^",NULL,0);
@@ -254,7 +264,11 @@ void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
if (ri.key_len != 0 && memcmp(ri.key,keyname,ri.key_len) != 0)
continue;
bcastState *bs = ri.data;
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,NULL,NULL);
/* We insert the client pointer as associated value in the radix
* tree. This way we know who was the client that did the last
* change to the key, and can avoid sending the notification in the
* case the client is in NOLOOP mode. */
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
}
raxStop(&ri);
}
@@ -262,15 +276,26 @@ void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
/* This function is called from signalModifiedKey() or other places in Redis
* when a key changes value. In the context of keys tracking, our task here is
* to send a notification to every client that may have keys about such caching
* slot. */
void trackingInvalidateKey(robj *keyobj) {
* slot.
*
* Note that 'c' may be NULL in case the operation was performed outside the
* context of a client modifying the database (for instance when we delete a
* key because of expire).
*
* The last argument 'bcast' tells the function if it should also schedule
* the key for broadcasting to clients in BCAST mode. This is the case when
* the function is called from the Redis core once a key is modified, however
* we also call the function in order to evict keys in the key table in case
* of memory pressure: in that case the key didn't really change, so we want
* just to notify the clients that are in the table for this key, that would
* otherwise miss the fact we are no longer tracking the key for them. */
void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
if (TrackingTable == NULL) return;
sds sdskey = keyobj->ptr;
if (raxSize(PrefixTable) > 0)
trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
if (bcast && raxSize(PrefixTable) > 0)
trackingRememberKeyToBroadcast(c,key,keylen);
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
rax *ids = raxFind(TrackingTable,(unsigned char*)key,keylen);
if (ids == raxNotFound) return;
raxIterator ri;
@@ -279,19 +304,28 @@ void trackingInvalidateKey(robj *keyobj) {
while(raxNext(&ri)) {
uint64_t id;
memcpy(&id,ri.key,sizeof(id));
client *c = lookupClientByID(id);
client *target = lookupClientByID(id);
/* Note that if the client is in BCAST mode, we don't want to
* send invalidation messages that were pending in the case
* previously the client was not in BCAST mode. This can happen if
* TRACKING is enabled normally, and then the client switches to
* BCAST mode. */
if (c == NULL ||
!(c->flags & CLIENT_TRACKING)||
c->flags & CLIENT_TRACKING_BCAST)
if (target == NULL ||
!(target->flags & CLIENT_TRACKING)||
target->flags & CLIENT_TRACKING_BCAST)
{
continue;
}
sendTrackingMessage(c,sdskey,sdslen(sdskey),0);
/* If the client enabled the NOLOOP mode, don't send notifications
* about keys changed by the client itself. */
if (target->flags & CLIENT_TRACKING_NOLOOP &&
target == c)
{
continue;
}
sendTrackingMessage(target,key,keylen,0);
}
raxStop(&ri);
@@ -299,7 +333,13 @@ void trackingInvalidateKey(robj *keyobj) {
* again if more keys will be modified in this caching slot. */
TrackingTableTotalItems -= raxSize(ids);
raxFree(ids);
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
raxRemove(TrackingTable,(unsigned char*)key,keylen,NULL);
}
/* Wrapper (the one actually called across the core) to pass the key
* as object. */
void trackingInvalidateKey(client *c, robj *keyobj) {
trackingInvalidateKeyRaw(c,keyobj->ptr,sdslen(keyobj->ptr),1);
}
/* This function is called when one or all the Redis databases are flushed
@@ -366,10 +406,8 @@ void trackingLimitUsedSlots(void) {
effort--;
raxSeek(&ri,"^",NULL,0);
raxRandomWalk(&ri,0);
rax *ids = ri.data;
TrackingTableTotalItems -= raxSize(ids);
raxFree(ids);
raxRemove(TrackingTable,ri.key,ri.key_len,NULL);
if (raxEOF(&ri)) break;
trackingInvalidateKeyRaw(NULL,(char*)ri.key,ri.key_len,0);
if (raxSize(TrackingTable) <= max_keys) {
timeout_counter = 0;
raxStop(&ri);
@@ -383,6 +421,54 @@ void trackingLimitUsedSlots(void) {
timeout_counter++;
}
/* Generate Redis protocol for an array containing all the key names
* in the 'keys' radix tree. If the client is not NULL, the list will not
* include keys that were modified the last time by this client, in order
* to implement the NOLOOP option.
*
* If the resultin array would be empty, NULL is returned instead. */
sds trackingBuildBroadcastReply(client *c, rax *keys) {
raxIterator ri;
uint64_t count;
if (c == NULL) {
count = raxSize(keys);
} else {
count = 0;
raxStart(&ri,keys);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
if (ri.data != c) count++;
}
raxStop(&ri);
if (count == 0) return NULL;
}
/* Create the array reply with the list of keys once, then send
* it to all the clients subscribed to this prefix. */
char buf[32];
size_t len = ll2string(buf,sizeof(buf),count);
sds proto = sdsempty();
proto = sdsMakeRoomFor(proto,count*15);
proto = sdscatlen(proto,"*",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
raxStart(&ri,keys);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
if (c && ri.data == c) continue;
len = ll2string(buf,sizeof(buf),ri.key_len);
proto = sdscatlen(proto,"$",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
proto = sdscatlen(proto,ri.key,ri.key_len);
proto = sdscatlen(proto,"\r\n",2);
}
raxStop(&ri);
return proto;
}
/* This function will run the prefixes of clients in BCAST mode and
* keys that were modified about each prefix, and will send the
* notifications to each client in each prefix. */
@@ -394,29 +480,15 @@ void trackingBroadcastInvalidationMessages(void) {
raxStart(&ri,PrefixTable);
raxSeek(&ri,"^",NULL,0);
/* For each prefix... */
while(raxNext(&ri)) {
bcastState *bs = ri.data;
if (raxSize(bs->keys)) {
/* Create the array reply with the list of keys once, then send
* it to all the clients subscribed to this prefix. */
char buf[32];
size_t len = ll2string(buf,sizeof(buf),raxSize(bs->keys));
sds proto = sdsempty();
proto = sdsMakeRoomFor(proto,raxSize(bs->keys)*15);
proto = sdscatlen(proto,"*",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
raxStart(&ri2,bs->keys);
raxSeek(&ri2,"^",NULL,0);
while(raxNext(&ri2)) {
len = ll2string(buf,sizeof(buf),ri2.key_len);
proto = sdscatlen(proto,"$",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
proto = sdscatlen(proto,ri2.key,ri2.key_len);
proto = sdscatlen(proto,"\r\n",2);
}
raxStop(&ri2);
/* Generate the common protocol for all the clients that are
* not using the NOLOOP option. */
sds proto = trackingBuildBroadcastReply(NULL,bs->keys);
/* Send this array of keys to every client in the list. */
raxStart(&ri2,bs->clients);
@@ -424,7 +496,16 @@ void trackingBroadcastInvalidationMessages(void) {
while(raxNext(&ri2)) {
client *c;
memcpy(&c,ri2.key,sizeof(c));
sendTrackingMessage(c,proto,sdslen(proto),1);
if (c->flags & CLIENT_TRACKING_NOLOOP) {
/* This client may have certain keys excluded. */
sds adhoc = trackingBuildBroadcastReply(c,bs->keys);
if (adhoc) {
sendTrackingMessage(c,adhoc,sdslen(adhoc),1);
sdsfree(adhoc);
}
} else {
sendTrackingMessage(c,proto,sdslen(proto),1);
}
}
raxStop(&ri2);
@@ -449,3 +530,8 @@ uint64_t trackingGetTotalKeys(void) {
if (TrackingTable == NULL) return 0;
return raxSize(TrackingTable);
}
uint64_t trackingGetTotalPrefixes(void) {
if (PrefixTable == NULL) return 0;
return raxSize(PrefixTable);
}
+35 -13
View File
@@ -42,7 +42,7 @@
#include <time.h>
#include "util.h"
#include "sha1.h"
#include "sha256.h"
/* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen,
@@ -51,7 +51,7 @@ int stringmatchlen(const char *pattern, int patternLen,
while(patternLen && stringLen) {
switch(pattern[0]) {
case '*':
while (pattern[1] == '*') {
while (patternLen && pattern[1] == '*') {
pattern++;
patternLen--;
}
@@ -67,8 +67,6 @@ int stringmatchlen(const char *pattern, int patternLen,
return 0; /* no match */
break;
case '?':
if (stringLen == 0)
return 0; /* no match */
string++;
stringLen--;
break;
@@ -96,7 +94,7 @@ int stringmatchlen(const char *pattern, int patternLen,
pattern--;
patternLen++;
break;
} else if (pattern[1] == '-' && patternLen >= 3) {
} else if (patternLen >= 3 && pattern[1] == '-') {
int start = pattern[0];
int end = pattern[2];
int c = string[0];
@@ -602,6 +600,10 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
}
if (*p == '.') l--;
}
if (l == 2 && buf[0] == '-' && buf[1] == '0') {
buf[0] = '0';
l = 1;
}
break;
default: return 0; /* Invalid mode. */
}
@@ -618,7 +620,7 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
void getRandomBytes(unsigned char *p, size_t len) {
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
static unsigned char seed[64]; /* 512 bit internal block size. */
static uint64_t counter = 0; /* The counter we hash with the seed. */
if (!seed_initialized) {
@@ -643,14 +645,34 @@ void getRandomBytes(unsigned char *p, size_t len) {
}
while(len) {
unsigned char digest[20];
SHA1_CTX ctx;
unsigned int copylen = len > 20 ? 20 : len;
/* This implements SHA256-HMAC. */
unsigned char digest[SHA256_BLOCK_SIZE];
unsigned char kxor[64];
unsigned int copylen =
len > SHA256_BLOCK_SIZE ? SHA256_BLOCK_SIZE : len;
SHA1Init(&ctx);
SHA1Update(&ctx, seed, sizeof(seed));
SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
SHA1Final(digest, &ctx);
/* IKEY: key xored with 0x36. */
memcpy(kxor,seed,sizeof(kxor));
for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x36;
/* Obtain HASH(IKEY||MESSAGE). */
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx,kxor,sizeof(kxor));
sha256_update(&ctx,(unsigned char*)&counter,sizeof(counter));
sha256_final(&ctx,digest);
/* OKEY: key xored with 0x5c. */
memcpy(kxor,seed,sizeof(kxor));
for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x5C;
/* Obtain HASH(OKEY || HASH(IKEY||MESSAGE)). */
sha256_init(&ctx);
sha256_update(&ctx,kxor,sizeof(kxor));
sha256_update(&ctx,digest,SHA256_BLOCK_SIZE);
sha256_final(&ctx,digest);
/* Increment the counter for the next iteration. */
counter++;
memcpy(p,digest,copylen);
+1 -1
View File
@@ -440,7 +440,7 @@ unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
if ((prevlensize) == 1) { \
(prevlen) = (ptr)[0]; \
} else if ((prevlensize) == 5) { \
assert(sizeof((prevlen)) == 4); \
assert(sizeof((prevlen)) == 4); \
memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \
memrev32ifbe(&prevlen); \
} \
+50
View File
@@ -0,0 +1,50 @@
source "../tests/includes/init-tests.tcl"
proc cluster_allocate_mixedSlots {n} {
set slot 16383
while {$slot >= 0} {
set node [expr {$slot % $n}]
lappend slots_$node $slot
incr slot -1
}
for {set j 0} {$j < $n} {incr j} {
R $j cluster addslots {*}[set slots_${j}]
}
}
proc create_cluster_with_mixedSlot {masters slaves} {
cluster_allocate_mixedSlots $masters
if {$slaves} {
cluster_allocate_slaves $masters $slaves
}
assert_cluster_state ok
}
test "Create a 5 nodes cluster" {
create_cluster_with_mixedSlot 5 15
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 0
}
test "Instance #5 is a slave" {
assert {[RI 5 role] eq {slave}}
}
test "client do not break when cluster slot" {
R 0 config set client-output-buffer-limit "normal 33554432 16777216 60"
if { [catch {R 0 cluster slots}] } {
fail "output overflow when cluster slots"
}
}
test "client can handle keys with hash tag" {
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
$cluster set foo{tag} bar
$cluster close
}
+12
View File
@@ -54,6 +54,12 @@ tags {"aof"} {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
test "Truncated AOF loaded: we expect foo to be equal to 5" {
assert {[$client get foo] eq "5"}
}
@@ -71,6 +77,12 @@ tags {"aof"} {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
assert {[$client get foo] eq "6"}
}
-61
View File
@@ -1,61 +0,0 @@
# Test the meaningful offset implementation to make sure masters
# are able to PSYNC with replicas even if the replication stream
# has pending PINGs at the end.
start_server {tags {"psync2"}} {
start_server {} {
# Config
set debug_msg 0 ; # Enable additional debug messages
for {set j 0} {$j < 2} {incr j} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
$R($j) CONFIG SET repl-ping-replica-period 1
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
}
# Setup replication
test "PSYNC2 meaningful offset: setup" {
$R(1) replicaof $R_host(0) $R_port(0)
$R(0) set foo bar
wait_for_condition 50 1000 {
[$R(0) dbsize] == 1 && [$R(1) dbsize] == 1
} else {
fail "Replicas not replicating from master"
}
}
test "PSYNC2 meaningful offset: write and wait replication" {
$R(0) INCR counter
$R(0) INCR counter
$R(0) INCR counter
wait_for_condition 50 1000 {
[$R(0) GET counter] eq [$R(1) GET counter]
} else {
fail "Master and replica don't agree about counter"
}
}
# In this test we'll make sure the replica will get stuck, but with
# an active connection: this way the master will continue to send PINGs
# every second (we modified the PING period earlier)
test "PSYNC2 meaningful offset: pause replica and promote it" {
$R(1) MULTI
$R(1) DEBUG SLEEP 5
$R(1) SLAVEOF NO ONE
$R(1) EXEC
$R(1) ping ; # Wait for it to return back available
}
test "Make the old master a replica of the new one and check conditions" {
set sync_partial [status $R(1) sync_partial_ok]
assert {$sync_partial == 0}
$R(0) REPLICAOF $R_host(1) $R_port(1)
wait_for_condition 50 1000 {
[status $R(1) sync_partial_ok] == 1
} else {
fail "The new master was not able to partial sync"
}
}
}}
+4 -1
View File
@@ -28,7 +28,10 @@ start_server {} {
$R(2) slaveof $R_host(0) $R_port(0)
$R(0) set foo bar
wait_for_condition 50 1000 {
[$R(1) dbsize] == 1 && [$R(2) dbsize] == 1
[status $R(1) master_link_status] == "up" &&
[status $R(2) master_link_status] == "up" &&
[$R(1) dbsize] == 1 &&
[$R(2) dbsize] == 1
} else {
fail "Replicas not replicating from master"
}
+135 -36
View File
@@ -1,3 +1,75 @@
proc show_cluster_status {} {
uplevel 1 {
# The following is the regexp we use to match the log line
# time info. Logs are in the following form:
#
# 11296:M 25 May 2020 17:37:14.652 # Server initialized
set log_regexp {^[0-9]+:[A-Z] [0-9]+ [A-z]+ [0-9]+ ([0-9:.]+) .*}
set repl_regexp {(master|repl|sync|backlog|meaningful|offset)}
puts "Master ID is $master_id"
for {set j 0} {$j < 5} {incr j} {
puts "$j: sync_full: [status $R($j) sync_full]"
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
puts "$j: x var is : [$R($j) GET x]"
puts "---"
}
# Show the replication logs of every instance, interleaving
# them by the log date.
#
# First: load the lines as lists for each instance.
array set log {}
for {set j 0} {$j < 5} {incr j} {
set fd [open $R_log($j)]
while {[gets $fd l] >= 0} {
if {[regexp $log_regexp $l] &&
[regexp -nocase $repl_regexp $l]} {
lappend log($j) $l
}
}
close $fd
}
# To interleave the lines, at every step consume the element of
# the list with the lowest time and remove it. Do it until
# all the lists are empty.
#
# regexp {^[0-9]+:[A-Z] [0-9]+ [A-z]+ [0-9]+ ([0-9:.]+) .*} $l - logdate
while 1 {
# Find the log with smallest time.
set empty 0
set best 0
set bestdate {}
for {set j 0} {$j < 5} {incr j} {
if {[llength $log($j)] == 0} {
incr empty
continue
}
regexp $log_regexp [lindex $log($j) 0] - date
if {$bestdate eq {}} {
set best $j
set bestdate $date
} else {
if {[string compare $bestdate $date] > 0} {
set best $j
set bestdate $date
}
}
}
if {$empty == 5} break ; # Our exit condition: no more logs
# Emit the one with the smallest time (that is the first
# event in the time line).
puts "\[$best port $R_port($best)\] [lindex $log($best) 0]"
set log($best) [lrange $log($best) 1 end]
}
}
}
start_server {tags {"psync2"}} {
start_server {} {
start_server {} {
@@ -12,7 +84,7 @@ start_server {} {
set no_exit 0 ; # Do not exit at end of the test
set duration 20 ; # Total test seconds
set duration 40 ; # Total test seconds
set genload 1 ; # Load master with writes at every cycle
@@ -28,6 +100,7 @@ start_server {} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
set R_log($j) [srv [expr 0-$j] stdout]
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
}
@@ -44,6 +117,7 @@ start_server {} {
set used [list $master_id]
test "PSYNC2: \[NEW LAYOUT\] Set #$master_id as master" {
$R($master_id) slaveof no one
$R($master_id) config set repl-ping-replica-period 1 ;# increse the chance that random ping will cause issues
if {$counter_value == 0} {
$R($master_id) set x $counter_value
}
@@ -66,6 +140,17 @@ start_server {} {
lappend used $slave_id
}
# Wait for replicas to sync. so next loop won't get -LOADING error
wait_for_condition 50 1000 {
[status $R([expr {($master_id+1)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+2)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+3)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+4)%5}]) master_link_status] == "up"
} else {
show_cluster_status
fail "Replica not reconnecting"
}
# 3) Increment the counter and wait for all the instances
# to converge.
test "PSYNC2: cluster is consistent after failover" {
@@ -74,6 +159,7 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($j) get x] == $counter_value
} else {
show_cluster_status
fail "Instance #$j x variable is inconsistent"
}
}
@@ -109,47 +195,27 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($j) get x] == $counter_value
} else {
show_cluster_status
fail "Instance #$j x variable is inconsistent"
}
}
}
# wait for all the slaves to be in sync with the master
set master_ofs [status $R($master_id) master_repl_offset]
# wait for all the slaves to be in sync.
set masteroff [status $R($master_id) master_repl_offset]
wait_for_condition 500 100 {
$master_ofs == [status $R(0) master_repl_offset] &&
$master_ofs == [status $R(1) master_repl_offset] &&
$master_ofs == [status $R(2) master_repl_offset] &&
$master_ofs == [status $R(3) master_repl_offset] &&
$master_ofs == [status $R(4) master_repl_offset]
[status $R(0) master_repl_offset] >= $masteroff &&
[status $R(1) master_repl_offset] >= $masteroff &&
[status $R(2) master_repl_offset] >= $masteroff &&
[status $R(3) master_repl_offset] >= $masteroff &&
[status $R(4) master_repl_offset] >= $masteroff
} else {
if {$debug_msg} {
for {set j 0} {$j < 5} {incr j} {
puts "$j: sync_full: [status $R($j) sync_full]"
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
puts "---"
}
}
fail "Slaves are not in sync with the master after too long time."
show_cluster_status
fail "Replicas offsets didn't catch up with the master after too long time."
}
# Put down the old master so that it cannot generate more
# replication stream, this way in the next master switch, the time at
# which we move slaves away is not important, each will have full
# history (otherwise PINGs will make certain slaves have more history),
# and sometimes a full resync will be needed.
$R($master_id) slaveof 127.0.0.1 0 ;# We use port zero to make it fail.
if {$debug_msg} {
for {set j 0} {$j < 5} {incr j} {
puts "$j: sync_full: [status $R($j) sync_full]"
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
puts "---"
}
show_cluster_status
}
test "PSYNC2: total sum of full synchronizations is exactly 4" {
@@ -157,9 +223,27 @@ start_server {} {
for {set j 0} {$j < 5} {incr j} {
incr sum [status $R($j) sync_full]
}
assert {$sum == 4}
if {$sum != 4} {
show_cluster_status
assert {$sum == 4}
}
}
# In absence of pings, are the instances really able to have
# the exact same offset?
$R($master_id) config set repl-ping-replica-period 3600
wait_for_condition 500 100 {
[status $R($master_id) master_repl_offset] == [status $R(0) master_repl_offset] &&
[status $R($master_id) master_repl_offset] == [status $R(1) master_repl_offset] &&
[status $R($master_id) master_repl_offset] == [status $R(2) master_repl_offset] &&
[status $R($master_id) master_repl_offset] == [status $R(3) master_repl_offset] &&
[status $R($master_id) master_repl_offset] == [status $R(4) master_repl_offset]
} else {
show_cluster_status
fail "Replicas and master offsets were unable to match *exactly*."
}
$R($master_id) config set repl-ping-replica-period 10
# 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.
@@ -175,10 +259,16 @@ start_server {} {
$R($j) slaveof $master_host $master_port
}
# Wait for slaves to sync
# Wait for replicas to sync. it is not enough to just wait for connected_slaves==4
# since we might do the check before the master realized that they're disconnected
wait_for_condition 50 1000 {
[status $R($master_id) connected_slaves] == 4
[status $R($master_id) connected_slaves] == 4 &&
[status $R([expr {($master_id+1)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+2)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+3)%5}]) master_link_status] == "up" &&
[status $R([expr {($master_id+4)%5}]) master_link_status] == "up"
} else {
show_cluster_status
fail "Replica not reconnecting"
}
}
@@ -188,6 +278,7 @@ start_server {} {
set slave_id [expr {($master_id+1)%5}]
set sync_count [status $R($master_id) sync_full]
set sync_partial [status $R($master_id) sync_partial_ok]
set sync_partial_err [status $R($master_id) sync_partial_err]
catch {
$R($slave_id) config rewrite
$R($slave_id) debug restart
@@ -197,7 +288,12 @@ start_server {} {
wait_for_condition 50 1000 {
[status $R($master_id) sync_partial_ok] == $sync_partial + 1
} else {
fail "Replica not reconnecting"
puts "prev sync_full: $sync_count"
puts "prev sync_partial_ok: $sync_partial"
puts "prev sync_partial_err: $sync_partial_err"
puts [$R($master_id) info stats]
show_cluster_status
fail "Replica didn't partial sync"
}
set new_sync_count [status $R($master_id) sync_full]
assert {$sync_count == $new_sync_count}
@@ -218,6 +314,7 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
show_cluster_status
fail "Replica not reconnecting"
}
@@ -252,6 +349,7 @@ start_server {} {
wait_for_condition 50 1000 {
[status $R($master_id) connected_slaves] == 4
} else {
show_cluster_status
fail "Replica not reconnecting"
}
set new_sync_count [status $R($master_id) sync_full]
@@ -262,6 +360,7 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
show_cluster_status
fail "Debug digest mismatch between master and replica in post-restart handshake"
}
}
+52
View File
@@ -128,4 +128,56 @@ start_server {} {
# make sure the server is still writable
r set x xx
}
}
test {client freed during loading} {
start_server [list overrides [list key-load-delay 10 rdbcompression no]] {
# create a big rdb that will take long to load. it is important
# for keys to be big since the server processes events only once in 2mb.
# 100mb of rdb, 100k keys will load in more than 1 second
r debug populate 100000 key 1000
catch {
r debug restart
}
set stdout [srv 0 stdout]
while 1 {
# check that the new server actually started and is ready for connections
if {[exec grep -i "Server initialized" | wc -l < $stdout] > 1} {
break
}
after 10
}
# make sure it's still loading
assert_equal [s loading] 1
# connect and disconnect 10 clients
set clients {}
for {set j 0} {$j < 10} {incr j} {
lappend clients [redis_deferring_client]
}
foreach rd $clients {
$rd debug log bla
}
foreach rd $clients {
$rd read
}
foreach rd $clients {
$rd close
}
# make sure the server freed the clients
wait_for_condition 100 100 {
[s connected_clients] < 3
} else {
fail "clients didn't disconnect"
}
# make sure it's still loading
assert_equal [s loading] 1
# no need to keep waiting for loading to complete
exec kill [srv 0 pid]
}
}
+1 -1
View File
@@ -118,7 +118,7 @@ start_server {tags {"repl"}} {
# correctly the RDB file: such file will contain "lua" AUX
# sections with scripts already in the memory of the master.
wait_for_condition 50 100 {
wait_for_condition 500 100 {
[s -1 master_link_status] eq {up}
} else {
fail "Replication not started."
+55 -3
View File
@@ -513,9 +513,9 @@ start_server {tags {"repl"}} {
set master_port [srv 0 port]
set master_pid [srv 0 pid]
# put enough data in the db that the rdb file will be bigger than the socket buffers
# and since we'll have key-load-delay of 100, 10000 keys will take at least 1 second
# and since we'll have key-load-delay of 100, 20000 keys will take at least 2 seconds
# we also need the replica to process requests during transfer (which it does only once in 2mb)
$master debug populate 10000 test 10000
$master debug populate 20000 test 10000
$master config set rdbcompression no
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues
set os [catch {exec unamee}]
@@ -612,7 +612,7 @@ start_server {tags {"repl"}} {
# Wait that replicas acknowledge they are online so
# we are sure that DBSIZE and DEBUG DIGEST will not
# fail because of timing issues.
wait_for_condition 50 100 {
wait_for_condition 150 100 {
[lindex [$replica role] 3] eq {connected}
} else {
fail "replicas still not connected after some time"
@@ -637,3 +637,55 @@ start_server {tags {"repl"}} {
}
}
}
test {replicaof right after disconnection} {
# this is a rare race condition that was reproduced sporadically by the psync2 unit.
# see details in #7205
start_server {tags {"repl"}} {
set replica1 [srv 0 client]
set replica1_host [srv 0 host]
set replica1_port [srv 0 port]
set replica1_log [srv 0 stdout]
start_server {} {
set replica2 [srv 0 client]
set replica2_host [srv 0 host]
set replica2_port [srv 0 port]
set replica2_log [srv 0 stdout]
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
$replica1 replicaof $master_host $master_port
$replica2 replicaof $master_host $master_port
wait_for_condition 50 100 {
[string match {*master_link_status:up*} [$replica1 info replication]] &&
[string match {*master_link_status:up*} [$replica2 info replication]]
} else {
fail "Can't turn the instance into a replica"
}
set rd [redis_deferring_client -1]
$rd debug sleep 1
after 100
# when replica2 will wake up from the sleep it will find both disconnection
# from it's master and also a replicaof command at the same event loop
$master client kill type replica
$replica2 replicaof $replica1_host $replica1_port
$rd read
wait_for_condition 50 100 {
[string match {*master_link_status:up*} [$replica2 info replication]]
} else {
fail "role change failed."
}
# make sure psync succeeded, and there were no unexpected full syncs.
assert_equal [status $master sync_full] 2
assert_equal [status $replica1 sync_full] 0
assert_equal [status $replica2 sync_full] 0
}
}
}
}
+99 -23
View File
@@ -109,41 +109,33 @@ int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
fsl->list[fsl->length++] = ele;
if (fsl->length >= 2)
RedisModule_SignalKeyAsReady(ctx, argv[1]);
RedisModule_SignalKeyAsReady(ctx, argv[1]);
return RedisModule_ReplyWithSimpleString(ctx, "OK");
}
int bpop2_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
fsl_t *fsl;
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
return REDISMODULE_ERR;
if (!fsl || fsl->length < 2)
return REDISMODULE_ERR;
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
return REDISMODULE_OK;
}
int bpop2_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
}
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
* When that happens, unblock client and pop the last two elements (from the right). */
int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 3)
return RedisModule_WrongArity(ctx);
@@ -155,13 +147,10 @@ int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
return REDISMODULE_OK;
if (!fsl || fsl->length < 2) {
/* Key is empty or has <2 elements, we must block */
RedisModule_BlockClientOnKeys(ctx, bpop2_reply_callback, bpop2_timeout_callback,
if (!fsl) {
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
NULL, timeout, &argv[1], 1, NULL);
} else {
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
}
@@ -175,10 +164,10 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
fsl_t *fsl;
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
return REDISMODULE_ERR;
if (!fsl || fsl->list[fsl->length-1] <= *pgt)
if (fsl->list[fsl->length-1] <= *pgt)
return REDISMODULE_ERR;
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
@@ -218,7 +207,6 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
/* We use malloc so the tests in blockedonkeys.tcl can check for memory leaks */
long long *pgt = RedisModule_Alloc(sizeof(long long));
*pgt = gt;
/* Key is empty or has <2 elements, we must block */
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
bpopgt_free_privdata, timeout, &argv[1], 1, pgt);
} else {
@@ -228,6 +216,88 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_OK;
}
int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleString *src_keyname = RedisModule_GetBlockedClientReadyKey(ctx);
RedisModuleString *dst_keyname = RedisModule_GetBlockedClientPrivateData(ctx);
fsl_t *src;
if (!get_fsl(ctx, src_keyname, REDISMODULE_READ, 0, &src, 0) || !src)
return REDISMODULE_ERR;
fsl_t *dst;
if (!get_fsl(ctx, dst_keyname, REDISMODULE_WRITE, 1, &dst, 0) || !dst)
return REDISMODULE_ERR;
long long ele = src->list[--src->length];
dst->list[dst->length++] = ele;
RedisModule_SignalKeyAsReady(ctx, dst_keyname);
return RedisModule_ReplyWithLongLong(ctx, ele);
}
int bpoppush_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
}
void bpoppush_free_privdata(RedisModuleCtx *ctx, void *privdata) {
RedisModule_FreeString(ctx, privdata);
}
/* FSL.BPOPPUSH <src> <dst> <timeout> - Block clients until <src> has an element.
* When that happens, unblock client, pop the last element from <src> and push it to <dst>
* (from the right). */
int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 4)
return RedisModule_WrongArity(ctx);
long long timeout;
if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
fsl_t *src;
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &src, 1))
return REDISMODULE_OK;
if (!src) {
/* Retain string for reply callback */
RedisModule_RetainString(ctx, argv[2]);
/* Key is empty, we must block */
RedisModule_BlockClientOnKeys(ctx, bpoppush_reply_callback, bpoppush_timeout_callback,
bpoppush_free_privdata, timeout, &argv[1], 1, argv[2]);
} else {
fsl_t *dst;
if (!get_fsl(ctx, argv[2], REDISMODULE_WRITE, 1, &dst, 1))
return REDISMODULE_OK;
long long ele = src->list[--src->length];
dst->list[dst->length++] = ele;
RedisModule_SignalKeyAsReady(ctx, argv[2]);
RedisModule_ReplyWithLongLong(ctx, ele);
}
return REDISMODULE_OK;
}
/* FSL.GETALL <key> - Reply with an array containing all elements. */
int fsl_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2)
return RedisModule_WrongArity(ctx);
fsl_t *fsl;
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
return REDISMODULE_OK;
if (!fsl)
return RedisModule_ReplyWithArray(ctx, 0);
RedisModule_ReplyWithArray(ctx, fsl->length);
for (int i = 0; i < fsl->length; i++)
RedisModule_ReplyWithLongLong(ctx, fsl->list[i]);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@@ -252,11 +322,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.bpop2",fsl_bpop2,"",0,0,0) == REDISMODULE_ERR)
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
+45 -1
View File
@@ -64,7 +64,8 @@ void *threadMain(void *arg) {
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
for (int i = 0; i < 10; i++) {
RedisModule_ThreadSafeContextLock(ctx);
RedisModule_Replicate(ctx,"INCR","c","thread");
RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
RedisModule_ThreadSafeContextUnlock(ctx);
}
RedisModule_FreeThreadSafeContext(ctx);
@@ -89,6 +90,38 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
return REDISMODULE_OK;
}
int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
/* Replicate two commands to test MULTI/EXEC wrapping. */
RedisModule_Replicate(ctx,"INCR","c","counter-1");
RedisModule_Replicate(ctx,"INCR","c","counter-2");
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTest3Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test mixes multiple propagation systems. */
reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
RedisModule_FreeCallReply(reply);
RedisModule_Replicate(ctx,"INCR","c","counter-1");
RedisModule_Replicate(ctx,"INCR","c","counter-2");
reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
RedisModule_FreeCallReply(reply);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@@ -100,5 +133,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
propagateTestCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-2",
propagateTest2Command,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-3",
propagateTest3Command,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
+23 -2
View File
@@ -286,8 +286,29 @@ proc ::redis_cluster::crc16 {s} {
# Hash a single key returning the slot it belongs to, Implemented hash
# tags as described in the Redis Cluster specification.
proc ::redis_cluster::hash {key} {
# TODO: Handle hash slots.
expr {[::redis_cluster::crc16 $key] & 16383}
set keylen [string length $key]
set s {}
set e {}
for {set s 0} {$s < $keylen} {incr s} {
if {[string index $key $s] eq "\{"} break
}
if {[expr {$s == $keylen}]} {
set res [expr {[crc16 $key] & 16383}]
return $res
}
for {set e [expr {$s+1}]} {$e < $keylen} {incr e} {
if {[string index $key $e] == "\}"} break
}
if {$e == $keylen || $e == [expr {$s+1}]} {
set res [expr {[crc16 $key] & 16383}]
return $res
}
set key_sub [string range $key [expr {$s+1}] [expr {$e-1}]]
return [expr {[crc16 $key_sub] & 16383}]
}
# Return the slot the specified keys hash to.
+2 -5
View File
@@ -159,12 +159,9 @@ proc start_server {options {code undefined}} {
if {$::external} {
if {[llength $::servers] == 0} {
set srv {}
# In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
# and assign it to ::port through the `--port` option, so we need to reduce it.
set baseport [expr {$::port-100}]
dict set srv "host" $::host
dict set srv "port" $baseport
set client [redis $::host $baseport 0 $::tls]
dict set srv "port" $::port
set client [redis $::host $::port 0 $::tls]
dict set srv "client" $client
$client select 9
+61 -15
View File
@@ -47,7 +47,6 @@ set ::all_tests {
integration/logging
integration/psync2
integration/psync2-reg
integration/psync2-pingoff
unit/pubsub
unit/slowlog
unit/scripting
@@ -82,6 +81,7 @@ set ::skiptests {}
set ::allowtags {}
set ::only_tests {}
set ::single_tests {}
set ::run_solo_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
@@ -105,13 +105,27 @@ set ::tlsdir "tests/tls"
set ::client 0
set ::numclients 16
proc execute_tests name {
# This function is called by one of the test clients when it receives
# a "run" command from the server, with a filename as data.
# It will run the specified test source file and signal it to the
# test server when finished.
proc execute_test_file name {
set path "tests/$name.tcl"
set ::curfile $path
source $path
send_data_packet $::test_server_fd done "$name"
}
# This function is called by one of the test clients when it receives
# a "run_code" command from the server, with a verbatim test source code
# as argument, and an associated name.
# It will run the specified code and signal it to the test server when
# finished.
proc execute_test_code {name code} {
eval $code
send_data_packet $::test_server_fd done "$name"
}
# Setup a list to hold a stack of server configs. When calls to start_server
# are nested, use "srv 0 pid" to get the pid of the inner server. To access
# outer servers, use "srv -1 pid" etcetera.
@@ -188,10 +202,19 @@ proc s {args} {
status [srv $level "client"] [lindex $args 0]
}
proc cleanup {} {
if {$::dont_clean} {
# Test wrapped into run_solo are sent back from the client to the
# test server, so that the test server will send them again to
# clients once the clients are idle.
proc run_solo {name code} {
if {$::numclients == 1 || $::loop || $::external} {
# run_solo is not supported in these scenarios, just run the code.
eval $code
return
}
send_data_packet $::test_server_fd run_solo [list $name $code]
}
proc cleanup {} {
if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
flush stdout
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
@@ -212,13 +235,19 @@ proc test_server_main {} {
# Start the client instances
set ::clients_pids {}
set start_port [expr {$::port+100}]
for {set j 0} {$j < $::numclients} {incr j} {
set start_port [find_available_port $start_port]
if {$::external} {
set p [exec $tclsh [info script] {*}$::argv \
--client $port --port $start_port &]
--client $port --port $::port &]
lappend ::clients_pids $p
incr start_port 10
} else {
set start_port [expr {$::port+100}]
for {set j 0} {$j < $::numclients} {incr j} {
set start_port [find_available_port $start_port]
set p [exec $tclsh [info script] {*}$::argv \
--client $port --port $start_port &]
lappend ::clients_pids $p
incr start_port 10
}
}
# Setup global state for the test server
@@ -331,6 +360,8 @@ proc read_from_test_client fd {
} elseif {$status eq {server-killed}} {
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
set ::active_clients_task($fd) "(KILLED SERVER) pid:$data"
} elseif {$status eq {run_solo}} {
lappend ::run_solo_tests $data
} else {
if {!$::quiet} {
puts "\[$status\]: $data"
@@ -363,6 +394,13 @@ proc force_kill_all_servers {} {
}
}
proc lpop {listVar {count 1}} {
upvar 1 $listVar l
set ele [lindex $l 0]
set l [lrange $l 1 end]
set ele
}
# A new client is idle. Remove it from the list of active clients and
# if there are still test units to run, launch them.
proc signal_idle_client fd {
@@ -383,6 +421,14 @@ proc signal_idle_client fd {
if {$::loop && $::next_test == [llength $::all_tests]} {
set ::next_test 0
}
} elseif {[llength $::run_solo_tests] != 0 && [llength $::active_clients] == 0} {
if {!$::quiet} {
puts [colorstr bold-white "Testing solo test"]
set ::active_clients_task($fd) "ASSIGNED: $fd solo test"
}
set ::clients_start_time($fd) [clock seconds]
send_data_packet $fd run_code [lpop ::run_solo_tests]
lappend ::active_clients $fd
} else {
lappend ::idle_clients $fd
set ::active_clients_task($fd) "SLEEPING, no more units to assign"
@@ -406,11 +452,11 @@ proc the_end {} {
foreach failed $::failed_tests {
puts "*** $failed"
}
cleanup
if {!$::dont_clean} cleanup
exit 1
} else {
puts "\n[colorstr bold-white {\o/}] [colorstr bold-green {All tests passed without errors!}]\n"
cleanup
if {!$::dont_clean} cleanup
exit 0
}
}
@@ -426,7 +472,10 @@ proc test_client_main server_port {
set payload [read $::test_server_fd $bytes]
foreach {cmd data} $payload break
if {$cmd eq {run}} {
execute_tests $data
execute_test_file $data
} elseif {$cmd eq {run_code}} {
foreach {name code} $data break
execute_test_code $name $code
} else {
error "Unknown test client command: $cmd"
}
@@ -506,9 +555,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
# If we use an external server, we can only set numclients to 1,
# otherwise the port will be miscalculated.
set ::numclients 1
incr j
} elseif {$opt eq {--port}} {
set ::port $arg
+4
View File
@@ -94,6 +94,10 @@ start_server {tags {"introspection"}} {
slaveof
bind
requirepass
server_cpulist
bio_cpulist
aof_rewrite_cpulist
bgsave_cpulist
}
set configs {}
+125 -2
View File
@@ -36,6 +36,7 @@ start_server {tags {"memefficiency"}} {
}
}
run_solo {defrag} {
start_server {tags {"defrag"}} {
if {[string match {*jemalloc*} [s mem_allocator]]} {
test "Active defrag" {
@@ -94,6 +95,10 @@ start_server {tags {"defrag"}} {
}
if {$::verbose} {
puts "frag $frag"
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
puts "hits: $hits"
puts "misses: $misses"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
@@ -220,6 +225,10 @@ start_server {tags {"defrag"}} {
}
if {$::verbose} {
puts "frag $frag"
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
puts "hits: $hits"
puts "misses: $misses"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
@@ -255,11 +264,12 @@ start_server {tags {"defrag"}} {
set expected_frag 1.7
# add a mass of list nodes to two lists (allocations are interlaced)
set val [string repeat A 100] ;# 5 items of 100 bytes puts us in the 640 bytes bin, which has 32 regs, so high potential for fragmentation
for {set j 0} {$j < 500000} {incr j} {
set elements 500000
for {set j 0} {$j < $elements} {incr j} {
$rd lpush biglist1 $val
$rd lpush biglist2 $val
}
for {set j 0} {$j < 500000} {incr j} {
for {set j 0} {$j < $elements} {incr j} {
$rd read ; # Discard replies
$rd read ; # Discard replies
}
@@ -301,6 +311,8 @@ start_server {tags {"defrag"}} {
# test the the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
set frag [s allocator_frag_ratio]
set max_latency 0
foreach event [r latency latest] {
@@ -311,6 +323,8 @@ start_server {tags {"defrag"}} {
}
if {$::verbose} {
puts "frag $frag"
puts "misses: $misses"
puts "hits: $hits"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
@@ -319,6 +333,10 @@ start_server {tags {"defrag"}} {
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
assert {$max_latency <= 30}
# in extreme cases of stagnation, we see over 20m misses before the tests aborts with "defrag didn't stop",
# in normal cases we only see 100k misses out of 500k elements
assert {$misses < $elements}
}
# verify the data isn't corrupted or changed
set newdigest [r debug digest]
@@ -326,5 +344,110 @@ start_server {tags {"defrag"}} {
r save ;# saving an rdb iterates over all the data / pointers
r del biglist1 ;# coverage for quicklistBookmarksClear
} {1}
test "Active defrag edge case" {
# there was an edge case in defrag where all the slabs of a certain bin are exact the same
# % utilization, with the exception of the current slab from which new allocations are made
# if the current slab is lower in utilization the defragger would have ended up in stagnation,
# keept running and not move any allocation.
# this test is more consistent on a fresh server with no history
start_server {tags {"defrag"}} {
r flushdb
r config resetstat
r config set save "" ;# prevent bgsave from interfereing with save below
r config set hz 100
r config set activedefrag no
r config set active-defrag-max-scan-fields 1000
r config set active-defrag-threshold-lower 5
r config set active-defrag-cycle-min 65
r config set active-defrag-cycle-max 75
r config set active-defrag-ignore-bytes 1mb
r config set maxmemory 0
set expected_frag 1.3
r debug mallctl-str thread.tcache.flush VOID
# fill the first slab containin 32 regs of 640 bytes.
for {set j 0} {$j < 32} {incr j} {
r setrange "_$j" 600 x
r debug mallctl-str thread.tcache.flush VOID
}
# add a mass of keys with 600 bytes values, fill the bin of 640 bytes which has 32 regs per slab.
set rd [redis_deferring_client]
set keys 640000
for {set j 0} {$j < $keys} {incr j} {
$rd setrange $j 600 x
}
for {set j 0} {$j < $keys} {incr j} {
$rd read ; # Discard replies
}
# create some fragmentation of 50%
set sent 0
for {set j 0} {$j < $keys} {incr j 1} {
$rd del $j
incr sent
incr j 1
}
for {set j 0} {$j < $sent} {incr j} {
$rd read ; # Discard replies
}
# create higher fragmentation in the first slab
for {set j 10} {$j < 32} {incr j} {
r del "_$j"
}
# start defrag
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
if {$::verbose} {
puts "frag $frag"
}
assert {$frag >= $expected_frag}
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)
wait_for_condition 50 100 {
[s active_defrag_running] ne 0
} else {
fail "defrag not started."
}
# wait for the active defrag to stop working
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 info stats]
puts [r memory malloc-stats]
fail "defrag didn't stop."
}
# test the the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
set frag [s allocator_frag_ratio]
if {$::verbose} {
puts "frag $frag"
puts "hits: $hits"
puts "misses: $misses"
}
assert {$frag < 1.1}
assert {$misses < 10000000} ;# when defrag doesn't stop, we have some 30m misses, when it does, we have 2m misses
}
# 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
}
}
}
}
} ;# run_solo
+88 -20
View File
@@ -3,37 +3,70 @@ set testmodule [file normalize tests/modules/blockonkeys.so]
start_server {tags {"modules"}} {
r module load $testmodule
test "Module client blocked on keys: Circular BPOPPUSH" {
set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client]
r del src dst
$rd1 fsl.bpoppush src dst 0
$rd2 fsl.bpoppush dst src 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {2}
} else {
fail "Clients are not blocked"
}
r fsl.push src 42
assert_equal {42} [r fsl.getall src]
assert_equal {} [r fsl.getall dst]
}
test "Module client blocked on keys: Self-referential BPOPPUSH" {
set rd1 [redis_deferring_client]
r del src
$rd1 fsl.bpoppush src src 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r fsl.push src 42
assert_equal {42} [r fsl.getall src]
}
test {Module client blocked on keys (no metadata): No block} {
r del k
r fsl.push k 33
r fsl.push k 34
r fsl.bpop2 k 0
} {34 33}
r fsl.bpop k 0
} {34}
test {Module client blocked on keys (no metadata): Timeout} {
r del k
set rd [redis_deferring_client]
r fsl.push k 33
$rd fsl.bpop2 k 1
$rd fsl.bpop k 1
assert_equal {Request timedout} [$rd read]
}
test {Module client blocked on keys (no metadata): Blocked, case 1} {
test {Module client blocked on keys (no metadata): Blocked} {
r del k
set rd [redis_deferring_client]
r fsl.push k 33
$rd fsl.bpop2 k 0
$rd fsl.bpop k 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r fsl.push k 34
assert_equal {34 33} [$rd read]
}
test {Module client blocked on keys (no metadata): Blocked, case 2} {
r del k
set rd [redis_deferring_client]
r fsl.push k 33
r fsl.push k 34
$rd fsl.bpop2 k 0
assert_equal {34 33} [$rd read]
assert_equal {34} [$rd read]
}
test {Module client blocked on keys (with metadata): No block} {
@@ -60,6 +93,12 @@ start_server {tags {"modules"}} {
set cid [$rd read]
r fsl.push k 33
$rd fsl.bpopgt k 33 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r fsl.push k 34
assert_equal {34} [$rd read]
r client kill id $cid ;# try to smoke-out client-related memory leak
@@ -69,6 +108,12 @@ start_server {tags {"modules"}} {
r del k
set rd [redis_deferring_client]
$rd fsl.bpopgt k 35 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r fsl.push k 33
r fsl.push k 34
r fsl.push k 35
@@ -82,6 +127,12 @@ start_server {tags {"modules"}} {
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r client kill id $cid ;# try to smoke-out client-related memory leak
}
@@ -91,6 +142,12 @@ start_server {tags {"modules"}} {
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r client unblock $cid timeout ;# try to smoke-out client-related memory leak
assert_equal {Request timedout} [$rd read]
}
@@ -101,6 +158,12 @@ start_server {tags {"modules"}} {
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r client unblock $cid error ;# try to smoke-out client-related memory leak
assert_error "*unblocked*" {$rd read}
}
@@ -108,13 +171,18 @@ start_server {tags {"modules"}} {
test {Module client blocked on keys does not wake up on wrong type} {
r del k
set rd [redis_deferring_client]
$rd fsl.bpop2 k 0
$rd fsl.bpop k 0
;# wait until clients are actually blocked
wait_for_condition 50 100 {
[s 0 blocked_clients] eq {1}
} else {
fail "Clients are not blocked"
}
r lpush k 12
r lpush k 13
r lpush k 14
r del k
r fsl.push k 33
r fsl.push k 34
assert_equal {34 33} [$rd read]
assert_equal {34} [$rd read]
}
}
+34 -1
View File
@@ -20,11 +20,44 @@ tags "modules" {
wait_for_condition 5000 10 {
([$replica get timer] eq "10") && \
([$replica get thread] eq "10")
([$replica get a-from-thread] eq "10")
} else {
fail "The two counters don't match the expected value."
}
$master propagate-test-2
$master propagate-test-3
$master multi
$master propagate-test-2
$master propagate-test-3
$master exec
wait_for_ofs_sync $master $replica
assert_equal [s -1 unexpected_error_replies] 0
}
}
}
}
tags "modules aof" {
test {Modules RM_Replicate replicates MULTI/EXEC correctly} {
start_server [list overrides [list loadmodule "$testmodule"]] {
# Enable the AOF
r config set appendonly yes
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
waitForBgrewriteaof r
r propagate-test-2
r propagate-test-3
r multi
r propagate-test-2
r propagate-test-3
r exec
# Load the AOF
r debug loadaof
assert_equal [s 0 unexpected_error_replies] 0
}
}
}
+11
View File
@@ -146,6 +146,17 @@ start_server {tags {"scripting"}} {
set e
} {*not allowed*}
test {EVAL - Scripts can't run XREAD and XREADGROUP with BLOCK option} {
r del s
r xgroup create s g $ MKSTREAM
set res [r eval {return redis.pcall('xread','STREAMS','s','$')} 1 s]
assert {$res eq {}}
assert_error "*xread command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s}
set res [r eval {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s]
assert {$res eq {}}
assert_error "*xreadgroup command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s}
}
test {EVAL - Scripts can't run certain commands} {
set e {}
r debug lua-always-replicate-commands 0
-20
View File
@@ -25,26 +25,6 @@ start_server {tags {"tls"}} {
}
test {TLS: Verify tls-protocols behaves as expected} {
r CONFIG SET tls-protocols TLSv1
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 0}]
catch {$s PING} e
assert_match {*I/O error*} $e
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 1}]
catch {$s PING} e
assert_match {PONG} $e
r CONFIG SET tls-protocols TLSv1.1
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 0}]
catch {$s PING} e
assert_match {*I/O error*} $e
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 1}]
catch {$s PING} e
assert_match {PONG} $e
r CONFIG SET tls-protocols TLSv1.2
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 0}]
+67
View File
@@ -7,6 +7,9 @@ start_server {tags {"tracking"}} {
$rd1 subscribe __redis__:invalidate
$rd1 read ; # Consume the SUBSCRIBE reply.
# Create another client as well in order to test NOLOOP
set rd2 [redis_deferring_client]
test {Clients are able to enable tracking and redirect it} {
r CLIENT TRACKING on REDIRECT $redir
} {*OK}
@@ -62,5 +65,69 @@ start_server {tags {"tracking"}} {
assert {$keys eq {c:1234}}
}
test {Tracking NOLOOP mode in standard mode works} {
r CLIENT TRACKING off
r CLIENT TRACKING on REDIRECT $redir NOLOOP
r MGET otherkey1 loopkey otherkey2
$rd2 SET otherkey1 1; # We should get this
r SET loopkey 1 ; # We should not get this
$rd2 SET otherkey2 1; # We should get this
# Because of the internals, we know we are going to receive
# two separated notifications for the two different prefixes.
set keys1 [lsort [lindex [$rd1 read] 2]]
set keys2 [lsort [lindex [$rd1 read] 2]]
set keys [lsort [list {*}$keys1 {*}$keys2]]
assert {$keys eq {otherkey1 otherkey2}}
}
test {Tracking NOLOOP mode in BCAST mode works} {
r CLIENT TRACKING off
r CLIENT TRACKING on BCAST REDIRECT $redir NOLOOP
$rd2 SET otherkey1 1; # We should get this
r SET loopkey 1 ; # We should not get this
$rd2 SET otherkey2 1; # We should get this
# Because of the internals, we know we are going to receive
# two separated notifications for the two different prefixes.
set keys1 [lsort [lindex [$rd1 read] 2]]
set keys2 [lsort [lindex [$rd1 read] 2]]
set keys [lsort [list {*}$keys1 {*}$keys2]]
assert {$keys eq {otherkey1 otherkey2}}
}
test {Tracking gets notification of expired keys} {
r CLIENT TRACKING off
r CLIENT TRACKING on BCAST REDIRECT $redir NOLOOP
r SET mykey myval px 1
r SET mykeyotherkey myval ; # We should not get it
after 1000
# Because of the internals, we know we are going to receive
# two separated notifications for the two different prefixes.
set keys1 [lsort [lindex [$rd1 read] 2]]
set keys [lsort [list {*}$keys1]]
assert {$keys eq {mykey}}
}
test {Tracking gets notification on tracking table key eviction} {
r CLIENT TRACKING off
r CLIENT TRACKING on REDIRECT $redir NOLOOP
r MSET key1 1 key2 2
# Let the server track the two keys for us
r MGET key1 key2
# Force the eviction of all the keys but one:
r config set tracking-table-max-keys 1
# Note that we may have other keys in the table for this client,
# since we disabled/enabled tracking multiple time with the same
# ID, and tracking does not do ID cleanups for performance reasons.
# So we check that eventually we'll receive one or the other key,
# otherwise the test will die for timeout.
while 1 {
set keys [lindex [$rd1 read] 2]
if {$keys eq {key1} || $keys eq {key2}} break
}
# We should receive an expire notification for one of
# the two keys (only one must remain)
assert {$keys eq {key1} || $keys eq {key2}}
}
$rd1 close
}
+7
View File
@@ -151,4 +151,11 @@ start_server {tags {"incr"}} {
catch {r incrbyfloat foo 1} err
format $err
} {ERR*valid*}
test {No negative zero} {
r del foo
r incrbyfloat foo [expr double(1)/41]
r incrbyfloat foo [expr double(-1)/41]
r get foo
} {0}
}
+67
View File
@@ -93,6 +93,18 @@ start_server {
assert {[r XACK mystream mygroup $id1 $id2] eq 1}
}
test {XACK should fail if got at least one invalid ID} {
r del mystream
r xgroup create s g $ MKSTREAM
r xadd s * f1 v1
set c [llength [lindex [r xreadgroup group g c streams s >] 0 1]]
assert {$c == 1}
set pending [r xpending s g - + 10 c]
set id1 [lindex $pending 0 0]
assert_error "*Invalid stream ID specified*" {r xack s g $id1 invalid-id}
assert {[r xack s g $id1] eq 1}
}
test {PEL NACK reassignment after XGROUP SETID event} {
r del events
r xadd events * f1 v1
@@ -170,6 +182,27 @@ start_server {
assert_error "*NOGROUP*" {$rd read}
}
test {RENAME can unblock XREADGROUP with data} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
r XGROUP CREATE mystream2 mygroup $ MKSTREAM
r XADD mystream2 100 f1 v1
r RENAME mystream2 mystream
assert_equal "{mystream {{100-0 {f1 v1}}}}" [$rd read] ;# mystream2 had mygroup before RENAME
}
test {RENAME can unblock XREADGROUP with -NOGROUP} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
r XADD mystream2 100 f1 v1
r RENAME mystream2 mystream
assert_error "*NOGROUP*" {$rd read} ;# mystream2 didn't have mygroup before RENAME
}
test {XCLAIM can claim PEL items from another consumer} {
# Add 3 items into the stream, and create a consumer group
r del mystream
@@ -261,6 +294,40 @@ start_server {
assert {[lindex $reply 0 3] == 2}
}
test {XINFO FULL output} {
r del x
r XADD x 100 a 1
r XADD x 101 b 1
r XADD x 102 c 1
r XADD x 103 e 1
r XADD x 104 f 1
r XGROUP CREATE x g1 0
r XGROUP CREATE x g2 0
r XREADGROUP GROUP g1 Alice COUNT 1 STREAMS x >
r XREADGROUP GROUP g1 Bob COUNT 1 STREAMS x >
r XREADGROUP GROUP g1 Bob NOACK COUNT 1 STREAMS x >
r XREADGROUP GROUP g2 Charlie COUNT 4 STREAMS x >
r XDEL x 103
set reply [r XINFO STREAM x FULL]
assert_equal [llength $reply] 12
assert_equal [lindex $reply 1] 4 ;# stream length
assert_equal [lindex $reply 9] "{100-0 {a 1}} {101-0 {b 1}} {102-0 {c 1}} {104-0 {f 1}}" ;# entries
assert_equal [lindex $reply 11 0 1] "g1" ;# first group name
assert_equal [lindex $reply 11 0 7 0 0] "100-0" ;# first entry in group's PEL
assert_equal [lindex $reply 11 0 9 0 1] "Alice" ;# first consumer
assert_equal [lindex $reply 11 0 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
assert_equal [lindex $reply 11 1 1] "g2" ;# second group name
assert_equal [lindex $reply 11 1 9 0 1] "Charlie" ;# first consumer
assert_equal [lindex $reply 11 1 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
assert_equal [lindex $reply 11 1 9 0 7 1 0] "101-0" ;# second entry in first consumer's PEL
set reply [r XINFO STREAM x FULL COUNT 1]
assert_equal [llength $reply] 12
assert_equal [lindex $reply 1] 4
assert_equal [lindex $reply 9] "{100-0 {a 1}}"
}
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
+1 -1
View File
@@ -414,7 +414,7 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
}
}
start_server {tags {"xsetid"}} {
start_server {tags {"stream xsetid"}} {
test {XADD can CREATE an empty stream} {
r XADD mystream MAXLEN 0 * a b
assert {[dict get [r xinfo stream mystream] length] == 0}
+30
View File
@@ -419,4 +419,34 @@ start_server {tags {"string"}} {
r set foo bar
r getrange foo 0 4294967297
} {bar}
set rna1 {CACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTCGTCCGGGTGTG}
set rna2 {ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
set rnalcs {ACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
test {STRALGO LCS string output with STRINGS option} {
r STRALGO LCS STRINGS $rna1 $rna2
} $rnalcs
test {STRALGO LCS len} {
r STRALGO LCS LEN STRINGS $rna1 $rna2
} [string length $rnalcs]
test {LCS with KEYS option} {
r set virus1 $rna1
r set virus2 $rna2
r STRALGO LCS KEYS virus1 virus2
} $rnalcs
test {LCS indexes} {
dict get [r STRALGO LCS IDX KEYS virus1 virus2] matches
} {{{238 238} {239 239}} {{236 236} {238 238}} {{229 230} {236 237}} {{224 224} {235 235}} {{1 222} {13 234}}}
test {LCS indexes with match len} {
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN] matches
} {{{238 238} {239 239} 1} {{236 236} {238 238} 1} {{229 230} {236 237} 2} {{224 224} {235 235} 1} {{1 222} {13 234} 222}}
test {LCS indexes with match len and minimum match len} {
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
} {{{1 222} {13 234} 222}}
}
+8 -6
View File
@@ -1,6 +1,7 @@
#!/bin/bash
# Settings
BIN_PATH="../../src/"
CLUSTER_HOST=127.0.0.1
PORT=30000
TIMEOUT=2000
@@ -25,7 +26,7 @@ then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
echo "Starting $PORT"
../../src/redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
$BIN_PATH/redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
done
exit 0
fi
@@ -37,7 +38,7 @@ then
PORT=$((PORT+1))
HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
done
../../src/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
$BIN_PATH/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
exit 0
fi
@@ -46,7 +47,7 @@ then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
echo "Stopping $PORT"
../../src/redis-cli -p $PORT shutdown nosave
$BIN_PATH/redis-cli -p $PORT shutdown nosave
done
exit 0
fi
@@ -57,7 +58,7 @@ then
while [ 1 ]; do
clear
date
../../src/redis-cli -p $PORT cluster nodes | head -30
$BIN_PATH/redis-cli -p $PORT cluster nodes | head -30
sleep 1
done
exit 0
@@ -81,7 +82,7 @@ if [ "$1" == "call" ]
then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
../../src/redis-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
$BIN_PATH/redis-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
done
exit 0
fi
@@ -101,7 +102,7 @@ then
exit 0
fi
echo "Usage: $0 [start|create|stop|watch|tail|clean]"
echo "Usage: $0 [start|create|stop|watch|tail|clean|call]"
echo "start -- Launch Redis Cluster instances."
echo "create -- Create a cluster using redis-cli --cluster create."
echo "stop -- Stop Redis Cluster instances."
@@ -110,3 +111,4 @@ echo "tail <id> -- Run tail -f of instance at base port + ID."
echo "tailall -- Run tail -f for all the log files at once."
echo "clean -- Remove all instances data, logs, configs."
echo "clean-logs -- Remove just instances logs."
echo "call <cmd> -- Call a command (up to 7 arguments) on all nodes."