Compare commits
354
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4834969032 | ||
|
|
71c37605d0 | ||
|
|
3e78344d87 | ||
|
|
e5f1de1448 | ||
|
|
8507c3f26e | ||
|
|
534c3bc21a | ||
|
|
3d07ed983e | ||
|
|
ce8b6ede34 | ||
|
|
68c0e6e331 | ||
|
|
32e0d2376f | ||
|
|
9d4f51e1ff | ||
|
|
73a78adf13 | ||
|
|
c7c3b23787 | ||
|
|
9fe7cd8f14 | ||
|
|
2b09884faf | ||
|
|
e19a06d2dd | ||
|
|
fe43406929 | ||
|
|
9ce638695c | ||
|
|
cd2ee8b113 | ||
|
|
1da93f85cc | ||
|
|
86fb7b20bf | ||
|
|
9e0e5ccbf4 | ||
|
|
8fd1031b10 | ||
|
|
f03aed3ca9 | ||
|
|
ecc48369ce | ||
|
|
08c26591e3 | ||
|
|
fc9b4e7958 | ||
|
|
5aa347b377 | ||
|
|
0aecb135c9 | ||
|
|
80bde9844b | ||
|
|
7af83a0c11 | ||
|
|
20f047965c | ||
|
|
880ca07719 | ||
|
|
bbe18eacda | ||
|
|
6c3bfb00fc | ||
|
|
bec4bfaa0b | ||
|
|
1e585d01de | ||
|
|
1d6711a764 | ||
|
|
cf7475b9b8 | ||
|
|
3ff82790e1 | ||
|
|
fb1d5717de | ||
|
|
58ac1f8bbe | ||
|
|
b6c794acf6 | ||
|
|
448d696549 | ||
|
|
c7e0c410d6 | ||
|
|
3968550135 | ||
|
|
3a27b3d0d8 | ||
|
|
14b2f7b033 | ||
|
|
bb64c7d8b2 | ||
|
|
c5e0bc1070 | ||
|
|
8b87876094 | ||
|
|
0d6f11f4d1 | ||
|
|
3213e8de92 | ||
|
|
777cc5b987 | ||
|
|
c202ba2b35 | ||
|
|
744fe7f348 | ||
|
|
7cdf272d46 | ||
|
|
9df1f73e4c | ||
|
|
bf18044082 | ||
|
|
81c4be42c1 | ||
|
|
12fd92bf67 | ||
|
|
9f43264f86 | ||
|
|
54871412c2 | ||
|
|
f39bb4107c | ||
|
|
394b974920 | ||
|
|
378218d746 | ||
|
|
bd33014f90 | ||
|
|
3176f8e955 | ||
|
|
3b0f008615 | ||
|
|
44f9e0d7c7 | ||
|
|
b2fc998ad6 | ||
|
|
c560ade831 | ||
|
|
411f4b4c12 | ||
|
|
9849cf2df5 | ||
|
|
62015d4fb0 | ||
|
|
d23cae5f06 | ||
|
|
ffbf8f4b8e | ||
|
|
5c9a7b891f | ||
|
|
02817d8b3d | ||
|
|
4dc37c240e | ||
|
|
b278d352c5 | ||
|
|
30377f154f | ||
|
|
690560e8db | ||
|
|
cf0abff567 | ||
|
|
77bdfb5339 | ||
|
|
0dd8fd062d | ||
|
|
43385c4375 | ||
|
|
eac2a79cf5 | ||
|
|
2ead41e05b | ||
|
|
a974531d1a | ||
|
|
7777379814 | ||
|
|
096c74c886 | ||
|
|
2dbf2047ac | ||
|
|
65ce839d09 | ||
|
|
cb51bb4320 | ||
|
|
4cf8fdbbd3 | ||
|
|
52e7192e4a | ||
|
|
0c1d28e2d0 | ||
|
|
3841236074 | ||
|
|
db146de086 | ||
|
|
f74c7394d7 | ||
|
|
ca6aed02f8 | ||
|
|
1051d93837 | ||
|
|
61b7a176ef | ||
|
|
cff5f36d94 | ||
|
|
05e8db24ed | ||
|
|
c9bab6f302 | ||
|
|
8296e5c846 | ||
|
|
a67181f88a | ||
|
|
6f3d357d8f | ||
|
|
6f58486139 | ||
|
|
a9419e2386 | ||
|
|
f579b77449 | ||
|
|
ef2c7a5bbb | ||
|
|
14629278e3 | ||
|
|
83b8eaa638 | ||
|
|
a62b8410ef | ||
|
|
aba57a1bb7 | ||
|
|
596749e75f | ||
|
|
db74d71eb3 | ||
|
|
6bd0d342dd | ||
|
|
7895835df6 | ||
|
|
7e11825ef4 | ||
|
|
092e4de613 | ||
|
|
51b627d916 | ||
|
|
c328834832 | ||
|
|
a8322f44b3 | ||
|
|
4e5e0d3719 | ||
|
|
ff57b8d550 | ||
|
|
2ef829d65c | ||
|
|
c1e9186f06 | ||
|
|
e4298d11e2 | ||
|
|
d60c17cbb3 | ||
|
|
227965221a | ||
|
|
6c001bfc0d | ||
|
|
2b689ad641 | ||
|
|
3e7349fdaf | ||
|
|
247d2a734b | ||
|
|
2290c4bee1 | ||
|
|
e3dfd8c811 | ||
|
|
a036c64c01 | ||
|
|
febe102bf6 | ||
|
|
7fa493912e | ||
|
|
9ab91b8c6c | ||
|
|
83af8ef1fd | ||
|
|
f5b29c6444 | ||
|
|
dce7cefb7c | ||
|
|
d05f5c8f64 | ||
|
|
476eea95da | ||
|
|
bd9259d12d | ||
|
|
ed5cc77ce0 | ||
|
|
7d39c149c4 | ||
|
|
0e21efdb1c | ||
|
|
32844178ac | ||
|
|
5b4bec9d33 | ||
|
|
5941022b52 | ||
|
|
e245a2046a | ||
|
|
067647a783 | ||
|
|
abf52c7cf4 | ||
|
|
66b5afdaa4 | ||
|
|
c241f51607 | ||
|
|
e711e6f614 | ||
|
|
19880ab851 | ||
|
|
80e1695652 | ||
|
|
132be8aed5 | ||
|
|
46d89a9abb | ||
|
|
590717782f | ||
|
|
8d519a85f4 | ||
|
|
f63e81c202 | ||
|
|
f2ad89a314 | ||
|
|
1ab64d405e | ||
|
|
c8452ab005 | ||
|
|
9a65f9cd3e | ||
|
|
8a1219d93b | ||
|
|
ef2a95c461 | ||
|
|
e623bd22ba | ||
|
|
b89302c462 | ||
|
|
14c4ddb5a6 | ||
|
|
f4b27ae222 | ||
|
|
bd01334da1 | ||
|
|
a3a1460525 | ||
|
|
39c70e728b | ||
|
|
7b5e7f3ed0 | ||
|
|
fe56c67405 | ||
|
|
0ce8323c0d | ||
|
|
3d56311f0c | ||
|
|
26897c03e2 | ||
|
|
d506334b67 | ||
|
|
2f282aee0b | ||
|
|
29226a3919 | ||
|
|
12ff0c0d79 | ||
|
|
e3e94ec35c | ||
|
|
0b800c4332 | ||
|
|
6dd0d6f7dd | ||
|
|
60acac4cd0 | ||
|
|
9042d1c249 | ||
|
|
14d6318b32 | ||
|
|
da6b7516f1 | ||
|
|
20c6a7fe2c | ||
|
|
2f2987ffc5 | ||
|
|
187fa78637 | ||
|
|
7a30be1237 | ||
|
|
7b41701eb8 | ||
|
|
e245ed9a44 | ||
|
|
093adb7ef9 | ||
|
|
b0392e75ec | ||
|
|
92b39a0abf | ||
|
|
db693be00d | ||
|
|
11dd3f4b03 | ||
|
|
0e49938b68 | ||
|
|
5401fe7fb9 | ||
|
|
35ca670060 | ||
|
|
fddeeae724 | ||
|
|
7829287691 | ||
|
|
ac3c012a7f | ||
|
|
4ebb4d54c6 | ||
|
|
1e394b73b7 | ||
|
|
1465755e38 | ||
|
|
3c19ae941d | ||
|
|
6491717c88 | ||
|
|
d6f5ec6f09 | ||
|
|
acaa18f1d1 | ||
|
|
15be174cf1 | ||
|
|
be28050ac0 | ||
|
|
c426d85c4c | ||
|
|
4e9c30a6ca | ||
|
|
4a30adde31 | ||
|
|
fd174cca23 | ||
|
|
1c2352f0dd | ||
|
|
fe408ad23f | ||
|
|
dd071ff6c9 | ||
|
|
73cbc33a0a | ||
|
|
eb0e5fe4e3 | ||
|
|
0bdeb861f4 | ||
|
|
53d46fa712 | ||
|
|
e52a036ac6 | ||
|
|
8513f31be0 | ||
|
|
41a886605a | ||
|
|
dfc82fca54 | ||
|
|
f4ac796c34 | ||
|
|
d4ae76d1a6 | ||
|
|
b9a8e56439 | ||
|
|
b28cbe90e1 | ||
|
|
1a8d7cd9c2 | ||
|
|
e8f5d21940 | ||
|
|
1daee8c748 | ||
|
|
ab1d5fe11a | ||
|
|
9a375e5408 | ||
|
|
445a2a2b1b | ||
|
|
b65ddfb16a | ||
|
|
e6ea603ad3 | ||
|
|
993716c351 | ||
|
|
9674ede85a | ||
|
|
780815dd6e | ||
|
|
bb5b8b3a6f | ||
|
|
4017a11144 | ||
|
|
dd760bd5e6 | ||
|
|
4ff47a0b9b | ||
|
|
aba6855282 | ||
|
|
be88c0b16a | ||
|
|
0cf3794e6e | ||
|
|
ea3a20c5d0 | ||
|
|
de5ca516ae | ||
|
|
8f7e496ba5 | ||
|
|
8d617596f1 | ||
|
|
85a1b4f807 | ||
|
|
d4c5fc57db | ||
|
|
1c95c07596 | ||
|
|
16b8d364cc | ||
|
|
0aca977c47 | ||
|
|
313b2240ae | ||
|
|
6183f0590d | ||
|
|
22e9321c3e | ||
|
|
f89c93c8ad | ||
|
|
8213f64d64 | ||
|
|
46fd927800 | ||
|
|
6201f7b4e0 | ||
|
|
4bff45c7a6 | ||
|
|
afc7e08a20 | ||
|
|
e03358c0d9 | ||
|
|
846cf12ae5 | ||
|
|
6501b6bb6d | ||
|
|
d55598988b | ||
|
|
cefe21d28a | ||
|
|
4fc2099235 | ||
|
|
9fbd49bbaf | ||
|
|
cab396761e | ||
|
|
f9c84d6d39 | ||
|
|
e22a1218bc | ||
|
|
28dd8dd150 | ||
|
|
bf680b6f8c | ||
|
|
73306c6fed | ||
|
|
103c5a1a3c | ||
|
|
a317f55d25 | ||
|
|
185e0d9cd8 | ||
|
|
4215e74be5 | ||
|
|
c9324f81a2 | ||
|
|
b6ce7d5ddc | ||
|
|
b4ba5ac840 | ||
|
|
816fc6cbc6 | ||
|
|
b05a22e261 | ||
|
|
491682a668 | ||
|
|
a2177cd2b6 | ||
|
|
0484dbcf68 | ||
|
|
2d4366c544 | ||
|
|
ab33bcd346 | ||
|
|
2352a51992 | ||
|
|
3d7d20b7f3 | ||
|
|
967ad3643c | ||
|
|
d6efd5fc1d | ||
|
|
8dc08ae215 | ||
|
|
699971535e | ||
|
|
a93f8f0998 | ||
|
|
a44a59340c | ||
|
|
d6ba4fd56d | ||
|
|
b3660be8f5 | ||
|
|
d4182a0a0d | ||
|
|
d222eda9f0 | ||
|
|
513eb5728c | ||
|
|
f3980bb9fd | ||
|
|
c2a85fb3c5 | ||
|
|
4cb5bd4e9d | ||
|
|
8d6b7861a7 | ||
|
|
b6260a0270 | ||
|
|
a7c180e559 | ||
|
|
bf4def0fbc | ||
|
|
167705519b | ||
|
|
920158ec81 | ||
|
|
f45e790125 | ||
|
|
9505dd2011 | ||
|
|
0420c3276f | ||
|
|
28e95c7c52 | ||
|
|
a8c1bb310e | ||
|
|
1a02b5f6ee | ||
|
|
a71e814853 | ||
|
|
09327f11dd | ||
|
|
3f8a3efe5f | ||
|
|
7239e9ca5f | ||
|
|
61f12973f7 | ||
|
|
b67f027699 | ||
|
|
18d65849fd | ||
|
|
846203279c | ||
|
|
a3a5a25fa0 | ||
|
|
289d8d9c2c | ||
|
|
62a4a8c1d5 | ||
|
|
0a5805d7f1 | ||
|
|
46f5a2ca07 | ||
|
|
49890c8ee9 | ||
|
|
0fd2b25c8d | ||
|
|
f4eb64cd35 | ||
|
|
2ef8c2f6c2 | ||
|
|
82ae4f30ed | ||
|
|
eaeba1b2c8 | ||
|
|
3447062d5e |
+1
-1
@@ -32,7 +32,7 @@ each source file that you contribute.
|
||||
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
|
||||
@@ -119,7 +119,7 @@ parameter (the path of the configuration file):
|
||||
It is possible to alter the Redis configuration by passing parameters directly
|
||||
as options using the command line. Examples:
|
||||
|
||||
% ./redis-server --port 9999 --slaveof 127.0.0.1 6379
|
||||
% ./redis-server --port 9999 --replicaof 127.0.0.1 6379
|
||||
% ./redis-server /etc/redis/6379.conf --loglevel debug
|
||||
|
||||
All the options in redis.conf are also supported as options using the command
|
||||
@@ -245,7 +245,7 @@ A few important fields in this structure are:
|
||||
* `server.db` is an array of Redis databases, where data is stored.
|
||||
* `server.commands` is the command table.
|
||||
* `server.clients` is a linked list of clients connected to the server.
|
||||
* `server.master` is a special client, the master, if the instance is a slave.
|
||||
* `server.master` is a special client, the master, if the instance is a replica.
|
||||
|
||||
There are tons of other fields. Most fields are commented directly inside
|
||||
the structure definition.
|
||||
@@ -323,7 +323,7 @@ Inside server.c you can find code that handles other vital things of the Redis s
|
||||
networking.c
|
||||
---
|
||||
|
||||
This file defines all the I/O functions with clients, masters and slaves
|
||||
This file defines all the I/O functions with clients, masters and replicas
|
||||
(which in Redis are just special clients):
|
||||
|
||||
* `createClient()` allocates and initializes a new client.
|
||||
@@ -390,16 +390,16 @@ replication.c
|
||||
|
||||
This is one of the most complex files inside Redis, it is recommended to
|
||||
approach it only after getting a bit familiar with the rest of the code base.
|
||||
In this file there is the implementation of both the master and slave role
|
||||
In this file there is the implementation of both the master and replica role
|
||||
of Redis.
|
||||
|
||||
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing slave instances connected
|
||||
to our master, so that the slaves can get the writes performed by the clients:
|
||||
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
|
||||
to our master, so that the replicas can get the writes performed by the clients:
|
||||
this way their data set will remain synchronized with the one in the master.
|
||||
|
||||
This file also implements both the `SYNC` and `PSYNC` commands that are
|
||||
used in order to perform the first synchronization between masters and
|
||||
slaves, or to continue the replication after a disconnection.
|
||||
replicas, or to continue the replication after a disconnection.
|
||||
|
||||
Other C files
|
||||
---
|
||||
|
||||
@@ -234,8 +234,8 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
|
||||
if (slab != bin->slabcur) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
size_t availregs = bin_info->nregs * bin->stats.curslabs;
|
||||
*bin_util = (bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
|
||||
*bin_util = ((long long)bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((long long)(bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
|
||||
defrag = 1;
|
||||
}
|
||||
malloc_mutex_unlock(tsdn, &bin->lock);
|
||||
|
||||
+161
-120
@@ -264,59 +264,64 @@ dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
#
|
||||
# +------------------+ +---------------+
|
||||
# | Master | ---> | Replica |
|
||||
# | (receive writes) | | (exact copy) |
|
||||
# +------------------+ +---------------+
|
||||
#
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of slaves.
|
||||
# 2) Redis slaves are able to perform a partial resynchronization with the
|
||||
# a given number of replicas.
|
||||
# 2) Redis replicas are able to perform a partial resynchronization with the
|
||||
# master if the replication link is lost for a relatively small amount of
|
||||
# time. You may want to configure the replication backlog size (see the next
|
||||
# sections of this file) with a sensible value depending on your needs.
|
||||
# 3) Replication is automatic and does not need user intervention. After a
|
||||
# network partition slaves automatically try to reconnect to masters
|
||||
# network partition replicas automatically try to reconnect to masters
|
||||
# and resynchronize with them.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
# replicaof <masterip> <masterport>
|
||||
|
||||
# If the master is password protected (using the "requirepass" configuration
|
||||
# directive below) it is possible to tell the slave to authenticate before
|
||||
# directive below) it is possible to tell the replica to authenticate before
|
||||
# starting the replication synchronization process, otherwise the master will
|
||||
# refuse the slave request.
|
||||
# refuse the replica request.
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave loses its connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
# When a replica loses its connection with the master, or when the replication
|
||||
# is still in progress, the replica can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
|
||||
# still reply to client requests, possibly with out of date data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
|
||||
# 2) if replica-serve-stale-data is set to 'no' the replica will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO, SLAVEOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
|
||||
# but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
|
||||
# SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
|
||||
# COMMAND, POST, HOST: and LATENCY.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
replica-serve-stale-data yes
|
||||
|
||||
# You can configure a slave instance to accept writes or not. Writing against
|
||||
# a slave instance may be useful to store some ephemeral data (because data
|
||||
# written on a slave will be easily deleted after resync with the master) but
|
||||
# You can configure a replica instance to accept writes or not. Writing against
|
||||
# a replica instance may be useful to store some ephemeral data (because data
|
||||
# written on a replica will be easily deleted after resync with the master) but
|
||||
# may also cause problems if clients are writing to it because of a
|
||||
# misconfiguration.
|
||||
#
|
||||
# Since Redis 2.6 by default slaves are read-only.
|
||||
# Since Redis 2.6 by default replicas are read-only.
|
||||
#
|
||||
# Note: read only slaves are not designed to be exposed to untrusted clients
|
||||
# Note: read only replicas are not designed to be exposed to untrusted clients
|
||||
# on the internet. It's just a protection layer against misuse of the instance.
|
||||
# Still a read only slave exports by default all the administrative commands
|
||||
# Still a read only replica exports by default all the administrative commands
|
||||
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
|
||||
# security of read only slaves using 'rename-command' to shadow all the
|
||||
# security of read only replicas using 'rename-command' to shadow all the
|
||||
# administrative / dangerous commands.
|
||||
slave-read-only yes
|
||||
replica-read-only yes
|
||||
|
||||
# Replication SYNC strategy: disk or socket.
|
||||
#
|
||||
@@ -324,25 +329,25 @@ slave-read-only yes
|
||||
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
|
||||
# -------------------------------------------------------
|
||||
#
|
||||
# New slaves and reconnecting slaves that are not able to continue the replication
|
||||
# New replicas and reconnecting replicas that are not able to continue the replication
|
||||
# process just receiving differences, need to do what is called a "full
|
||||
# synchronization". An RDB file is transmitted from the master to the slaves.
|
||||
# synchronization". An RDB file is transmitted from the master to the replicas.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
# file on disk. Later the file is transferred by the parent
|
||||
# process to the slaves incrementally.
|
||||
# process to the replicas incrementally.
|
||||
# 2) Diskless: The Redis master creates a new process that directly writes the
|
||||
# RDB file to slave sockets, without touching the disk at all.
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more slaves
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
# can be queued and served with the RDB file as soon as the current child producing
|
||||
# the RDB file finishes its work. With diskless replication instead once
|
||||
# the transfer starts, new slaves arriving will be queued and a new transfer
|
||||
# the transfer starts, new replicas arriving will be queued and a new transfer
|
||||
# will start when the current one terminates.
|
||||
#
|
||||
# When diskless replication is used, the master waits a configurable amount of
|
||||
# time (in seconds) before starting the transfer in the hope that multiple slaves
|
||||
# time (in seconds) before starting the transfer in the hope that multiple replicas
|
||||
# will arrive and the transfer can be parallelized.
|
||||
#
|
||||
# With slow disks and fast (large bandwidth) networks, diskless replication
|
||||
@@ -351,140 +356,140 @@ repl-diskless-sync no
|
||||
|
||||
# When diskless replication is enabled, it is possible to configure the delay
|
||||
# the server waits in order to spawn the child that transfers the RDB via socket
|
||||
# to the slaves.
|
||||
# to the replicas.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
# new slaves arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more slaves arrive.
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more replicas arrive.
|
||||
#
|
||||
# The delay is specified in seconds, and by default is 5 seconds. To disable
|
||||
# it entirely just set it to 0 seconds and the transfer will start ASAP.
|
||||
repl-diskless-sync-delay 5
|
||||
|
||||
# Slaves send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_slave_period option. The default value is 10
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_replica_period option. The default value is 10
|
||||
# seconds.
|
||||
#
|
||||
# repl-ping-slave-period 10
|
||||
# repl-ping-replica-period 10
|
||||
|
||||
# The following option sets the replication timeout for:
|
||||
#
|
||||
# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
|
||||
# 2) Master timeout from the point of view of slaves (data, pings).
|
||||
# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
|
||||
# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
|
||||
# 2) Master timeout from the point of view of replicas (data, pings).
|
||||
# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
|
||||
#
|
||||
# It is important to make sure that this value is greater than the value
|
||||
# specified for repl-ping-slave-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the slave.
|
||||
# specified for repl-ping-replica-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the replica.
|
||||
#
|
||||
# repl-timeout 60
|
||||
|
||||
# Disable TCP_NODELAY on the slave socket after SYNC?
|
||||
# Disable TCP_NODELAY on the replica socket after SYNC?
|
||||
#
|
||||
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||
# less bandwidth to send data to slaves. But this can add a delay for
|
||||
# the data to appear on the slave side, up to 40 milliseconds with
|
||||
# less bandwidth to send data to replicas. But this can add a delay for
|
||||
# the data to appear on the replica side, up to 40 milliseconds with
|
||||
# Linux kernels using a default configuration.
|
||||
#
|
||||
# If you select "no" the delay for data to appear on the slave side will
|
||||
# If you select "no" the delay for data to appear on the replica side will
|
||||
# be reduced but more bandwidth will be used for replication.
|
||||
#
|
||||
# By default we optimize for low latency, but in very high traffic conditions
|
||||
# or when the master and slaves are many hops away, turning this to "yes" may
|
||||
# or when the master and replicas are many hops away, turning this to "yes" may
|
||||
# be a good idea.
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# Set the replication backlog size. The backlog is a buffer that accumulates
|
||||
# slave data when slaves are disconnected for some time, so that when a slave
|
||||
# replica data when replicas are disconnected for some time, so that when a replica
|
||||
# wants to reconnect again, often a full resync is not needed, but a partial
|
||||
# resync is enough, just passing the portion of data the slave missed while
|
||||
# resync is enough, just passing the portion of data the replica missed while
|
||||
# disconnected.
|
||||
#
|
||||
# The bigger the replication backlog, the longer the time the slave can be
|
||||
# The bigger the replication backlog, the longer the time the replica can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
#
|
||||
# The backlog is only allocated once there is at least a slave connected.
|
||||
# The backlog is only allocated once there is at least a replica connected.
|
||||
#
|
||||
# repl-backlog-size 1mb
|
||||
|
||||
# After a master has no longer connected slaves for some time, the backlog
|
||||
# After a master has no longer connected replicas for some time, the backlog
|
||||
# will be freed. The following option configures the amount of seconds that
|
||||
# need to elapse, starting from the time the last slave disconnected, for
|
||||
# need to elapse, starting from the time the last replica disconnected, for
|
||||
# the backlog buffer to be freed.
|
||||
#
|
||||
# Note that slaves never free the backlog for timeout, since they may be
|
||||
# Note that replicas never free the backlog for timeout, since they may be
|
||||
# promoted to masters later, and should be able to correctly "partially
|
||||
# resynchronize" with the slaves: hence they should always accumulate backlog.
|
||||
# resynchronize" with the replicas: hence they should always accumulate backlog.
|
||||
#
|
||||
# A value of 0 means to never release the backlog.
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The slave priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a slave to promote into a
|
||||
# The replica priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a replica to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
#
|
||||
# A slave with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel will
|
||||
# pick the one with priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the slave as not able to perform the
|
||||
# role of master, so a slave with priority of 0 will never be selected by
|
||||
# However a special priority of 0 marks the replica as not able to perform the
|
||||
# role of master, so a replica with priority of 0 will never be selected by
|
||||
# Redis Sentinel for promotion.
|
||||
#
|
||||
# By default the priority is 100.
|
||||
slave-priority 100
|
||||
replica-priority 100
|
||||
|
||||
# It is possible for a master to stop accepting writes if there are less than
|
||||
# N slaves connected, having a lag less or equal than M seconds.
|
||||
# N replicas connected, having a lag less or equal than M seconds.
|
||||
#
|
||||
# The N slaves need to be in "online" state.
|
||||
# The N replicas need to be in "online" state.
|
||||
#
|
||||
# The lag in seconds, that must be <= the specified value, is calculated from
|
||||
# the last ping received from the slave, that is usually sent every second.
|
||||
# the last ping received from the replica, that is usually sent every second.
|
||||
#
|
||||
# This option does not GUARANTEE that N replicas will accept the write, but
|
||||
# will limit the window of exposure for lost writes in case not enough slaves
|
||||
# will limit the window of exposure for lost writes in case not enough replicas
|
||||
# are available, to the specified number of seconds.
|
||||
#
|
||||
# For example to require at least 3 slaves with a lag <= 10 seconds use:
|
||||
# For example to require at least 3 replicas with a lag <= 10 seconds use:
|
||||
#
|
||||
# min-slaves-to-write 3
|
||||
# min-slaves-max-lag 10
|
||||
# min-replicas-to-write 3
|
||||
# min-replicas-max-lag 10
|
||||
#
|
||||
# Setting one or the other to 0 disables the feature.
|
||||
#
|
||||
# By default min-slaves-to-write is set to 0 (feature disabled) and
|
||||
# min-slaves-max-lag is set to 10.
|
||||
# By default min-replicas-to-write is set to 0 (feature disabled) and
|
||||
# min-replicas-max-lag is set to 10.
|
||||
|
||||
# A Redis master is able to list the address and port of the attached
|
||||
# slaves in different ways. For example the "INFO replication" section
|
||||
# replicas in different ways. For example the "INFO replication" section
|
||||
# offers this information, which is used, among other tools, by
|
||||
# Redis Sentinel in order to discover slave instances.
|
||||
# Redis Sentinel in order to discover replica instances.
|
||||
# Another place where this info is available is in the output of the
|
||||
# "ROLE" command of a master.
|
||||
#
|
||||
# The listed IP and address normally reported by a slave is obtained
|
||||
# The listed IP and address normally reported by a replica is obtained
|
||||
# in the following way:
|
||||
#
|
||||
# IP: The address is auto detected by checking the peer address
|
||||
# of the socket used by the slave to connect with the master.
|
||||
# of the socket used by the replica to connect with the master.
|
||||
#
|
||||
# Port: The port is communicated by the slave during the replication
|
||||
# handshake, and is normally the port that the slave is using to
|
||||
# list for connections.
|
||||
# Port: The port is communicated by the replica during the replication
|
||||
# handshake, and is normally the port that the replica is using to
|
||||
# listen for connections.
|
||||
#
|
||||
# However when port forwarding or Network Address Translation (NAT) is
|
||||
# used, the slave may be actually reachable via different IP and port
|
||||
# pairs. The following two options can be used by a slave in order to
|
||||
# used, the replica may be actually reachable via different IP and port
|
||||
# pairs. The following two options can be used by a replica in order to
|
||||
# report to its master a specific set of IP and port, so that both INFO
|
||||
# and ROLE will report those values.
|
||||
#
|
||||
# There is no need to use both the options if you need to override just
|
||||
# the port or the IP address.
|
||||
#
|
||||
# slave-announce-ip 5.5.5.5
|
||||
# slave-announce-port 1234
|
||||
# replica-announce-ip 5.5.5.5
|
||||
# replica-announce-port 1234
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
@@ -518,7 +523,7 @@ slave-priority 100
|
||||
# rename-command CONFIG ""
|
||||
#
|
||||
# Please note that changing the name of commands that are logged into the
|
||||
# AOF file or transmitted to slaves may cause problems.
|
||||
# AOF file or transmitted to replicas may cause problems.
|
||||
|
||||
################################### CLIENTS ####################################
|
||||
|
||||
@@ -547,15 +552,15 @@ slave-priority 100
|
||||
# This option is usually useful when using Redis as an LRU or LFU cache, or to
|
||||
# set a hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have slaves attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the slaves are subtracted
|
||||
# WARNING: If you have replicas attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the replicas are subtracted
|
||||
# from the used memory count, so that network problems / resyncs will
|
||||
# not trigger a loop where keys are evicted, and in turn the output
|
||||
# buffer of slaves is full with DELs of keys evicted triggering the deletion
|
||||
# buffer of replicas is full with DELs of keys evicted triggering the deletion
|
||||
# of more keys, and so forth until the database is completely emptied.
|
||||
#
|
||||
# In short... if you have slaves attached it is suggested that you set a lower
|
||||
# limit for maxmemory so that there is some free RAM on the system for slave
|
||||
# In short... if you have replicas attached it is suggested that you set a lower
|
||||
# limit for maxmemory so that there is some free RAM on the system for replica
|
||||
# output buffers (but this is not needed if the policy is 'noeviction').
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
@@ -602,6 +607,26 @@ slave-priority 100
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
|
||||
# Starting from Redis 5, by default a replica will ignore its maxmemory setting
|
||||
# (unless it is promoted to master after a failover or manually). It means
|
||||
# that the eviction of keys will be just handled by the master, sending the
|
||||
# DEL commands to the replica as keys evict in the master side.
|
||||
#
|
||||
# This behavior ensures that masters and replicas stay consistent, and is usually
|
||||
# what you want, however if your replica is writable, or you want the replica to have
|
||||
# a different memory setting, and you are sure all the writes performed to the
|
||||
# replica are idempotent, then you may change this default (but be sure to understand
|
||||
# what you are doing).
|
||||
#
|
||||
# Note that since the replica by default does not evict, it may end using more
|
||||
# memory than the one set via maxmemory (there are certain buffers that may
|
||||
# be larger on the replica, or data structures may sometimes take more memory and so
|
||||
# forth). So make sure you monitor your replicas and make sure they have enough
|
||||
# memory to never hit a real out-of-memory condition before the master hits
|
||||
# the configured maxmemory setting.
|
||||
#
|
||||
# replica-ingore-maxmemory yes
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@@ -637,7 +662,7 @@ slave-priority 100
|
||||
# or SORT with STORE option may delete existing keys. The SET command
|
||||
# itself removes any old content of the specified key in order to replace
|
||||
# it with the specified string.
|
||||
# 4) During replication, when a slave performs a full resynchronization with
|
||||
# 4) During replication, when a replica performs a full resynchronization with
|
||||
# its master, the content of the whole database is removed in order to
|
||||
# load the RDB file just transferred.
|
||||
#
|
||||
@@ -649,7 +674,7 @@ slave-priority 100
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
slave-lazy-flush no
|
||||
replica-lazy-flush no
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
@@ -826,42 +851,42 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# A replica of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have an exact measure of
|
||||
# There is no simple way for a replica to actually have an exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the slave with the best
|
||||
# 1) If there are multiple replicas able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the replica with the best
|
||||
# replication offset (more data from the master processed).
|
||||
# Slaves will try to get their rank by offset, and apply to the start
|
||||
# Replicas will try to get their rank by offset, and apply to the start
|
||||
# of the failover a delay proportional to their rank.
|
||||
#
|
||||
# 2) Every single slave computes the time of the last interaction with
|
||||
# 2) Every single replica computes the time of the last interaction with
|
||||
# its master. This can be the last ping or command received (if the master
|
||||
# is still in the "connected" state), or the time that elapsed since the
|
||||
# disconnection with the master (if the replication link is currently down).
|
||||
# If the last interaction is too old, the slave will not try to failover
|
||||
# If the last interaction is too old, the replica will not try to failover
|
||||
# at all.
|
||||
#
|
||||
# The point "2" can be tuned by user. Specifically a slave will not perform
|
||||
# The point "2" can be tuned by user. Specifically a replica will not perform
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * slave-validity-factor) + repl-ping-slave-period
|
||||
# (node-timeout * replica-validity-factor) + repl-ping-replica-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the slave-validity-factor
|
||||
# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
|
||||
# slave will not try to failover if it was not able to talk with the master
|
||||
# So for example if node-timeout is 30 seconds, and the replica-validity-factor
|
||||
# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
|
||||
# replica will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large slave-validity-factor may allow slaves with too old data to failover
|
||||
# A large replica-validity-factor may allow replicas with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a slave at all.
|
||||
# elect a replica at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the slave-validity-factor
|
||||
# to a value of 0, which means, that slaves will always try to failover the
|
||||
# For maximum availability, it is possible to set the replica-validity-factor
|
||||
# to a value of 0, which means, that replicas will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
# offset rank).
|
||||
@@ -869,22 +894,22 @@ lua-time-limit 5000
|
||||
# Zero is the only value able to guarantee that when all the partitions heal
|
||||
# the cluster will always be able to continue.
|
||||
#
|
||||
# cluster-slave-validity-factor 10
|
||||
# cluster-replica-validity-factor 10
|
||||
|
||||
# Cluster slaves are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working slaves. This improves the cluster ability
|
||||
# Cluster replicas are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working replicas. This improves the cluster ability
|
||||
# to resist to failures as otherwise an orphaned master can't be failed over
|
||||
# in case of failure if it has no working slaves.
|
||||
# in case of failure if it has no working replicas.
|
||||
#
|
||||
# Slaves migrate to orphaned masters only if there are still at least a
|
||||
# given number of other working slaves for their old master. This number
|
||||
# is the "migration barrier". A migration barrier of 1 means that a slave
|
||||
# will migrate only if there is at least 1 other working slave for its master
|
||||
# and so forth. It usually reflects the number of slaves you want for every
|
||||
# Replicas migrate to orphaned masters only if there are still at least a
|
||||
# given number of other working replicas for their old master. This number
|
||||
# is the "migration barrier". A migration barrier of 1 means that a replica
|
||||
# will migrate only if there is at least 1 other working replica for its master
|
||||
# and so forth. It usually reflects the number of replicas you want for every
|
||||
# master in your cluster.
|
||||
#
|
||||
# Default is 1 (slaves migrate only if their masters remain with at least
|
||||
# one slave). To disable migration just set it to a very large value.
|
||||
# Default is 1 (replicas migrate only if their masters remain with at least
|
||||
# one replica). To disable migration just set it to a very large value.
|
||||
# A value of 0 can be set but is useful only for debugging and dangerous
|
||||
# in production.
|
||||
#
|
||||
@@ -903,7 +928,7 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-require-full-coverage yes
|
||||
|
||||
# This option, when set to yes, prevents slaves from trying to failover its
|
||||
# This option, when set to yes, prevents replicas from trying to failover its
|
||||
# master during master failures. However the master can still perform a
|
||||
# manual failover, if forced to do so.
|
||||
#
|
||||
@@ -911,7 +936,7 @@ lua-time-limit 5000
|
||||
# data center operations, where we want one side to never be promoted if not
|
||||
# in the case of a total DC failure.
|
||||
#
|
||||
# cluster-slave-no-failover no
|
||||
# cluster-replica-no-failover no
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
@@ -1145,7 +1170,7 @@ activerehashing yes
|
||||
# The limit can be set differently for the three different classes of clients:
|
||||
#
|
||||
# normal -> normal clients including MONITOR clients
|
||||
# slave -> slave clients
|
||||
# replica -> replica clients
|
||||
# pubsub -> clients subscribed to at least one pubsub channel or pattern
|
||||
#
|
||||
# The syntax of every client-output-buffer-limit directive is the following:
|
||||
@@ -1166,12 +1191,12 @@ activerehashing yes
|
||||
# asynchronous clients may create a scenario where data is requested faster
|
||||
# than it can read.
|
||||
#
|
||||
# Instead there is a default limit for pubsub and slave clients, since
|
||||
# subscribers and slaves receive data in a push fashion.
|
||||
# Instead there is a default limit for pubsub and replica clients, since
|
||||
# subscribers and replicas receive data in a push fashion.
|
||||
#
|
||||
# Both the hard or the soft limit can be disabled by setting them to zero.
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Client query buffers accumulate new commands. They are limited to a fixed
|
||||
@@ -1205,6 +1230,22 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
# Normally it is useful to have an HZ value which is proportional to the
|
||||
# number of clients connected. This is useful in order, for instance, to
|
||||
# avoid too many clients are processed for each background task invocation
|
||||
# in order to avoid latency spikes.
|
||||
#
|
||||
# Since the default HZ value by default is conservatively set to 10, Redis
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
#
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# as a baseline, but multiples of the configured HZ value will be actually
|
||||
# used as needed once more clients are connected. In this way an idle
|
||||
# instance will use very little CPU time while a busy instance will be
|
||||
# more responsive.
|
||||
dynamic-hz yes
|
||||
|
||||
# When a child rewrites the AOF file, if the following option is enabled
|
||||
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
|
||||
@@ -11,4 +11,4 @@ then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/test_helper.tcl $*
|
||||
$TCLSH tests/test_helper.tcl "${@}"
|
||||
|
||||
+39
-19
@@ -20,6 +20,21 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
|
||||
# daemonized.
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis Sentinel writes a pid file in
|
||||
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
|
||||
# location here.
|
||||
pidfile /var/run/redis-sentinel.pid
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Sentinel to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
@@ -58,11 +73,11 @@ dir /tmp
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Slaves are auto-discovered, so you don't need to specify slaves in
|
||||
# Replicas are auto-discovered, so you don't need to specify replicas in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the slaves using additional configuration options.
|
||||
# the replicas using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# slave is promoted to master.
|
||||
# replica is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
@@ -70,11 +85,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# sentinel auth-pass <master-name> <password>
|
||||
#
|
||||
# Set the password to use to authenticate with the master and slaves.
|
||||
# Set the password to use to authenticate with the master and replicas.
|
||||
# Useful if there is a password set in the Redis instances to monitor.
|
||||
#
|
||||
# Note that the master password is also used for slaves, so it is not
|
||||
# possible to set a different password in masters and slaves instances
|
||||
# Note that the master password is also used for replicas, so it is not
|
||||
# possible to set a different password in masters and replicas instances
|
||||
# if you want to be able to monitor these instances with Sentinel.
|
||||
#
|
||||
# However you can have Redis instances without the authentication enabled
|
||||
@@ -89,7 +104,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# sentinel down-after-milliseconds <master-name> <milliseconds>
|
||||
#
|
||||
# Number of milliseconds the master (or any attached slave or sentinel) should
|
||||
# Number of milliseconds the master (or any attached replica or sentinel) should
|
||||
# be unreachable (as in, not acceptable reply to PING, continuously, for the
|
||||
# specified period) in order to consider it in S_DOWN state (Subjectively
|
||||
# Down).
|
||||
@@ -97,11 +112,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numslaves>
|
||||
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||
#
|
||||
# How many slaves we can reconfigure to point to the new slave simultaneously
|
||||
# during the failover. Use a low number if you use the slaves to serve query
|
||||
# to avoid that all the slaves will be unreachable at about the same
|
||||
# How many replicas we can reconfigure to point to the new replica simultaneously
|
||||
# during the failover. Use a low number if you use the replicas to serve query
|
||||
# to avoid that all the replicas will be unreachable at about the same
|
||||
# time while performing the synchronization with the master.
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
@@ -113,18 +128,18 @@ sentinel parallel-syncs mymaster 1
|
||||
# already tried against the same master by a given Sentinel, is two
|
||||
# times the failover timeout.
|
||||
#
|
||||
# - The time needed for a slave replicating to a wrong master according
|
||||
# - The time needed for a replica replicating to a wrong master according
|
||||
# to a Sentinel current configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
# - The time needed to cancel a failover that is already in progress but
|
||||
# did not produced any configuration change (SLAVEOF NO ONE yet not
|
||||
# acknowledged by the promoted slave).
|
||||
# acknowledged by the promoted replica).
|
||||
#
|
||||
# - The maximum time a failover in progress waits for all the slaves to be
|
||||
# reconfigured as slaves of the new master. However even after this time
|
||||
# the slaves will be reconfigured by the Sentinels anyway, but not with
|
||||
# - The maximum time a failover in progress waits for all the replicas to be
|
||||
# reconfigured as replicas of the new master. However even after this time
|
||||
# the replicas will be reconfigured by the Sentinels anyway, but not with
|
||||
# the exact parallel-syncs progression as specified.
|
||||
#
|
||||
# Default is 3 minutes.
|
||||
@@ -185,7 +200,7 @@ sentinel failover-timeout mymaster 180000
|
||||
# <role> is either "leader" or "observer"
|
||||
#
|
||||
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
|
||||
# the old address of the master and the new address of the elected slave
|
||||
# the old address of the master and the new address of the elected replica
|
||||
# (now a master).
|
||||
#
|
||||
# This script should be resistant to multiple invocations.
|
||||
@@ -213,12 +228,17 @@ sentinel deny-scripts-reconfig yes
|
||||
#
|
||||
# In such case it is possible to tell Sentinel to use different command names
|
||||
# instead of the normal ones. For example if the master "mymaster", and the
|
||||
# associated slaves, have "CONFIG" all renamed to "GUESSME", I could use:
|
||||
# associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
|
||||
#
|
||||
# sentinel rename-command mymaster CONFIG GUESSME
|
||||
# SENTINEL rename-command mymaster CONFIG GUESSME
|
||||
#
|
||||
# After such configuration is set, every time Sentinel would use CONFIG it will
|
||||
# use GUESSME instead. Note that there is no actual need to respect the command
|
||||
# case, so writing "config guessme" is the same in the example above.
|
||||
#
|
||||
# SENTINEL SET can also be used in order to perform this configuration at runtime.
|
||||
#
|
||||
# In order to set a command back to its original name (undo the renaming), it
|
||||
# is possible to just rename a command to itsef:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
|
||||
@@ -433,7 +433,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
* before replying to a client. */
|
||||
int invert = fe->mask & AE_BARRIER;
|
||||
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
* processed event removed an element that fired and we still
|
||||
* didn't processed, so we check if the event is still valid.
|
||||
*
|
||||
@@ -485,7 +485,7 @@ int aeWait(int fd, int mask, long long milliseconds) {
|
||||
if ((retval = poll(&pfd, 1, milliseconds))== 1) {
|
||||
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
|
||||
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
|
||||
return retmask;
|
||||
} else {
|
||||
|
||||
@@ -645,7 +645,7 @@ struct client *createFakeClient(void) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -798,7 +798,9 @@ int loadAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
* If the client is in the middle of a MULTI/EXEC, handle it as it was
|
||||
* a short read, even if technically the protocol is correct: we want
|
||||
* to remove the unprocessed tail and continue. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* The design is trivial, we have a structure representing a job to perform
|
||||
* and a different thread and job queue for every job type.
|
||||
* Every thread wait for new jobs in its queue, and process every job
|
||||
* Every thread waits for new jobs in its queue, and process every job
|
||||
* sequentially.
|
||||
*
|
||||
* Jobs of the same type are guaranteed to be processed from the least
|
||||
@@ -204,14 +204,14 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
}
|
||||
zfree(job);
|
||||
|
||||
/* Unblock threads blocked on bioWaitStepOfType() if any. */
|
||||
pthread_cond_broadcast(&bio_step_cond[type]);
|
||||
|
||||
/* Lock again before reiterating the loop, if there are no longer
|
||||
* jobs to process we'll block again in pthread_cond_wait(). */
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
listDelNode(bio_jobs[type],ln);
|
||||
bio_pending[type]--;
|
||||
|
||||
/* Unblock threads blocked on bioWaitStepOfType() if any. */
|
||||
pthread_cond_broadcast(&bio_step_cond[type]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+63
-29
@@ -126,12 +126,37 @@ void processUnblockedClients(void) {
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBuffer(c);
|
||||
processInputBufferAndReplicate(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This function will schedule the client for reprocessing at a safe time.
|
||||
*
|
||||
* This is useful when a client was blocked for some reason (blocking opeation,
|
||||
* CLIENT PAUSE, or whatever), because it may end with some accumulated query
|
||||
* buffer that needs to be processed ASAP:
|
||||
*
|
||||
* 1. When a client is blocked, its readable handler is still active.
|
||||
* 2. However in this case it only gets data into the query buffer, but the
|
||||
* query is not parsed or executed once there is enough to proceed as
|
||||
* usually (because the client is blocked... so we can't execute commands).
|
||||
* 3. When the client is unblocked, without this function, the client would
|
||||
* have to write some query in order for the readable handler to finally
|
||||
* call processQueryBuffer*() on it.
|
||||
* 4. With this function instead we can put the client in a queue that will
|
||||
* process it for queries ready to be executed at a safe time.
|
||||
*/
|
||||
void queueClientForReprocessing(client *c) {
|
||||
/* The client may already be into the unblocked list because of a previous
|
||||
* blocking operation, don't add back it into the list multiple times. */
|
||||
if (!(c->flags & CLIENT_UNBLOCKED)) {
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Unblock a client calling the right function depending on the kind
|
||||
* of operation the client is blocking for. */
|
||||
void unblockClient(client *c) {
|
||||
@@ -152,12 +177,7 @@ void unblockClient(client *c) {
|
||||
server.blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* The client may already be into the unblocked list because of a previous
|
||||
* blocking operation, don't add back it into the list multiple times. */
|
||||
if (!(c->flags & CLIENT_UNBLOCKED)) {
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
@@ -195,7 +215,7 @@ void disconnectAllBlockedClients(void) {
|
||||
if (c->flags & CLIENT_BLOCKED) {
|
||||
addReplySds(c,sdsnew(
|
||||
"-UNBLOCKED force unblock from blocking operation, "
|
||||
"instance state changed (master -> slave?)\r\n"));
|
||||
"instance state changed (master -> replica?)\r\n"));
|
||||
unblockClient(c);
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
@@ -269,7 +289,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
@@ -285,7 +305,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
@@ -370,32 +390,46 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
if (s->last_id.ms > gt->ms ||
|
||||
(s->last_id.ms == gt->ms &&
|
||||
s->last_id.seq > gt->seq))
|
||||
{
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyError(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and
|
||||
* consumer here. */
|
||||
streamCG *group = NULL;
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* In theory if the group is not found we
|
||||
* just perform the read without the group,
|
||||
* but actually when the group, or the key
|
||||
* itself is deleted (triggering the removal
|
||||
* of the group), we check for blocked clients
|
||||
* and send them an error. */
|
||||
}
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
@@ -412,7 +446,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, 0, &pi);
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
|
||||
+56
-17
@@ -1230,7 +1230,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Clear FAIL state for node %.40s: %s is reachable again.",
|
||||
node->name,
|
||||
nodeIsSlave(node) ? "slave" : "master without slots");
|
||||
nodeIsSlave(node) ? "replica" : "master without slots");
|
||||
node->flags &= ~CLUSTER_NODE_FAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
@@ -1589,6 +1589,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
}
|
||||
}
|
||||
|
||||
/* After updating the slots configuration, don't do any actual change
|
||||
* in the state of the server if a module disabled Redis Cluster
|
||||
* keys redirections. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return;
|
||||
|
||||
/* If at least one slot was reassigned from a node to another node
|
||||
* with a greater configEpoch, it is possible that:
|
||||
* 1) We are a master left without slots. This means that we were
|
||||
@@ -2059,7 +2065,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_slave = sender;
|
||||
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
|
||||
serverLog(LL_WARNING,"Manual failover requested by slave %.40s.",
|
||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||
sender->name);
|
||||
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||
clusterNode *n; /* The node the update is about. */
|
||||
@@ -2873,7 +2879,7 @@ void clusterLogCantFailover(int reason) {
|
||||
switch(reason) {
|
||||
case CLUSTER_CANT_FAILOVER_DATA_AGE:
|
||||
msg = "Disconnected from master for longer than allowed. "
|
||||
"Please check the 'cluster-slave-validity-factor' configuration "
|
||||
"Please check the 'cluster-replica-validity-factor' configuration "
|
||||
"option.";
|
||||
break;
|
||||
case CLUSTER_CANT_FAILOVER_WAITING_DELAY:
|
||||
@@ -3054,7 +3060,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
server.cluster->failover_auth_time += added_delay;
|
||||
server.cluster->failover_auth_rank = newrank;
|
||||
serverLog(LL_WARNING,
|
||||
"Slave rank updated to #%d, added %lld milliseconds of delay.",
|
||||
"Replica rank updated to #%d, added %lld milliseconds of delay.",
|
||||
newrank, added_delay);
|
||||
}
|
||||
}
|
||||
@@ -3210,7 +3216,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* the natural slaves of this instance to advertise their switch from
|
||||
* the old master to the new one. */
|
||||
if (target && candidate == myself &&
|
||||
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY)
|
||||
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY &&
|
||||
!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
|
||||
{
|
||||
serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
|
||||
target->name);
|
||||
@@ -3321,14 +3328,18 @@ void clusterCron(void) {
|
||||
int changed = 0;
|
||||
|
||||
if (prev_ip == NULL && curr_ip != NULL) changed = 1;
|
||||
if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
else if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
|
||||
if (changed) {
|
||||
if (prev_ip) zfree(prev_ip);
|
||||
prev_ip = curr_ip;
|
||||
if (prev_ip) prev_ip = zstrdup(prev_ip);
|
||||
|
||||
if (curr_ip) {
|
||||
/* We always take a copy of the previous IP address, by
|
||||
* duplicating the string. This way later we can check if
|
||||
* the address really changed. */
|
||||
prev_ip = zstrdup(prev_ip);
|
||||
strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN);
|
||||
myself->ip[NET_IP_STR_LEN-1] = '\0';
|
||||
} else {
|
||||
@@ -3559,7 +3570,8 @@ void clusterCron(void) {
|
||||
|
||||
if (nodeIsSlave(myself)) {
|
||||
clusterHandleManualFailover();
|
||||
clusterHandleSlaveFailover();
|
||||
if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
|
||||
clusterHandleSlaveFailover();
|
||||
/* If there are orphaned slaves, and we are a slave among the masters
|
||||
* with the max number of non-failing slaves, consider migrating to
|
||||
* the orphaned masters. Note that it does not make sense to try
|
||||
@@ -3865,6 +3877,11 @@ int verifyClusterConfigWithData(void) {
|
||||
int j;
|
||||
int update_config = 0;
|
||||
|
||||
/* Return ASAP if a module disabled cluster redirections. In that case
|
||||
* every master can store keys about every possible hash slot. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return C_OK;
|
||||
|
||||
/* If this node is a slave, don't perform the check at all as we
|
||||
* completely depend on the replication stream. */
|
||||
if (nodeIsSlave(myself)) return C_OK;
|
||||
@@ -4183,7 +4200,7 @@ void clusterCommand(client *c) {
|
||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"FAILOVER [force|takeover] -- Promote current slave node to being a master.",
|
||||
"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
@@ -4193,11 +4210,11 @@ void clusterCommand(client *c) {
|
||||
"MYID -- Return the node id.",
|
||||
"NODES -- Return cluster configuration seen by node. Output format:",
|
||||
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
|
||||
"REPLICATE <node-id> -- Configure current node as slave to <node-id>.",
|
||||
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
|
||||
"RESET [hard|soft] -- Reset current node (default: soft).",
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"SLAVES <node-id> -- Return <node-id> slaves.",
|
||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
@@ -4574,7 +4591,7 @@ NULL
|
||||
|
||||
/* Can't replicate a slave. */
|
||||
if (nodeIsSlave(n)) {
|
||||
addReplyError(c,"I can only replicate a master, not a slave.");
|
||||
addReplyError(c,"I can only replicate a master, not a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4593,7 +4610,8 @@ NULL
|
||||
clusterSetMaster(n);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves") && c->argc == 3) {
|
||||
} else if ((!strcasecmp(c->argv[1]->ptr,"slaves") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) {
|
||||
/* CLUSTER SLAVES <NODE ID> */
|
||||
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
|
||||
int j;
|
||||
@@ -4647,10 +4665,10 @@ NULL
|
||||
|
||||
/* Check preconditions. */
|
||||
if (nodeIsMaster(myself)) {
|
||||
addReplyError(c,"You should send CLUSTER FAILOVER to a slave");
|
||||
addReplyError(c,"You should send CLUSTER FAILOVER to a replica");
|
||||
return;
|
||||
} else if (myself->slaveof == NULL) {
|
||||
addReplyError(c,"I'm a slave but my master is unknown to me");
|
||||
addReplyError(c,"I'm a replica but my master is unknown to me");
|
||||
return;
|
||||
} else if (!force &&
|
||||
(nodeFailed(myself->slaveof) ||
|
||||
@@ -5146,6 +5164,11 @@ try_again:
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
|
||||
}
|
||||
|
||||
int expired = 0; /* Number of keys that we'll find already expired.
|
||||
Note that serializing large keys may take some time
|
||||
so certain keys that were found non expired by the
|
||||
lookupKey() function, may be expired later. */
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
long long ttl = 0;
|
||||
@@ -5153,6 +5176,10 @@ try_again:
|
||||
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 0) {
|
||||
expired++;
|
||||
continue;
|
||||
}
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
serverAssertWithInfo(c,NULL,
|
||||
@@ -5217,9 +5244,13 @@ try_again:
|
||||
int socket_error = 0;
|
||||
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
|
||||
|
||||
/* Allocate the new argument vector that will replace the current command,
|
||||
* to propagate the MIGRATE as a DEL command (if no COPY option was given).
|
||||
* We allocate num_keys+1 because the additional argument is for "DEL"
|
||||
* command name itself. */
|
||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
for (j = 0; j < num_keys-expired; j++) {
|
||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
|
||||
socket_error = 1;
|
||||
break;
|
||||
@@ -5417,9 +5448,17 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
multiCmd mc;
|
||||
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
|
||||
|
||||
/* Allow any key to be set if a module disabled cluster redirections. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return myself;
|
||||
|
||||
/* Set error code optimistically for the base case. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_NONE;
|
||||
|
||||
/* Modules can turn off Redis Cluster redirection: this is useful
|
||||
* when writing a module that implements a completely different
|
||||
* distributed system. */
|
||||
|
||||
/* We handle all the cases as if they were EXEC commands, so we have
|
||||
* a common code path for everything */
|
||||
if (cmd->proc == execCommand) {
|
||||
|
||||
@@ -100,6 +100,13 @@ typedef struct clusterLink {
|
||||
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
|
||||
#define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */
|
||||
|
||||
/* Flags that a module can set in order to prevent certain Redis Cluster
|
||||
* features to be enabled. Useful when implementing a different distributed
|
||||
* system on top of Redis Cluster message bus, using modules. */
|
||||
#define CLUSTER_MODULE_FLAG_NONE 0
|
||||
#define CLUSTER_MODULE_FLAG_NO_FAILOVER (1<<1)
|
||||
#define CLUSTER_MODULE_FLAG_NO_REDIRECTION (1<<2)
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
|
||||
+158
-47
@@ -344,15 +344,19 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "lfu-decay-time must be 0 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
|
||||
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
||||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
||||
slaveof_linenum = linenum;
|
||||
server.masterhost = sdsnew(argv[1]);
|
||||
server.masterport = atoi(argv[2]);
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
} else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"repl-ping-slave-period") ||
|
||||
!strcasecmp(argv[0],"repl-ping-replica-period")) &&
|
||||
argc == 2)
|
||||
{
|
||||
server.repl_ping_slave_period = atoi(argv[1]);
|
||||
if (server.repl_ping_slave_period <= 0) {
|
||||
err = "repl-ping-slave-period must be 1 or greater";
|
||||
err = "repl-ping-replica-period must be 1 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
|
||||
@@ -391,14 +395,27 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-serve-stale-data") ||
|
||||
!strcasecmp(argv[0],"replica-serve-stale-data"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-read-only") ||
|
||||
!strcasecmp(argv[0],"replica-read-only"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"slave-ignore-maxmemory") ||
|
||||
!strcasecmp(argv[0],"replica-ignore-maxmemory"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((server.repl_slave_ignore_maxmemory = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
|
||||
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -423,7 +440,9 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slave-lazy-flush") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-lazy-flush") ||
|
||||
!strcasecmp(argv[0],"replica-lazy-flush")) && argc == 2)
|
||||
{
|
||||
if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
@@ -440,10 +459,14 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"dynamic-hz") && argc == 2) {
|
||||
if ((server.dynamic_hz = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
|
||||
server.hz = atoi(argv[1]);
|
||||
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
|
||||
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
|
||||
server.config_hz = atoi(argv[1]);
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
|
||||
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
|
||||
int yes;
|
||||
|
||||
@@ -651,15 +674,17 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "cluster migration barrier must zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
|
||||
} else if ((!strcasecmp(argv[0],"cluster-slave-validity-factor") ||
|
||||
!strcasecmp(argv[0],"cluster-replica-validity-factor"))
|
||||
&& argc == 2)
|
||||
{
|
||||
server.cluster_slave_validity_factor = atoi(argv[1]);
|
||||
if (server.cluster_slave_validity_factor < 0) {
|
||||
err = "cluster slave validity factor must be zero or positive";
|
||||
err = "cluster replica validity factor must be zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-no-failover") &&
|
||||
} else if ((!strcasecmp(argv[0],"cluster-slave-no-failover") ||
|
||||
!strcasecmp(argv[0],"cluster-replica-no-failiver")) &&
|
||||
argc == 2)
|
||||
{
|
||||
server.cluster_slave_no_failover = yesnotoi(argv[1]);
|
||||
@@ -669,6 +694,8 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
||||
server.lua_always_replicate_commands = yesnotoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
||||
argc == 2)
|
||||
{
|
||||
@@ -710,27 +737,37 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-priority") ||
|
||||
!strcasecmp(argv[0],"replica-priority")) && argc == 2)
|
||||
{
|
||||
server.slave_priority = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-announce-ip") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-announce-ip") ||
|
||||
!strcasecmp(argv[0],"replica-announce-ip")) && argc == 2)
|
||||
{
|
||||
zfree(server.slave_announce_ip);
|
||||
server.slave_announce_ip = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-announce-port") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"slave-announce-port") ||
|
||||
!strcasecmp(argv[0],"replica-announce-port")) && argc == 2)
|
||||
{
|
||||
server.slave_announce_port = atoi(argv[1]);
|
||||
if (server.slave_announce_port < 0 ||
|
||||
server.slave_announce_port > 65535)
|
||||
{
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"min-slaves-to-write") ||
|
||||
!strcasecmp(argv[0],"min-replicas-to-write")) && argc == 2)
|
||||
{
|
||||
server.repl_min_slaves_to_write = atoi(argv[1]);
|
||||
if (server.repl_min_slaves_to_write < 0) {
|
||||
err = "Invalid value for min-slaves-to-write."; goto loaderr;
|
||||
err = "Invalid value for min-replicas-to-write."; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) {
|
||||
} else if ((!strcasecmp(argv[0],"min-slaves-max-lag") ||
|
||||
!strcasecmp(argv[0],"min-replicas-max-lag")) && argc == 2)
|
||||
{
|
||||
server.repl_min_slaves_max_lag = atoi(argv[1]);
|
||||
if (server.repl_min_slaves_max_lag < 0) {
|
||||
err = "Invalid value for min-slaves-max-lag."; goto loaderr;
|
||||
err = "Invalid value for min-replicas-max-lag."; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
|
||||
int flags = keyspaceEventsStringToFlags(argv[1]);
|
||||
@@ -772,7 +809,7 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.cluster_enabled && server.masterhost) {
|
||||
linenum = slaveof_linenum;
|
||||
i = linenum-1;
|
||||
err = "slaveof directive not allowed in cluster mode";
|
||||
err = "replicaof directive not allowed in cluster mode";
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
@@ -856,6 +893,10 @@ void loadServerConfig(char *filename, char *options) {
|
||||
#define config_set_special_field(_name) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) {
|
||||
|
||||
#define config_set_special_field_with_alias(_name1,_name2) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name1) || \
|
||||
!strcasecmp(c->argv[2]->ptr,_name2)) {
|
||||
|
||||
#define config_set_else } else
|
||||
|
||||
void configSetCommand(client *c) {
|
||||
@@ -1015,7 +1056,9 @@ void configSetCommand(client *c) {
|
||||
|
||||
if (flags == -1) goto badfmt;
|
||||
server.notify_keyspace_events = flags;
|
||||
} config_set_special_field("slave-announce-ip") {
|
||||
} config_set_special_field_with_alias("slave-announce-ip",
|
||||
"replica-announce-ip")
|
||||
{
|
||||
zfree(server.slave_announce_ip);
|
||||
server.slave_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
|
||||
@@ -1031,6 +1074,8 @@ void configSetCommand(client *c) {
|
||||
"cluster-require-full-coverage",server.cluster_require_full_coverage) {
|
||||
} config_set_bool_field(
|
||||
"cluster-slave-no-failover",server.cluster_slave_no_failover) {
|
||||
} config_set_bool_field(
|
||||
"cluster-replica-no-failover",server.cluster_slave_no_failover) {
|
||||
} config_set_bool_field(
|
||||
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
|
||||
} config_set_bool_field(
|
||||
@@ -1041,8 +1086,16 @@ void configSetCommand(client *c) {
|
||||
"aof-use-rdb-preamble",server.aof_use_rdb_preamble) {
|
||||
} config_set_bool_field(
|
||||
"slave-serve-stale-data",server.repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"replica-serve-stale-data",server.repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"slave-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"replica-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"slave-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
|
||||
} config_set_bool_field(
|
||||
"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",server.activerehashing) {
|
||||
} config_set_bool_field(
|
||||
@@ -1070,8 +1123,12 @@ void configSetCommand(client *c) {
|
||||
"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) {
|
||||
} config_set_bool_field(
|
||||
"slave-lazy-flush",server.repl_slave_lazy_flush) {
|
||||
} config_set_bool_field(
|
||||
"replica-lazy-flush",server.repl_slave_lazy_flush) {
|
||||
} config_set_bool_field(
|
||||
"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
|
||||
} config_set_bool_field(
|
||||
"dynamic-hz",server.dynamic_hz) {
|
||||
|
||||
/* Numerical fields.
|
||||
* config_set_numerical_field(name,var,min,max) */
|
||||
@@ -1122,7 +1179,7 @@ void configSetCommand(client *c) {
|
||||
} config_set_numerical_field(
|
||||
"lua-time-limit",server.lua_time_limit,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,-1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-max-len",ll,0,LONG_MAX) {
|
||||
/* Cast to unsigned. */
|
||||
@@ -1131,6 +1188,8 @@ void configSetCommand(client *c) {
|
||||
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"repl-ping-slave-period",server.repl_ping_slave_period,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-ping-replica-period",server.repl_ping_slave_period,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-timeout",server.repl_timeout,1,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
@@ -1139,14 +1198,24 @@ void configSetCommand(client *c) {
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-priority",server.slave_priority,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"replica-priority",server.slave_priority,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-announce-port",server.slave_announce_port,0,65535) {
|
||||
} config_set_numerical_field(
|
||||
"replica-announce-port",server.slave_announce_port,0,65535) {
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"min-replicas-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"min-replicas-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} config_set_numerical_field(
|
||||
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
@@ -1158,11 +1227,13 @@ void configSetCommand(client *c) {
|
||||
} config_set_numerical_field(
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hz",server.hz,0,INT_MAX) {
|
||||
"cluster-replica-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hz",server.config_hz,0,INT_MAX) {
|
||||
/* Hz is more an hint from the user, so we accept values out of range
|
||||
* but cap them to reasonable values. */
|
||||
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
|
||||
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
|
||||
} config_set_numerical_field(
|
||||
"watchdog-period",ll,0,INT_MAX) {
|
||||
if (ll)
|
||||
@@ -1175,7 +1246,7 @@ void configSetCommand(client *c) {
|
||||
} config_set_memory_field("maxmemory",server.maxmemory) {
|
||||
if (server.maxmemory) {
|
||||
if (server.maxmemory < zmalloc_used_memory()) {
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
@@ -1268,6 +1339,7 @@ void configGetCommand(client *c) {
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
config_get_string_field("pidfile",server.pidfile);
|
||||
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
|
||||
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
|
||||
|
||||
/* Numerical values */
|
||||
config_get_numerical_field("maxmemory",server.maxmemory);
|
||||
@@ -1320,19 +1392,25 @@ void configGetCommand(client *c) {
|
||||
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
|
||||
config_get_numerical_field("databases",server.dbnum);
|
||||
config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
|
||||
config_get_numerical_field("repl-ping-replica-period",server.repl_ping_slave_period);
|
||||
config_get_numerical_field("repl-timeout",server.repl_timeout);
|
||||
config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
|
||||
config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
|
||||
config_get_numerical_field("maxclients",server.maxclients);
|
||||
config_get_numerical_field("watchdog-period",server.watchdog_period);
|
||||
config_get_numerical_field("slave-priority",server.slave_priority);
|
||||
config_get_numerical_field("replica-priority",server.slave_priority);
|
||||
config_get_numerical_field("slave-announce-port",server.slave_announce_port);
|
||||
config_get_numerical_field("replica-announce-port",server.slave_announce_port);
|
||||
config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
|
||||
config_get_numerical_field("min-replicas-to-write",server.repl_min_slaves_to_write);
|
||||
config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
config_get_numerical_field("min-replicas-max-lag",server.repl_min_slaves_max_lag);
|
||||
config_get_numerical_field("hz",server.config_hz);
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
|
||||
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
|
||||
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
|
||||
|
||||
@@ -1341,12 +1419,22 @@ void configGetCommand(client *c) {
|
||||
server.cluster_require_full_coverage);
|
||||
config_get_bool_field("cluster-slave-no-failover",
|
||||
server.cluster_slave_no_failover);
|
||||
config_get_bool_field("cluster-replica-no-failover",
|
||||
server.cluster_slave_no_failover);
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
server.aof_no_fsync_on_rewrite);
|
||||
config_get_bool_field("slave-serve-stale-data",
|
||||
server.repl_serve_stale_data);
|
||||
config_get_bool_field("replica-serve-stale-data",
|
||||
server.repl_serve_stale_data);
|
||||
config_get_bool_field("slave-read-only",
|
||||
server.repl_slave_ro);
|
||||
config_get_bool_field("replica-read-only",
|
||||
server.repl_slave_ro);
|
||||
config_get_bool_field("slave-ignore-maxmemory",
|
||||
server.repl_slave_ignore_maxmemory);
|
||||
config_get_bool_field("replica-ignore-maxmemory",
|
||||
server.repl_slave_ignore_maxmemory);
|
||||
config_get_bool_field("stop-writes-on-bgsave-error",
|
||||
server.stop_writes_on_bgsave_err);
|
||||
config_get_bool_field("daemonize", server.daemonize);
|
||||
@@ -1375,6 +1463,10 @@ void configGetCommand(client *c) {
|
||||
server.lazyfree_lazy_server_del);
|
||||
config_get_bool_field("slave-lazy-flush",
|
||||
server.repl_slave_lazy_flush);
|
||||
config_get_bool_field("replica-lazy-flush",
|
||||
server.repl_slave_lazy_flush);
|
||||
config_get_bool_field("dynamic-hz",
|
||||
server.dynamic_hz);
|
||||
|
||||
/* Enum values */
|
||||
config_get_enum_field("maxmemory-policy",
|
||||
@@ -1446,10 +1538,14 @@ void configGetCommand(client *c) {
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"slaveof",1)) {
|
||||
if (stringmatch(pattern,"slaveof",1) ||
|
||||
stringmatch(pattern,"replicaof",1))
|
||||
{
|
||||
char *optname = stringmatch(pattern,"slaveof",1) ?
|
||||
"slaveof" : "replicaof";
|
||||
char buf[256];
|
||||
|
||||
addReplyBulkCString(c,"slaveof");
|
||||
addReplyBulkCString(c,optname);
|
||||
if (server.masterhost)
|
||||
snprintf(buf,sizeof(buf),"%s %d",
|
||||
server.masterhost, server.masterport);
|
||||
@@ -1605,8 +1701,20 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
/* Now we populate the state according to the content of this line.
|
||||
* Append the line and populate the option -> line numbers map. */
|
||||
rewriteConfigAppendLine(state,line);
|
||||
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
|
||||
|
||||
/* Translate options using the word "slave" to the corresponding name
|
||||
* "replica", before adding such option to the config name -> lines
|
||||
* mapping. */
|
||||
char *p = strstr(argv[0],"slave");
|
||||
if (p) {
|
||||
sds alt = sdsempty();
|
||||
alt = sdscatlen(alt,argv[0],p-argv[0]);;
|
||||
alt = sdscatlen(alt,"replica",7);
|
||||
alt = sdscatlen(alt,p+5,strlen(p+5));
|
||||
sdsfree(argv[0]);
|
||||
argv[0] = alt;
|
||||
}
|
||||
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
|
||||
sdsfreesplitres(argv,argc);
|
||||
}
|
||||
fclose(fp);
|
||||
@@ -1793,15 +1901,14 @@ void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
}
|
||||
|
||||
/* Rewrite the slaveof option. */
|
||||
void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
|
||||
char *option = "slaveof";
|
||||
void rewriteConfigSlaveofOption(struct rewriteConfigState *state, char *option) {
|
||||
sds line;
|
||||
|
||||
/* If this is a master, we want all the slaveof config options
|
||||
* in the file to be removed. Note that if this is a cluster instance
|
||||
* we don't want a slaveof directive inside redis.conf. */
|
||||
if (server.cluster_enabled || server.masterhost == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"slaveof");
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
line = sdscatprintf(sdsempty(),"%s %s %d", option,
|
||||
@@ -1843,8 +1950,10 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state
|
||||
rewriteConfigFormatMemory(soft,sizeof(soft),
|
||||
server.client_obuf_limits[j].soft_limit_bytes);
|
||||
|
||||
char *typename = getClientTypeName(j);
|
||||
if (!strcmp(typename,"slave")) typename = "replica";
|
||||
line = sdscatprintf(sdsempty(),"%s %s %s %s %ld",
|
||||
option, getClientTypeName(j), hard, soft,
|
||||
option, typename, hard, soft,
|
||||
(long) server.client_obuf_limits[j].soft_limit_seconds);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
@@ -2022,7 +2131,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
|
||||
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,CONFIG_DEFAULT_CLIENT_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,CONFIG_DEFAULT_TCP_KEEPALIVE);
|
||||
rewriteConfigNumericalOption(state,"slave-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
|
||||
rewriteConfigNumericalOption(state,"replica-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
|
||||
rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY);
|
||||
rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
|
||||
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED);
|
||||
@@ -2035,22 +2144,23 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
|
||||
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
|
||||
rewriteConfigDirOption(state);
|
||||
rewriteConfigSlaveofOption(state);
|
||||
rewriteConfigStringOption(state,"slave-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
|
||||
rewriteConfigSlaveofOption(state,"replicaof");
|
||||
rewriteConfigStringOption(state,"replica-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
|
||||
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
|
||||
rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
|
||||
rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
|
||||
rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
|
||||
rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
|
||||
rewriteConfigYesNoOption(state,"replica-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
|
||||
rewriteConfigYesNoOption(state,"replica-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
|
||||
rewriteConfigYesNoOption(state,"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY);
|
||||
rewriteConfigNumericalOption(state,"repl-ping-replica-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
|
||||
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY);
|
||||
rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
|
||||
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
|
||||
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-replicas-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
|
||||
@@ -2076,10 +2186,10 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
|
||||
rewriteConfigYesNoOption(state,"cluster-slave-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
|
||||
rewriteConfigYesNoOption(state,"cluster-replica-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN);
|
||||
rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD);
|
||||
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN);
|
||||
@@ -2098,7 +2208,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
|
||||
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
|
||||
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
|
||||
@@ -2107,7 +2217,8 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
|
||||
rewriteConfigYesNoOption(state,"slave-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
|
||||
rewriteConfigYesNoOption(state,"replica-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
|
||||
rewriteConfigYesNoOption(state,"dynamic-hz",server.dynamic_hz,CONFIG_DEFAULT_DYNAMIC_HZ);
|
||||
|
||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
|
||||
@@ -184,14 +184,19 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
dictEntry auxentry = *de;
|
||||
robj *old = dictGetVal(de);
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
robj *old = dictGetVal(de);
|
||||
int saved_lru = old->lru;
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = saved_lru;
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = old->lru;
|
||||
}
|
||||
dictSetVal(db->dict, de, val);
|
||||
|
||||
if (server.lazyfree_lazy_server_del) {
|
||||
freeObjAsync(old);
|
||||
dictSetVal(db->dict, &auxentry, NULL);
|
||||
}
|
||||
|
||||
dictFreeVal(db->dict, &auxentry);
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
@@ -329,7 +334,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
int j, async = (flags & EMPTYDB_ASYNC);
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= server.dbnum) {
|
||||
@@ -337,8 +342,15 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
if (dbnum != -1 && dbnum != j) continue;
|
||||
int startdb, enddb;
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
enddb = server.dbnum-1;
|
||||
} else {
|
||||
startdb = enddb = dbnum;
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
removed += dictSize(server.db[j].dict);
|
||||
if (async) {
|
||||
emptyDbAsync(&server.db[j]);
|
||||
|
||||
@@ -290,6 +290,7 @@ void debugCommand(client *c) {
|
||||
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"LOG <message> -- write message to the server log.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
|
||||
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
@@ -333,6 +334,9 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
|
||||
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
serverAssertWithInfo(c,c->argv[0],1 == 2);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
|
||||
+2
-3
@@ -867,9 +867,8 @@ void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t resident = server.cron_malloc_stats.allocator_resident;
|
||||
size_t active = server.cron_malloc_stats.allocator_active;
|
||||
size_t allocated = server.cron_malloc_stats.allocator_allocated;
|
||||
size_t resident, active, allocated;
|
||||
zmalloc_get_allocator_info(&allocated, &active, &resident);
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
|
||||
+4
-2
@@ -705,8 +705,10 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
* table, there will be no elements in both tables up to
|
||||
* the current rehashing index, so we jump if possible.
|
||||
* (this happens when going from big to small table). */
|
||||
if (i >= d->ht[1].size) i = d->rehashidx;
|
||||
continue;
|
||||
if (i >= d->ht[1].size)
|
||||
i = d->rehashidx;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
|
||||
@@ -444,6 +444,10 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
/* By default slaves should ignore maxmemory and just be masters excat
|
||||
* copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
|
||||
+2
-2
@@ -127,8 +127,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
|
||||
|
||||
/* Return an error when trying to index outside the supported
|
||||
* constraints. */
|
||||
if (longitude > 180 || longitude < -180 ||
|
||||
latitude > 85.05112878 || latitude < -85.05112878) return 0;
|
||||
if (longitude > GEO_LONG_MAX || longitude < GEO_LONG_MIN ||
|
||||
latitude > GEO_LAT_MAX || latitude < GEO_LAT_MIN) return 0;
|
||||
|
||||
hash->bits = 0;
|
||||
hash->step = step;
|
||||
|
||||
+3
-2
@@ -294,7 +294,7 @@ sds createLatencyReport(void) {
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than == 0) {
|
||||
if (server.slowlog_log_slower_than < 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
@@ -560,10 +560,11 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
|
||||
/* LATENCY command implementations.
|
||||
*
|
||||
* LATENCY SAMPLES: return time-latency samples for the specified event.
|
||||
* LATENCY HISTORY: return time-latency samples for the specified event.
|
||||
* LATENCY LATEST: return the latest latency for all the events classes.
|
||||
* LATENCY DOCTOR: returns an human readable analysis of instance latency.
|
||||
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
|
||||
* LATENCY RESET: reset data of a specified event or all the data if no event provided.
|
||||
*/
|
||||
void latencyCommand(client *c) {
|
||||
struct latencyTimeSeries *ts;
|
||||
|
||||
@@ -90,6 +90,17 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Free an object, if the object is huge enough, free it in async way. */
|
||||
void freeObjAsync(robj *o) {
|
||||
size_t free_effort = lazyfreeGetFreeEffort(o);
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->refcount == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty a Redis DB asynchronously. What the function does actually is to
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
|
||||
@@ -707,6 +707,26 @@ unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, un
|
||||
}
|
||||
}
|
||||
lpSetTotalBytes(lp,new_listpack_bytes);
|
||||
|
||||
#if 0
|
||||
/* This code path is normally disabled: what it does is to force listpack
|
||||
* to return *always* a new pointer after performing some modification to
|
||||
* the listpack, even if the previous allocation was enough. This is useful
|
||||
* in order to spot bugs in code using listpacks: by doing so we can find
|
||||
* if the caller forgets to set the new pointer where the listpack reference
|
||||
* is stored, after an update. */
|
||||
unsigned char *oldlp = lp;
|
||||
lp = lp_malloc(new_listpack_bytes);
|
||||
memcpy(lp,oldlp,new_listpack_bytes);
|
||||
if (newp) {
|
||||
unsigned long offset = (*newp)-oldlp;
|
||||
*newp = lp + offset;
|
||||
}
|
||||
/* Make sure the old allocation contains garbage. */
|
||||
memset(oldlp,'A',new_listpack_bytes);
|
||||
lp_free(oldlp);
|
||||
#endif
|
||||
|
||||
return lp;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
* designed to work with what time(NULL) may return, and to support Redis
|
||||
* logging of the dates, it's not really a complete implementation. */
|
||||
static int is_leap_year(time_t year) {
|
||||
if (year % 4) return 0; /* A year divisible by 4 is not leap. */
|
||||
if (year % 4) return 0; /* A year not divisible by 4 is not leap. */
|
||||
else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */
|
||||
else if (year % 400) return 0; /* If div by 100 *and* 400 is not leap. */
|
||||
else return 1; /* If div by 100 and not by 400 is leap. */
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
void lolwut5Command(client *c);
|
||||
|
||||
/* The default target for LOLWUT if no matching version was found.
|
||||
* This is what unstable versions of Redis will display. */
|
||||
void lolwutUnstableCommand(client *c) {
|
||||
sds rendered = sdsnew("Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyBulkSds(c,rendered);
|
||||
}
|
||||
|
||||
void lolwutCommand(client *c) {
|
||||
char *v = REDIS_VERSION;
|
||||
if ((v[0] == '5' && v[1] == '.') ||
|
||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut5Command(c);
|
||||
else
|
||||
lolwutUnstableCommand(c);
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h>
|
||||
|
||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
char *pixels;
|
||||
} lwCanvas;
|
||||
|
||||
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
||||
* braille character. The byte should correspond to the pixels arranged as
|
||||
* follows, where 0 is the least significant bit, and 7 the most significant
|
||||
* bit:
|
||||
*
|
||||
* 0 3
|
||||
* 1 4
|
||||
* 2 5
|
||||
* 6 7
|
||||
*
|
||||
* The corresponding utf8 encoded character is set into the three bytes
|
||||
* pointed by 'output'.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
void lwTranslatePixelsGroup(int byte, char *output) {
|
||||
int code = 0x2800 + byte;
|
||||
/* Convert to unicode. This is in the U0800-UFFFF range, so we need to
|
||||
* emit it like this in three bytes:
|
||||
* 1110xxxx 10xxxxxx 10xxxxxx. */
|
||||
output[0] = 0xE0 | (code >> 12); /* 1110-xxxx */
|
||||
output[1] = 0x80 | ((code >> 6) & 0x3F); /* 10-xxxxxx */
|
||||
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
||||
}
|
||||
|
||||
/* Allocate and return a new canvas of the specified size. */
|
||||
lwCanvas *lwCreateCanvas(int width, int height) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,0,width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Free the canvas created by lwCreateCanvas(). */
|
||||
void lwFreeCanvas(lwCanvas *canvas) {
|
||||
zfree(canvas->pixels);
|
||||
zfree(canvas);
|
||||
}
|
||||
|
||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||
* dot will be displyed, and 1 means dot will be displayed.
|
||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||
* out of the size of the canvas without issues. */
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return;
|
||||
canvas->pixels[x+y*canvas->width] = color;
|
||||
}
|
||||
|
||||
/* Return the value of the specified pixel on the canvas. */
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return 0;
|
||||
return canvas->pixels[x+y*canvas->width];
|
||||
}
|
||||
|
||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2-x1);
|
||||
int dy = abs(y2-y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx-dy, e2;
|
||||
|
||||
while(1) {
|
||||
lwDrawPixel(canvas,x1,y1,color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err*2;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||
* rotation angle and size. In order to write a rotated square, we use the
|
||||
* trivial fact that the parametric equation:
|
||||
*
|
||||
* x = sin(k)
|
||||
* y = cos(k)
|
||||
*
|
||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||
* points of the square. In order to rotate the square, we just start with
|
||||
* k = PI/4 + rotation_angle, and we are done.
|
||||
*
|
||||
* Of course the vanilla equations above will describe the square inside a
|
||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||
* multiply the obtained coordinates, and then translate them. However this
|
||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||
* a good approach. */
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle) {
|
||||
int px[4], py[4];
|
||||
|
||||
/* Adjust the desired size according to the fact that the square inscribed
|
||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||
* size becomes a simple multiplication factor we can use with our
|
||||
* coordinates to magnify them. */
|
||||
size /= 1.4142135623;
|
||||
size = round(size);
|
||||
|
||||
/* Compute the four points. */
|
||||
float k = M_PI/4 + angle;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
px[j] = round(sin(k) * size + x);
|
||||
py[j] = round(cos(k) * size + y);
|
||||
k += M_PI/2;
|
||||
}
|
||||
|
||||
/* Draw the square. */
|
||||
for (int j = 0; j < 4; j++)
|
||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],1);
|
||||
}
|
||||
|
||||
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||
* caos and order.
|
||||
*
|
||||
* The function creates the canvas itself, depending on the columns available
|
||||
* in the output display and the number of squares per row and per column
|
||||
* requested by the caller. */
|
||||
lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_col) {
|
||||
/* Calculate the canvas size. */
|
||||
int canvas_width = console_cols*2;
|
||||
int padding = canvas_width > 4 ? 2 : 0;
|
||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||
int canvas_height = square_side * squares_per_col + padding*2;
|
||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height);
|
||||
|
||||
for (int y = 0; y < squares_per_col; y++) {
|
||||
for (int x = 0; x < squares_per_row; x++) {
|
||||
int sx = x * square_side + square_side/2 + padding;
|
||||
int sy = y * square_side + square_side/2 + padding;
|
||||
/* Rotate and translate randomly as we go down to lower
|
||||
* rows. */
|
||||
float angle = 0;
|
||||
if (y > 1) {
|
||||
float r1 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r2 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r3 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
if (rand() % 2) r1 = -r1;
|
||||
if (rand() % 2) r2 = -r2;
|
||||
if (rand() % 2) r3 = -r3;
|
||||
angle = r1;
|
||||
sx += r2*square_side/3;
|
||||
sy += r3*square_side/3;
|
||||
}
|
||||
lwDrawSquare(canvas,sx,sy,square_side,angle);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Converts the canvas to an SDS string representing the UTF8 characters to
|
||||
* print to the terminal in order to obtain a graphical representaiton of the
|
||||
* logical canvas. The actual returned string will require a terminal that is
|
||||
* width/2 large and height/4 tall in order to hold the whole image without
|
||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
||||
sds text = sdsempty();
|
||||
for (int y = 0; y < canvas->height; y += 4) {
|
||||
for (int x = 0; x < canvas->width; x += 2) {
|
||||
/* We need to emit groups of 8 bits according to a specific
|
||||
* arrangement. See lwTranslatePixelsGroup() for more info. */
|
||||
int byte = 0;
|
||||
if (lwGetPixel(canvas,x,y)) byte |= (1<<0);
|
||||
if (lwGetPixel(canvas,x,y+1)) byte |= (1<<1);
|
||||
if (lwGetPixel(canvas,x,y+2)) byte |= (1<<2);
|
||||
if (lwGetPixel(canvas,x+1,y)) byte |= (1<<3);
|
||||
if (lwGetPixel(canvas,x+1,y+1)) byte |= (1<<4);
|
||||
if (lwGetPixel(canvas,x+1,y+2)) byte |= (1<<5);
|
||||
if (lwGetPixel(canvas,x,y+3)) byte |= (1<<6);
|
||||
if (lwGetPixel(canvas,x+1,y+3)) byte |= (1<<7);
|
||||
char unicode[3];
|
||||
lwTranslatePixelsGroup(byte,unicode);
|
||||
text = sdscatlen(text,unicode,3);
|
||||
}
|
||||
if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/* The LOLWUT command:
|
||||
*
|
||||
* LOLWUT [terminal columns] [squares-per-row] [squares-per-col]
|
||||
*
|
||||
* By default the command uses 66 columns, 8 squares per row, 12 squares
|
||||
* per column.
|
||||
*/
|
||||
void lolwut5Command(client *c) {
|
||||
long cols = 66;
|
||||
long squares_per_row = 8;
|
||||
long squares_per_col = 12;
|
||||
|
||||
/* Parse the optional arguments if any. */
|
||||
if (c->argc > 1 &&
|
||||
getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 2 &&
|
||||
getLongFromObjectOrReply(c,c->argv[2],&squares_per_row,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 3 &&
|
||||
getLongFromObjectOrReply(c,c->argv[3],&squares_per_col,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||
* so we have maximum number of columns, rows, and output resulution. */
|
||||
if (cols < 1) cols = 1;
|
||||
if (cols > 1000) cols = 1000;
|
||||
if (squares_per_row < 1) squares_per_row = 1;
|
||||
if (squares_per_row > 200) squares_per_row = 200;
|
||||
if (squares_per_col < 1) squares_per_col = 1;
|
||||
if (squares_per_col > 200) squares_per_col = 200;
|
||||
|
||||
/* Generate some computer art and reply. */
|
||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||
sds rendered = lwRenderCanvas(canvas);
|
||||
rendered = sdscat(rendered,
|
||||
"\nGeorg nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyBulkSds(c,rendered);
|
||||
lwFreeCanvas(canvas);
|
||||
}
|
||||
+7
-4
@@ -52,6 +52,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 5
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#endif
|
||||
unsigned int
|
||||
lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
void *out_data, unsigned int out_len)
|
||||
@@ -86,8 +90,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
#ifdef lzf_movsb
|
||||
lzf_movsb (op, ip, ctrl);
|
||||
#else
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
switch (ctrl)
|
||||
{
|
||||
case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
|
||||
@@ -99,7 +101,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
|
||||
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
else /* back reference */
|
||||
@@ -185,4 +186,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
|
||||
return op - (u8 *)out_data;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 5
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
||||
fi
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
|
||||
|
||||
+386
-44
@@ -64,6 +64,7 @@ struct AutoMemEntry {
|
||||
#define REDISMODULE_AM_STRING 1
|
||||
#define REDISMODULE_AM_REPLY 2
|
||||
#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
|
||||
#define REDISMODULE_AM_DICT 4
|
||||
|
||||
/* The pool allocator block. Redis Modules can allocate memory via this special
|
||||
* allocator that will automatically release it all once the callback returns.
|
||||
@@ -241,9 +242,21 @@ typedef struct RedisModuleKeyspaceSubscriber {
|
||||
/* The module keyspace notification subscribers list */
|
||||
static list *moduleKeyspaceSubscribers;
|
||||
|
||||
/* Static client recycled for all notification clients, to avoid allocating
|
||||
* per round. */
|
||||
static client *moduleKeyspaceSubscribersClient;
|
||||
/* Static client recycled for when we need to provide a context with a client
|
||||
* in a situation where there is no client to provide. This avoidsallocating
|
||||
* a new client per round. For instance this is used in the keyspace
|
||||
* notifications, timers and cluster messages callbacks. */
|
||||
static client *moduleFreeContextReusedClient;
|
||||
|
||||
/* Data structures related to the exported dictionary data structure. */
|
||||
typedef struct RedisModuleDict {
|
||||
rax *rax; /* The radix tree. */
|
||||
} RedisModuleDict;
|
||||
|
||||
typedef struct RedisModuleDictIter {
|
||||
RedisModuleDict *dict;
|
||||
raxIterator ri;
|
||||
} RedisModuleDictIter;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
@@ -256,6 +269,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx);
|
||||
void RM_ZsetRangeStop(RedisModuleKey *kp);
|
||||
static void zsetKeyReset(RedisModuleKey *key);
|
||||
void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Heap allocation raw functions
|
||||
@@ -474,7 +488,7 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrappered around MULTI/EXEC. */
|
||||
* emits is always wrapped around MULTI/EXEC. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
@@ -548,7 +562,7 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
|
||||
ctx->keys_pos[ctx->keys_count++] = pos;
|
||||
}
|
||||
|
||||
/* Helper for RM_CreateCommand(). Truns a string representing command
|
||||
/* Helper for RM_CreateCommand(). Turns a string representing command
|
||||
* flags into the command flags used by the Redis core.
|
||||
*
|
||||
* It returns the set of flags, or -1 if unknown flags are found. */
|
||||
@@ -595,7 +609,7 @@ int commandFlagsFromString(char *s) {
|
||||
* And is supposed to always return REDISMODULE_OK.
|
||||
*
|
||||
* The set of flags 'strflags' specify the behavior of the command, and should
|
||||
* be passed as a C string compoesd of space separated words, like for
|
||||
* be passed as a C string composed of space separated words, like for
|
||||
* example "write deny-oom". The set of flags are:
|
||||
*
|
||||
* * **"write"**: The command may modify the data set (it may also read
|
||||
@@ -616,7 +630,7 @@ int commandFlagsFromString(char *s) {
|
||||
* * **"allow-stale"**: The command is allowed to run on slaves that don't
|
||||
* serve stale data. Don't use if you don't know what
|
||||
* this means.
|
||||
* * **"no-monitor"**: Don't propoagate the command on monitor. Use this if
|
||||
* * **"no-monitor"**: Don't propagate the command on monitor. Use this if
|
||||
* the command has sensible data among the arguments.
|
||||
* * **"fast"**: The command time complexity is not greater
|
||||
* than O(log(N)) where N is the size of the collection or
|
||||
@@ -777,6 +791,7 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
|
||||
case REDISMODULE_AM_STRING: decrRefCount(ptr); break;
|
||||
case REDISMODULE_AM_REPLY: RM_FreeCallReply(ptr); break;
|
||||
case REDISMODULE_AM_KEY: RM_CloseKey(ptr); break;
|
||||
case REDISMODULE_AM_DICT: RM_FreeDict(NULL,ptr); break;
|
||||
}
|
||||
}
|
||||
ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
|
||||
@@ -794,19 +809,26 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
|
||||
* with RedisModule_FreeString(), unless automatic memory is enabled.
|
||||
*
|
||||
* The string is created by copying the `len` bytes starting
|
||||
* at `ptr`. No reference is retained to the passed buffer. */
|
||||
* at `ptr`. No reference is retained to the passed buffer.
|
||||
*
|
||||
* The module context 'ctx' is optional and may be NULL if you want to create
|
||||
* a string out of the context scope. However in that case, the automatic
|
||||
* memory management will not be available, and the string memory must be
|
||||
* managed manually. */
|
||||
RedisModuleString *RM_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len) {
|
||||
RedisModuleString *o = createStringObject(ptr,len);
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/* Create a new module string object from a printf format and arguments.
|
||||
* The returned string must be freed with RedisModule_FreeString(), unless
|
||||
* automatic memory is enabled.
|
||||
*
|
||||
* The string is created using the sds formatter function sdscatvprintf(). */
|
||||
* The string is created using the sds formatter function sdscatvprintf().
|
||||
*
|
||||
* The passed context 'ctx' may be NULL if necessary, see the
|
||||
* RedisModule_CreateString() documentation for more info. */
|
||||
RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, ...) {
|
||||
sds s = sdsempty();
|
||||
|
||||
@@ -816,7 +838,7 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
|
||||
va_end(ap);
|
||||
|
||||
RedisModuleString *o = createObject(OBJ_STRING, s);
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
|
||||
return o;
|
||||
}
|
||||
@@ -826,7 +848,10 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
|
||||
* integer instead of taking a buffer and its length.
|
||||
*
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
* enabling automatic memory management. */
|
||||
* enabling automatic memory management.
|
||||
*
|
||||
* The passed context 'ctx' may be NULL if necessary, see the
|
||||
* RedisModule_CreateString() documentation for more info. */
|
||||
RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
size_t len = ll2string(buf,sizeof(buf),ll);
|
||||
@@ -837,10 +862,13 @@ RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll
|
||||
* RedisModuleString.
|
||||
*
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
* enabling automatic memory management. */
|
||||
* enabling automatic memory management.
|
||||
*
|
||||
* The passed context 'ctx' may be NULL if necessary, see the
|
||||
* RedisModule_CreateString() documentation for more info. */
|
||||
RedisModuleString *RM_CreateStringFromString(RedisModuleCtx *ctx, const RedisModuleString *str) {
|
||||
RedisModuleString *o = dupStringObject(str);
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -849,10 +877,16 @@ RedisModuleString *RM_CreateStringFromString(RedisModuleCtx *ctx, const RedisMod
|
||||
*
|
||||
* It is possible to call this function even when automatic memory management
|
||||
* is enabled. In that case the string will be released ASAP and removed
|
||||
* from the pool of string to release at the end. */
|
||||
* from the pool of string to release at the end.
|
||||
*
|
||||
* If the string was created with a NULL context 'ctx', it is also possible to
|
||||
* pass ctx as NULL when releasing the string (but passing a context will not
|
||||
* create any issue). Strings created with a context should be freed also passing
|
||||
* the context, so if you want to free a string out of context later, make sure
|
||||
* to create it using a NULL context. */
|
||||
void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
decrRefCount(str);
|
||||
autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str);
|
||||
if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str);
|
||||
}
|
||||
|
||||
/* Every call to this function, will make the string 'str' requiring
|
||||
@@ -876,9 +910,11 @@ void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
* Note that when memory management is turned off, you don't need
|
||||
* any call to RetainString() since creating a string will always result
|
||||
* into a string that lives after the callback function returns, if
|
||||
* no FreeString() call is performed. */
|
||||
* no FreeString() call is performed.
|
||||
*
|
||||
* It is possible to call this function with a NULL context. */
|
||||
void RM_RetainString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
if (!autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str)) {
|
||||
if (ctx == NULL || !autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str)) {
|
||||
/* Increment the string reference counting only if we can't
|
||||
* just remove the object from the list of objects that should
|
||||
* be reclaimed. Why we do that, instead of just incrementing
|
||||
@@ -956,9 +992,9 @@ RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
/* Append the specified buffere to the string 'str'. The string must be a
|
||||
/* Append the specified buffer to the string 'str'. The string must be a
|
||||
* string created by the user that is referenced only a single time, otherwise
|
||||
* REDISMODULE_ERR is returend and the operation is not performed. */
|
||||
* REDISMODULE_ERR is returned and the operation is not performed. */
|
||||
int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) {
|
||||
UNUSED(ctx);
|
||||
str = moduleAssertUnsharedString(str);
|
||||
@@ -1118,7 +1154,7 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
*
|
||||
* Note that in the above example there is no reason to postpone the array
|
||||
* length, since we produce a fixed number of elements, but in the practice
|
||||
* the code may use an interator or other ways of creating the output so
|
||||
* the code may use an iterator or other ways of creating the output so
|
||||
* that is not easy to calculate in advance the number of elements.
|
||||
*/
|
||||
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
@@ -1410,7 +1446,7 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) {
|
||||
* to call other APIs with the key handle as argument to perform
|
||||
* operations on the key.
|
||||
*
|
||||
* The return value is the handle repesenting the key, that must be
|
||||
* The return value is the handle representing the key, that must be
|
||||
* closed with RM_CloseKey().
|
||||
*
|
||||
* If the key does not exist and WRITE mode is requested, the handle
|
||||
@@ -1664,7 +1700,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
* Key API for List type
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Push an element into a list, on head or tail depending on 'where' argumnet.
|
||||
/* Push an element into a list, on head or tail depending on 'where' argument.
|
||||
* If the key pointer is about an empty key opened for writing, the key
|
||||
* is created. On error (key opened for read-only operations or of the wrong
|
||||
* type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */
|
||||
@@ -1769,7 +1805,7 @@ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *f
|
||||
* The input and output flags, and the return value, have the same exact
|
||||
* meaning, with the only difference that this function will return
|
||||
* REDISMODULE_ERR even when 'score' is a valid double number, but adding it
|
||||
* to the existing score resuts into a NaN (not a number) condition.
|
||||
* to the existing score results into a NaN (not a number) condition.
|
||||
*
|
||||
* This function has an additional field 'newscore', if not NULL is filled
|
||||
* with the new score of the element after the increment, if no error
|
||||
@@ -2150,7 +2186,9 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
*
|
||||
* The function is variadic and the user must specify pairs of field
|
||||
* names and values, both as RedisModuleString pointers (unless the
|
||||
* CFIELD option is set, see later).
|
||||
* CFIELD option is set, see later). At the end of the field/value-ptr pairs,
|
||||
* NULL must be specified as last argument to signal the end of the arguments
|
||||
* in the variadic function.
|
||||
*
|
||||
* Example to set the hash argv[1] to the value argv[2]:
|
||||
*
|
||||
@@ -2712,9 +2750,9 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
sds proto = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
sds o = listNodeValue(listFirst(c->reply));
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
proto = sdscatsds(proto,o);
|
||||
proto = sdscatlen(proto,o->buf,o->used);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto);
|
||||
@@ -2987,7 +3025,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
|
||||
}
|
||||
|
||||
/* Assuming RedisModule_KeyType() returned REDISMODULE_KEYTYPE_MODULE on
|
||||
* the key, returns the moduel type pointer of the value stored at key.
|
||||
* the key, returns the module type pointer of the value stored at key.
|
||||
*
|
||||
* If the key is NULL, is not associated with a module type, or is empty,
|
||||
* then NULL is returned instead. */
|
||||
@@ -3287,7 +3325,7 @@ void RM_DigestAddLongLong(RedisModuleDigest *md, long long ll) {
|
||||
mixDigest(md->o,buf,len);
|
||||
}
|
||||
|
||||
/* See the doucmnetation for `RedisModule_DigestAddElement()`. */
|
||||
/* See the documentation for `RedisModule_DigestAddElement()`. */
|
||||
void RM_DigestEndSequence(RedisModuleDigest *md) {
|
||||
xorDigest(md->x,md->o,sizeof(md->o));
|
||||
memset(md->o,0,sizeof(md->o));
|
||||
@@ -3484,7 +3522,7 @@ void unblockClientFromModule(client *c) {
|
||||
* reply_timeout: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
*
|
||||
* free_privdata: called in order to free the privata data that is passed
|
||||
* free_privdata: called in order to free the private data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
*/
|
||||
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) {
|
||||
@@ -3681,7 +3719,7 @@ void moduleBlockedClientTimedOut(client *c) {
|
||||
bc->timeout_callback(&ctx,(void**)c->argv,c->argc);
|
||||
moduleFreeContext(&ctx);
|
||||
/* For timeout events, we do not want to call the disconnect callback,
|
||||
* because the blocekd client will be automatically disconnected in
|
||||
* because the blocked client will be automatically disconnected in
|
||||
* this case, and the user can still hook using the timeout callback. */
|
||||
bc->disconnect_callback = NULL;
|
||||
}
|
||||
@@ -3698,7 +3736,7 @@ int RM_IsBlockedTimeoutRequest(RedisModuleCtx *ctx) {
|
||||
return (ctx->flags & REDISMODULE_CTX_BLOCKED_TIMEOUT) != 0;
|
||||
}
|
||||
|
||||
/* Get the privata data set by RedisModule_UnblockClient() */
|
||||
/* Get the private data set by RedisModule_UnblockClient() */
|
||||
void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) {
|
||||
return ctx->blocked_privdata;
|
||||
}
|
||||
@@ -3793,11 +3831,11 @@ void moduleReleaseGIL(void) {
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Subscribe to keyspace notifications. This is a low-level version of the
|
||||
* keyspace-notifications API. A module cand register callbacks to be notified
|
||||
* keyspace-notifications API. A module can register callbacks to be notified
|
||||
* when keyspce events occur.
|
||||
*
|
||||
* Notification events are filtered by their type (string events, set events,
|
||||
* etc), and the subsriber callback receives only events that match a specific
|
||||
* etc), and the subscriber callback receives only events that match a specific
|
||||
* mask of event types.
|
||||
*
|
||||
* When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents
|
||||
@@ -3832,7 +3870,7 @@ void moduleReleaseGIL(void) {
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occurred as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable norifications in redis.conf for
|
||||
* Notice that it is not necessary to enable notifications in redis.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
@@ -3873,10 +3911,10 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
|
||||
if ((sub->event_mask & type) && sub->active == 0) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.module = sub->module;
|
||||
ctx.client = moduleKeyspaceSubscribersClient;
|
||||
ctx.client = moduleFreeContextReusedClient;
|
||||
selectDb(ctx.client, dbid);
|
||||
|
||||
/* mark the handler as activer to avoid reentrant loops.
|
||||
/* mark the handler as active to avoid reentrant loops.
|
||||
* If the subscriber performs an action triggering itself,
|
||||
* it will not be notified about it. */
|
||||
sub->active = 1;
|
||||
@@ -3936,6 +3974,8 @@ void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8
|
||||
if (r->module_id == module_id) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.module = r->module;
|
||||
ctx.client = moduleFreeContextReusedClient;
|
||||
selectDb(ctx.client, 0);
|
||||
r->callback(&ctx,sender_id,type,payload,len);
|
||||
moduleFreeContext(&ctx);
|
||||
return;
|
||||
@@ -4084,7 +4124,7 @@ size_t RM_GetClusterSize(void) {
|
||||
*
|
||||
* * REDISMODULE_NODE_MYSELF This node
|
||||
* * REDISMODULE_NODE_MASTER The node is a master
|
||||
* * REDISMODULE_NODE_SLAVE The ndoe is a slave
|
||||
* * REDISMODULE_NODE_SLAVE The node is a replica
|
||||
* * REDISMODULE_NODE_PFAIL We see the node as failing
|
||||
* * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
|
||||
* * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
|
||||
@@ -4126,6 +4166,32 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Set Redis Cluster flags in order to change the normal behavior of
|
||||
* Redis Cluster, especially with the goal of disabling certain functions.
|
||||
* This is useful for modules that use the Cluster API in order to create
|
||||
* a different distributed system, but still want to use the Redis Cluster
|
||||
* message bus. Flags that can be set:
|
||||
*
|
||||
* CLUSTER_MODULE_FLAG_NO_FAILOVER
|
||||
* CLUSTER_MODULE_FLAG_NO_REDIRECTION
|
||||
*
|
||||
* With the following effects:
|
||||
*
|
||||
* NO_FAILOVER: prevent Redis Cluster slaves to failover a failing master.
|
||||
* Also disables the replica migration feature.
|
||||
*
|
||||
* NO_REDIRECTION: Every node will accept any key, without trying to perform
|
||||
* partitioning according to the user Redis Cluster algorithm.
|
||||
* Slots informations will still be propagated across the
|
||||
* cluster, but without effects. */
|
||||
void RM_SetClusterFlags(RedisModuleCtx *ctx, uint64_t flags) {
|
||||
UNUSED(ctx);
|
||||
if (flags & REDISMODULE_CLUSTER_FLAG_NO_FAILOVER)
|
||||
server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_FAILOVER;
|
||||
if (flags & REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION)
|
||||
server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_REDIRECTION;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules Timers API
|
||||
*
|
||||
@@ -4180,6 +4246,8 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
ctx.module = timer->module;
|
||||
ctx.client = moduleFreeContextReusedClient;
|
||||
selectDb(ctx.client, 0);
|
||||
timer->callback(&ctx,timer->data);
|
||||
moduleFreeContext(&ctx);
|
||||
raxRemove(Timers,(unsigned char*)ri.key,ri.key_len,NULL);
|
||||
@@ -4243,7 +4311,7 @@ RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisMod
|
||||
}
|
||||
|
||||
/* Stop a timer, returns REDISMODULE_OK if the timer was found, belonged to the
|
||||
* calling module, and was stoped, otherwise REDISMODULE_ERR is returned.
|
||||
* calling module, and was stopped, otherwise REDISMODULE_ERR is returned.
|
||||
* If not NULL, the data pointer is set to the value of the data argument when
|
||||
* the timer was created. */
|
||||
int RM_StopTimer(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) {
|
||||
@@ -4260,7 +4328,7 @@ int RM_StopTimer(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) {
|
||||
* (in milliseconds), and the private data pointer associated with the timer.
|
||||
* If the timer specified does not exist or belongs to a different module
|
||||
* no information is returned and the function returns REDISMODULE_ERR, otherwise
|
||||
* REDISMODULE_OK is returned. The argumnets remaining or data can be NULL if
|
||||
* REDISMODULE_OK is returned. The arguments remaining or data can be NULL if
|
||||
* the caller does not need certain information. */
|
||||
int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) {
|
||||
RedisModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id));
|
||||
@@ -4275,6 +4343,257 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules Dictionary API
|
||||
*
|
||||
* Implements a sorted dictionary (actually backed by a radix tree) with
|
||||
* the usual get / set / del / num-items API, together with an iterator
|
||||
* capable of going back and forth.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Create a new dictionary. The 'ctx' pointer can be the current module context
|
||||
* or NULL, depending on what you want. Please follow the following rules:
|
||||
*
|
||||
* 1. Use a NULL context if you plan to retain a reference to this dictionary
|
||||
* that will survive the time of the module callback where you created it.
|
||||
* 2. Use a NULL context if no context is available at the time you are creating
|
||||
* the dictionary (of course...).
|
||||
* 3. However use the current callback context as 'ctx' argument if the
|
||||
* dictionary time to live is just limited to the callback scope. In this
|
||||
* case, if enabled, you can enjoy the automatic memory management that will
|
||||
* reclaim the dictionary memory, as well as the strings returned by the
|
||||
* Next / Prev dictionary iterator calls.
|
||||
*/
|
||||
RedisModuleDict *RM_CreateDict(RedisModuleCtx *ctx) {
|
||||
struct RedisModuleDict *d = zmalloc(sizeof(*d));
|
||||
d->rax = raxNew();
|
||||
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_DICT,d);
|
||||
return d;
|
||||
}
|
||||
|
||||
/* Free a dictionary created with RM_CreateDict(). You need to pass the
|
||||
* context pointer 'ctx' only if the dictionary was created using the
|
||||
* context instead of passing NULL. */
|
||||
void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d) {
|
||||
if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_DICT,d);
|
||||
raxFree(d->rax);
|
||||
zfree(d);
|
||||
}
|
||||
|
||||
/* Return the size of the dictionary (number of keys). */
|
||||
uint64_t RM_DictSize(RedisModuleDict *d) {
|
||||
return raxSize(d->rax);
|
||||
}
|
||||
|
||||
/* Store the specified key into the dictionary, setting its value to the
|
||||
* pointer 'ptr'. If the key was added with success, since it did not
|
||||
* already exist, REDISMODULE_OK is returned. Otherwise if the key already
|
||||
* exists the function returns REDISMODULE_ERR. */
|
||||
int RM_DictSetC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) {
|
||||
int retval = raxTryInsert(d->rax,key,keylen,ptr,NULL);
|
||||
return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictSetC() but will replace the key with the new
|
||||
* value if the key already exists. */
|
||||
int RM_DictReplaceC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) {
|
||||
int retval = raxInsert(d->rax,key,keylen,ptr,NULL);
|
||||
return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictSetC() but takes the key as a RedisModuleString. */
|
||||
int RM_DictSet(RedisModuleDict *d, RedisModuleString *key, void *ptr) {
|
||||
return RM_DictSetC(d,key->ptr,sdslen(key->ptr),ptr);
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictReplaceC() but takes the key as a RedisModuleString. */
|
||||
int RM_DictReplace(RedisModuleDict *d, RedisModuleString *key, void *ptr) {
|
||||
return RM_DictReplaceC(d,key->ptr,sdslen(key->ptr),ptr);
|
||||
}
|
||||
|
||||
/* Return the value stored at the specified key. The function returns NULL
|
||||
* both in the case the key does not exist, or if you actually stored
|
||||
* NULL at key. So, optionally, if the 'nokey' pointer is not NULL, it will
|
||||
* be set by reference to 1 if the key does not exist, or to 0 if the key
|
||||
* exists. */
|
||||
void *RM_DictGetC(RedisModuleDict *d, void *key, size_t keylen, int *nokey) {
|
||||
void *res = raxFind(d->rax,key,keylen);
|
||||
if (nokey) *nokey = (res == raxNotFound);
|
||||
return (res == raxNotFound) ? NULL : res;
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictGetC() but takes the key as a RedisModuleString. */
|
||||
void *RM_DictGet(RedisModuleDict *d, RedisModuleString *key, int *nokey) {
|
||||
return RM_DictGetC(d,key->ptr,sdslen(key->ptr),nokey);
|
||||
}
|
||||
|
||||
/* Remove the specified key from the dictionary, returning REDISMODULE_OK if
|
||||
* the key was found and delted, or REDISMODULE_ERR if instead there was
|
||||
* no such key in the dictionary. When the operation is successful, if
|
||||
* 'oldval' is not NULL, then '*oldval' is set to the value stored at the
|
||||
* key before it was deleted. Using this feature it is possible to get
|
||||
* a pointer to the value (for instance in order to release it), without
|
||||
* having to call RedisModule_DictGet() before deleting the key. */
|
||||
int RM_DictDelC(RedisModuleDict *d, void *key, size_t keylen, void *oldval) {
|
||||
int retval = raxRemove(d->rax,key,keylen,oldval);
|
||||
return retval ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictDelC() but gets the key as a RedisModuleString. */
|
||||
int RM_DictDel(RedisModuleDict *d, RedisModuleString *key, void *oldval) {
|
||||
return RM_DictDelC(d,key->ptr,sdslen(key->ptr),oldval);
|
||||
}
|
||||
|
||||
/* Return an interator, setup in order to start iterating from the specified
|
||||
* key by applying the operator 'op', which is just a string specifying the
|
||||
* comparison operator to use in order to seek the first element. The
|
||||
* operators avalable are:
|
||||
*
|
||||
* "^" -- Seek the first (lexicographically smaller) key.
|
||||
* "$" -- Seek the last (lexicographically biffer) key.
|
||||
* ">" -- Seek the first element greter than the specified key.
|
||||
* ">=" -- Seek the first element greater or equal than the specified key.
|
||||
* "<" -- Seek the first element smaller than the specified key.
|
||||
* "<=" -- Seek the first element smaller or equal than the specified key.
|
||||
* "==" -- Seek the first element matching exactly the specified key.
|
||||
*
|
||||
* Note that for "^" and "$" the passed key is not used, and the user may
|
||||
* just pass NULL with a length of 0.
|
||||
*
|
||||
* If the element to start the iteration cannot be seeked based on the
|
||||
* key and operator passed, RedisModule_DictNext() / Prev() will just return
|
||||
* REDISMODULE_ERR at the first call, otherwise they'll produce elements.
|
||||
*/
|
||||
RedisModuleDictIter *RM_DictIteratorStartC(RedisModuleDict *d, const char *op, void *key, size_t keylen) {
|
||||
RedisModuleDictIter *di = zmalloc(sizeof(*di));
|
||||
di->dict = d;
|
||||
raxStart(&di->ri,d->rax);
|
||||
raxSeek(&di->ri,op,key,keylen);
|
||||
return di;
|
||||
}
|
||||
|
||||
/* Exactly like RedisModule_DictIteratorStartC, but the key is passed as a
|
||||
* RedisModuleString. */
|
||||
RedisModuleDictIter *RM_DictIteratorStart(RedisModuleDict *d, const char *op, RedisModuleString *key) {
|
||||
return RM_DictIteratorStartC(d,op,key->ptr,sdslen(key->ptr));
|
||||
}
|
||||
|
||||
/* Release the iterator created with RedisModule_DictIteratorStart(). This call
|
||||
* is mandatory otherwise a memory leak is introduced in the module. */
|
||||
void RM_DictIteratorStop(RedisModuleDictIter *di) {
|
||||
raxStop(&di->ri);
|
||||
zfree(di);
|
||||
}
|
||||
|
||||
/* After its creation with RedisModule_DictIteratorStart(), it is possible to
|
||||
* change the currently selected element of the iterator by using this
|
||||
* API call. The result based on the operator and key is exactly like
|
||||
* the function RedisModule_DictIteratorStart(), however in this case the
|
||||
* return value is just REDISMODULE_OK in case the seeked element was found,
|
||||
* or REDISMODULE_ERR in case it was not possible to seek the specified
|
||||
* element. It is possible to reseek an iterator as many times as you want. */
|
||||
int RM_DictIteratorReseekC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) {
|
||||
return raxSeek(&di->ri,op,key,keylen);
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictIteratorReseekC() but takes the key as as a
|
||||
* RedisModuleString. */
|
||||
int RM_DictIteratorReseek(RedisModuleDictIter *di, const char *op, RedisModuleString *key) {
|
||||
return RM_DictIteratorReseekC(di,op,key->ptr,sdslen(key->ptr));
|
||||
}
|
||||
|
||||
/* Return the current item of the dictionary iterator 'di' and steps to the
|
||||
* next element. If the iterator already yield the last element and there
|
||||
* are no other elements to return, NULL is returned, otherwise a pointer
|
||||
* to a string representing the key is provided, and the '*keylen' length
|
||||
* is set by reference (if keylen is not NULL). The '*dataptr', if not NULL
|
||||
* is set to the value of the pointer stored at the returned key as auxiliary
|
||||
* data (as set by the RedisModule_DictSet API).
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* ... create the iterator here ...
|
||||
* char *key;
|
||||
* void *data;
|
||||
* while((key = RedisModule_DictNextC(iter,&keylen,&data)) != NULL) {
|
||||
* printf("%.*s %p\n", (int)keylen, key, data);
|
||||
* }
|
||||
*
|
||||
* The returned pointer is of type void because sometimes it makes sense
|
||||
* to cast it to a char* sometimes to an unsigned char* depending on the
|
||||
* fact it contains or not binary data, so this API ends being more
|
||||
* comfortable to use.
|
||||
*
|
||||
* The validity of the returned pointer is until the next call to the
|
||||
* next/prev iterator step. Also the pointer is no longer valid once the
|
||||
* iterator is released. */
|
||||
void *RM_DictNextC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) {
|
||||
if (!raxNext(&di->ri)) return NULL;
|
||||
if (keylen) *keylen = di->ri.key_len;
|
||||
if (dataptr) *dataptr = di->ri.data;
|
||||
return di->ri.key;
|
||||
}
|
||||
|
||||
/* This function is exactly like RedisModule_DictNext() but after returning
|
||||
* the currently selected element in the iterator, it selects the previous
|
||||
* element (laxicographically smaller) instead of the next one. */
|
||||
void *RM_DictPrevC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) {
|
||||
if (!raxPrev(&di->ri)) return NULL;
|
||||
if (keylen) *keylen = di->ri.key_len;
|
||||
if (dataptr) *dataptr = di->ri.data;
|
||||
return di->ri.key;
|
||||
}
|
||||
|
||||
/* Like RedisModuleNextC(), but instead of returning an internally allocated
|
||||
* buffer and key length, it returns directly a module string object allocated
|
||||
* in the specified context 'ctx' (that may be NULL exactly like for the main
|
||||
* API RedisModule_CreateString).
|
||||
*
|
||||
* The returned string object should be deallocated after use, either manually
|
||||
* or by using a context that has automatic memory management active. */
|
||||
RedisModuleString *RM_DictNext(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) {
|
||||
size_t keylen;
|
||||
void *key = RM_DictNextC(di,&keylen,dataptr);
|
||||
if (key == NULL) return NULL;
|
||||
return RM_CreateString(ctx,key,keylen);
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictNext() but after returning the currently selected
|
||||
* element in the iterator, it selects the previous element (laxicographically
|
||||
* smaller) instead of the next one. */
|
||||
RedisModuleString *RM_DictPrev(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) {
|
||||
size_t keylen;
|
||||
void *key = RM_DictPrevC(di,&keylen,dataptr);
|
||||
if (key == NULL) return NULL;
|
||||
return RM_CreateString(ctx,key,keylen);
|
||||
}
|
||||
|
||||
/* Compare the element currently pointed by the iterator to the specified
|
||||
* element given by key/keylen, according to the operator 'op' (the set of
|
||||
* valid operators are the same valid for RedisModule_DictIteratorStart).
|
||||
* If the comparision is successful the command returns REDISMODULE_OK
|
||||
* otherwise REDISMODULE_ERR is returned.
|
||||
*
|
||||
* This is useful when we want to just emit a lexicographical range, so
|
||||
* in the loop, as we iterate elements, we can also check if we are still
|
||||
* on range.
|
||||
*
|
||||
* The function returne REDISMODULE_ERR if the iterator reached the
|
||||
* end of elements condition as well. */
|
||||
int RM_DictCompareC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) {
|
||||
if (raxEOF(&di->ri)) return REDISMODULE_ERR;
|
||||
int res = raxCompare(&di->ri,op,key,keylen);
|
||||
return res ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Like RedisModule_DictCompareC but gets the key to compare with the current
|
||||
* iterator key as a RedisModuleString. */
|
||||
int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *key) {
|
||||
if (raxEOF(&di->ri)) return REDISMODULE_ERR;
|
||||
int res = raxCompare(&di->ri,op,key->ptr,sdslen(key->ptr));
|
||||
return res ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules utility APIs
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -4336,8 +4655,8 @@ void moduleInitModulesSystem(void) {
|
||||
|
||||
/* Set up the keyspace notification susbscriber list and static client */
|
||||
moduleKeyspaceSubscribers = listCreate();
|
||||
moduleKeyspaceSubscribersClient = createClient(-1);
|
||||
moduleKeyspaceSubscribersClient->flags |= CLIENT_MODULE;
|
||||
moduleFreeContextReusedClient = createClient(-1);
|
||||
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
@@ -4475,7 +4794,7 @@ int moduleUnload(sds name) {
|
||||
|
||||
moduleUnregisterCommands(module);
|
||||
|
||||
/* Remvoe any noification subscribers this module might have */
|
||||
/* Remove any notification subscribers this module might have */
|
||||
moduleUnsubscribeNotifications(module);
|
||||
|
||||
/* Unregister all the hooks. TODO: Yet no hooks support here. */
|
||||
@@ -4700,4 +5019,27 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(BlockedClientDisconnected);
|
||||
REGISTER_API(SetDisconnectCallback);
|
||||
REGISTER_API(GetBlockedClientHandle);
|
||||
REGISTER_API(SetClusterFlags);
|
||||
REGISTER_API(CreateDict);
|
||||
REGISTER_API(FreeDict);
|
||||
REGISTER_API(DictSize);
|
||||
REGISTER_API(DictSetC);
|
||||
REGISTER_API(DictReplaceC);
|
||||
REGISTER_API(DictSet);
|
||||
REGISTER_API(DictReplace);
|
||||
REGISTER_API(DictGetC);
|
||||
REGISTER_API(DictGet);
|
||||
REGISTER_API(DictDelC);
|
||||
REGISTER_API(DictDel);
|
||||
REGISTER_API(DictIteratorStartC);
|
||||
REGISTER_API(DictIteratorStart);
|
||||
REGISTER_API(DictIteratorStop);
|
||||
REGISTER_API(DictIteratorReseekC);
|
||||
REGISTER_API(DictIteratorReseek);
|
||||
REGISTER_API(DictNextC);
|
||||
REGISTER_API(DictPrevC);
|
||||
REGISTER_API(DictNext);
|
||||
REGISTER_API(DictPrev);
|
||||
REGISTER_API(DictCompareC);
|
||||
REGISTER_API(DictCompare);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -43,6 +43,11 @@ hellotimer.xo: ../redismodule.h
|
||||
hellotimer.so: hellotimer.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
|
||||
@@ -77,7 +77,7 @@ void *HelloBlock_ThreadMain(void *arg) {
|
||||
/* An example blocked client disconnection callback.
|
||||
*
|
||||
* Note that in the case of the HELLO.BLOCK command, the blocked client is now
|
||||
* owned by the thread calling sleep(). In this speciifc case, there is not
|
||||
* owned by the thread calling sleep(). In this specific case, there is not
|
||||
* much we can do, however normally we could instead implement a way to
|
||||
* signal the thread that the client disconnected, and sleep the specified
|
||||
* amount of seconds with a while loop calling sleep(1), so that once we
|
||||
|
||||
@@ -69,7 +69,7 @@ int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
|
||||
RedisModule_ReplyWithLongLong(ctx,port);
|
||||
}
|
||||
RedisModule_FreeClusterNodesList(ids);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Callback for message MSGTYPE_PING */
|
||||
@@ -77,6 +77,7 @@ void PingReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, cons
|
||||
RedisModule_Log(ctx,"notice","PING (type %d) RECEIVED from %.*s: '%.*s'",
|
||||
type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload);
|
||||
RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PONG,(unsigned char*)"Ohi!",4);
|
||||
RedisModule_Call(ctx, "INCR", "c", "pings_received");
|
||||
}
|
||||
|
||||
/* Callback for message MSGTYPE_PONG. */
|
||||
@@ -102,6 +103,15 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
ListCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
/* Disable Redis Cluster sharding and redirections. This way every node
|
||||
* will be able to access every possible key, regardless of the hash slot.
|
||||
* This way the PING message handler will be able to increment a specific
|
||||
* variable. Normally you do that in order for the distributed system
|
||||
* you create as a module to have total freedom in the keyspace
|
||||
* manipulation. */
|
||||
RedisModule_SetClusterFlags(ctx,REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION);
|
||||
|
||||
/* Register our handlers for different message types. */
|
||||
RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PING,PingReceiver);
|
||||
RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PONG,PongReceiver);
|
||||
return REDISMODULE_OK;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/* Hellodict -- An example of modules dictionary API
|
||||
*
|
||||
* This module implements a volatile key-value store on top of the
|
||||
* dictionary exported by the Redis modules API.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleDict *Keyspace;
|
||||
|
||||
/* HELLODICT.SET <key> <value>
|
||||
*
|
||||
* Set the specified key to the specified value. */
|
||||
int cmd_SET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
RedisModule_DictSet(Keyspace,argv[1],argv[2]);
|
||||
/* We need to keep a reference to the value stored at the key, otherwise
|
||||
* it would be freed when this callback returns. */
|
||||
RedisModule_RetainString(NULL,argv[2]);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* HELLODICT.GET <key>
|
||||
*
|
||||
* Return the value of the specified key, or a null reply if the key
|
||||
* is not defined. */
|
||||
int cmd_GET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
RedisModuleString *val = RedisModule_DictGet(Keyspace,argv[1],NULL);
|
||||
if (val == NULL) {
|
||||
return RedisModule_ReplyWithNull(ctx);
|
||||
} else {
|
||||
return RedisModule_ReplyWithString(ctx, val);
|
||||
}
|
||||
}
|
||||
|
||||
/* HELLODICT.KEYRANGE <startkey> <endkey> <count>
|
||||
*
|
||||
* Return a list of matching keys, lexicographically between startkey
|
||||
* and endkey. No more than 'count' items are emitted. */
|
||||
int cmd_KEYRANGE(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||
|
||||
/* Parse the count argument. */
|
||||
long long count;
|
||||
if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
}
|
||||
|
||||
/* Seek the iterator. */
|
||||
RedisModuleDictIter *iter = RedisModule_DictIteratorStart(
|
||||
Keyspace, ">=", argv[1]);
|
||||
|
||||
/* Reply with the matching items. */
|
||||
char *key;
|
||||
size_t keylen;
|
||||
long long replylen = 0; /* Keep track of the amitted array len. */
|
||||
RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
while((key = RedisModule_DictNextC(iter,&keylen,NULL)) != NULL) {
|
||||
if (replylen >= count) break;
|
||||
if (RedisModule_DictCompare(iter,"<=",argv[2]) == REDISMODULE_ERR)
|
||||
break;
|
||||
RedisModule_ReplyWithStringBuffer(ctx,key,keylen);
|
||||
replylen++;
|
||||
}
|
||||
RedisModule_ReplySetArrayLength(ctx,replylen);
|
||||
|
||||
/* Cleanup. */
|
||||
RedisModule_DictIteratorStop(iter);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"hellodict",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.set",
|
||||
cmd_SET,"write deny-oom",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.get",
|
||||
cmd_GET,"readonly",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.keyrange",
|
||||
cmd_KEYRANGE,"readonly",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
/* Create our global dictionray. Here we'll set our keys and values. */
|
||||
Keyspace = RedisModule_CreateDict(NULL);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Helloworld cluster -- A ping/pong cluster API example.
|
||||
/* Timer API example -- Register and handle timer events
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
@@ -37,9 +37,6 @@
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MSGTYPE_PING 1
|
||||
#define MSGTYPE_PONG 2
|
||||
|
||||
/* Timer callback. */
|
||||
void timerHandler(RedisModuleCtx *ctx, void *data) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ void execCommand(client *c) {
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,CMD_CALL_FULL);
|
||||
call(c,CMD_CALL_FULL|CMD_CALL_NOQUEUE);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
|
||||
+200
-134
@@ -33,7 +33,7 @@
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static void setProtocolError(const char *errstr, client *c, long pos);
|
||||
static void setProtocolError(const char *errstr, client *c);
|
||||
|
||||
/* Return the size consumed from the allocator, for the specified SDS string,
|
||||
* including internal fragmentation. This function is used in order to compute
|
||||
@@ -56,11 +56,14 @@ size_t getStringObjectSdsUsedMemory(robj *o) {
|
||||
|
||||
/* Client.reply list dup and free methods. */
|
||||
void *dupClientReplyValue(void *o) {
|
||||
return sdsdup(o);
|
||||
clientReplyBlock *old = o;
|
||||
clientReplyBlock *buf = zmalloc(sizeof(clientReplyBlock) + old->size);
|
||||
memcpy(buf, o, sizeof(clientReplyBlock) + old->size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void freeClientReplyValue(void *o) {
|
||||
sdsfree(o);
|
||||
zfree(o);
|
||||
}
|
||||
|
||||
int listMatchObjects(void *a, void *b) {
|
||||
@@ -107,6 +110,7 @@ client *createClient(int fd) {
|
||||
c->fd = fd;
|
||||
c->name = NULL;
|
||||
c->bufpos = 0;
|
||||
c->qb_pos = 0;
|
||||
c->querybuf = sdsempty();
|
||||
c->pending_querybuf = sdsempty();
|
||||
c->querybuf_peak = 0;
|
||||
@@ -140,6 +144,7 @@ client *createClient(int fd) {
|
||||
c->bpop.target = NULL;
|
||||
c->bpop.xread_group = NULL;
|
||||
c->bpop.xread_consumer = NULL;
|
||||
c->bpop.xread_group_noack = 0;
|
||||
c->bpop.numreplicas = 0;
|
||||
c->bpop.reploffset = 0;
|
||||
c->woff = 0;
|
||||
@@ -239,25 +244,35 @@ int _addReplyToBuffer(client *c, const char *s, size_t len) {
|
||||
void _addReplyStringToList(client *c, const char *s, size_t len) {
|
||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
|
||||
|
||||
if (listLength(c->reply) == 0) {
|
||||
sds node = sdsnewlen(s,len);
|
||||
listAddNodeTail(c->reply,node);
|
||||
c->reply_bytes += len;
|
||||
} else {
|
||||
listNode *ln = listLast(c->reply);
|
||||
sds tail = listNodeValue(ln);
|
||||
listNode *ln = listLast(c->reply);
|
||||
clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
|
||||
|
||||
/* Append to this object when possible. If tail == NULL it was
|
||||
* set via addDeferredMultiBulkLength(). */
|
||||
if (tail && (sdsavail(tail) >= len || sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES)) {
|
||||
tail = sdscatlen(tail,s,len);
|
||||
listNodeValue(ln) = tail;
|
||||
c->reply_bytes += len;
|
||||
} else {
|
||||
sds node = sdsnewlen(s,len);
|
||||
listAddNodeTail(c->reply,node);
|
||||
c->reply_bytes += len;
|
||||
}
|
||||
/* Note that 'tail' may be NULL even if we have a tail node, becuase when
|
||||
* addDeferredMultiBulkLength() is used, it sets a dummy node to NULL just
|
||||
* fo fill it later, when the size of the bulk length is set. */
|
||||
|
||||
/* Append to tail string when possible. */
|
||||
if (tail) {
|
||||
/* Copy the part we can fit into the tail, and leave the rest for a
|
||||
* new node */
|
||||
size_t avail = tail->size - tail->used;
|
||||
size_t copy = avail >= len? len: avail;
|
||||
memcpy(tail->buf + tail->used, s, copy);
|
||||
tail->used += copy;
|
||||
s += copy;
|
||||
len -= copy;
|
||||
}
|
||||
if (len) {
|
||||
/* Create a new node, make sure it is allocated to at
|
||||
* least PROTO_REPLY_CHUNK_BYTES */
|
||||
size_t size = len < PROTO_REPLY_CHUNK_BYTES? PROTO_REPLY_CHUNK_BYTES: len;
|
||||
tail = zmalloc(size + sizeof(clientReplyBlock));
|
||||
/* take over the allocation's internal fragmentation */
|
||||
tail->size = zmalloc_usable(tail) - sizeof(clientReplyBlock);
|
||||
tail->used = len;
|
||||
memcpy(tail->buf, s, len);
|
||||
listAddNodeTail(c->reply, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
@@ -328,11 +343,30 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
if (!len || s[0] != '-') addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
|
||||
/* Sometimes it could be normal that a slave replies to a master with
|
||||
* an error and this function gets called. Actually the error will never
|
||||
* be sent because addReply*() against master clients has no effect...
|
||||
* A notable example is:
|
||||
*
|
||||
* EVAL 'redis.call("incr",KEYS[1]); redis.call("nonexisting")' 1 x
|
||||
*
|
||||
* Where the master must propagate the first change even if the second
|
||||
* will produce an error. However it is useful to log such events since
|
||||
* they are rare and may hint at errors in a script or a bug in Redis. */
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE)) {
|
||||
char* to = c->flags & CLIENT_MASTER? "master": "replica";
|
||||
char* from = c->flags & CLIENT_MASTER? "replica": "master";
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This slave is sending an error "
|
||||
"to its master: '%s' after processing the command "
|
||||
"'%s'", s, cmdname);
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
/* Here we want to panic because when a master is sending an
|
||||
* error to some slave in the context of replication, this can
|
||||
* only create some kind of offset or data desynchronization. Better
|
||||
* to catch it ASAP and crash instead of continuing. */
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
serverPanic("Continuing is unsafe: replication protocol violation.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,26 +423,41 @@ void *addDeferredMultiBulkLength(client *c) {
|
||||
/* Populate the length object and try gluing it to the next chunk. */
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length) {
|
||||
listNode *ln = (listNode*)node;
|
||||
sds len, next;
|
||||
clientReplyBlock *next;
|
||||
char lenstr[128];
|
||||
size_t lenstr_len = sprintf(lenstr, "*%ld\r\n", length);
|
||||
|
||||
/* Abort when *node is NULL: when the client should not accept writes
|
||||
* we return NULL in addDeferredMultiBulkLength() */
|
||||
if (node == NULL) return;
|
||||
serverAssert(!listNodeValue(ln));
|
||||
|
||||
len = sdscatprintf(sdsnewlen("*",1),"%ld\r\n",length);
|
||||
listNodeValue(ln) = len;
|
||||
c->reply_bytes += sdslen(len);
|
||||
if (ln->next != NULL) {
|
||||
next = listNodeValue(ln->next);
|
||||
|
||||
/* Only glue when the next node is non-NULL (an sds in this case) */
|
||||
if (next != NULL) {
|
||||
len = sdscatsds(len,next);
|
||||
listDelNode(c->reply,ln->next);
|
||||
listNodeValue(ln) = len;
|
||||
/* No need to update c->reply_bytes: we are just moving the same
|
||||
* amount of bytes from one node to another. */
|
||||
}
|
||||
/* Normally we fill this dummy NULL node, added by addDeferredMultiBulkLength(),
|
||||
* with a new buffer structure containing the protocol needed to specify
|
||||
* the length of the array following. However sometimes when there is
|
||||
* little memory to move, we may instead remove this NULL node, and prefix
|
||||
* our protocol in the node immediately after to it, in order to save a
|
||||
* write(2) syscall later. Conditions needed to do it:
|
||||
*
|
||||
* - The next node is non-NULL,
|
||||
* - It has enough room already allocated
|
||||
* - And not too large (avoid large memmove) */
|
||||
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
|
||||
next->size - next->used >= lenstr_len &&
|
||||
next->used < PROTO_REPLY_CHUNK_BYTES * 4) {
|
||||
memmove(next->buf + lenstr_len, next->buf, next->used);
|
||||
memcpy(next->buf, lenstr, lenstr_len);
|
||||
next->used += lenstr_len;
|
||||
listDelNode(c->reply,ln);
|
||||
} else {
|
||||
/* Create a new node */
|
||||
clientReplyBlock *buf = zmalloc(lenstr_len + sizeof(clientReplyBlock));
|
||||
/* Take over the allocation's internal fragmentation */
|
||||
buf->size = zmalloc_usable(buf) - sizeof(clientReplyBlock);
|
||||
buf->used = lenstr_len;
|
||||
memcpy(buf->buf, lenstr, lenstr_len);
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
@@ -570,7 +619,7 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
sdstoupper(cmd);
|
||||
addReplyErrorFormat(c,
|
||||
"Unknown subcommand or wrong number of arguments for '%s'. Try %s HELP.",
|
||||
c->argv[1]->ptr,cmd);
|
||||
(char*)c->argv[1]->ptr,cmd);
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
@@ -579,6 +628,7 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
* destination client. */
|
||||
void copyClientOutputBuffer(client *dst, client *src) {
|
||||
listRelease(dst->reply);
|
||||
dst->sentlen = 0;
|
||||
dst->reply = listDup(src->reply);
|
||||
memcpy(dst->buf,src->buf,src->bufpos);
|
||||
dst->bufpos = src->bufpos;
|
||||
@@ -777,8 +827,7 @@ void freeClient(client *c) {
|
||||
serverLog(LL_WARNING,"Connection with master lost.");
|
||||
if (!(c->flags & (CLIENT_CLOSE_AFTER_REPLY|
|
||||
CLIENT_CLOSE_ASAP|
|
||||
CLIENT_BLOCKED|
|
||||
CLIENT_UNBLOCKED)))
|
||||
CLIENT_BLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
@@ -787,7 +836,7 @@ void freeClient(client *c) {
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
serverLog(LL_WARNING,"Connection with slave %s lost.",
|
||||
serverLog(LL_WARNING,"Connection with replica %s lost.",
|
||||
replicationGetSlaveName(c));
|
||||
}
|
||||
|
||||
@@ -894,7 +943,7 @@ client *lookupClientByID(uint64_t id) {
|
||||
int writeToClient(int fd, client *c, int handler_installed) {
|
||||
ssize_t nwritten = 0, totwritten = 0;
|
||||
size_t objlen;
|
||||
sds o;
|
||||
clientReplyBlock *o;
|
||||
|
||||
while(clientHasPendingReplies(c)) {
|
||||
if (c->bufpos > 0) {
|
||||
@@ -911,23 +960,24 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
}
|
||||
} else {
|
||||
o = listNodeValue(listFirst(c->reply));
|
||||
objlen = sdslen(o);
|
||||
objlen = o->used;
|
||||
|
||||
if (objlen == 0) {
|
||||
c->reply_bytes -= o->size;
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
continue;
|
||||
}
|
||||
|
||||
nwritten = write(fd, o + c->sentlen, objlen - c->sentlen);
|
||||
nwritten = write(fd, o->buf + c->sentlen, objlen - c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
/* If we fully sent the object on head go to the next one */
|
||||
if (c->sentlen == objlen) {
|
||||
c->reply_bytes -= o->size;
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes -= objlen;
|
||||
/* If there are no longer objects in the list, we expect
|
||||
* the count of reply bytes to be exactly zero. */
|
||||
if (listLength(c->reply) == 0)
|
||||
@@ -1064,34 +1114,34 @@ void resetClient(client *c) {
|
||||
* with the error and close the connection. */
|
||||
int processInlineBuffer(client *c) {
|
||||
char *newline;
|
||||
int argc, j;
|
||||
int argc, j, linefeed_chars = 1;
|
||||
sds *argv, aux;
|
||||
size_t querylen;
|
||||
|
||||
/* Search for end of line */
|
||||
newline = strchr(c->querybuf,'\n');
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\n');
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,"Protocol error: too big inline request");
|
||||
setProtocolError("too big inline request",c,0);
|
||||
setProtocolError("too big inline request",c);
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Handle the \r\n case. */
|
||||
if (newline && newline != c->querybuf && *(newline-1) == '\r')
|
||||
newline--;
|
||||
if (newline && newline != c->querybuf+c->qb_pos && *(newline-1) == '\r')
|
||||
newline--, linefeed_chars++;
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
aux = sdsnewlen(c->querybuf,querylen);
|
||||
querylen = newline-(c->querybuf+c->qb_pos);
|
||||
aux = sdsnewlen(c->querybuf+c->qb_pos,querylen);
|
||||
argv = sdssplitargs(aux,&argc);
|
||||
sdsfree(aux);
|
||||
if (argv == NULL) {
|
||||
addReplyError(c,"Protocol error: unbalanced quotes in request");
|
||||
setProtocolError("unbalanced quotes in inline request",c,0);
|
||||
setProtocolError("unbalanced quotes in inline request",c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -1101,8 +1151,8 @@ int processInlineBuffer(client *c) {
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
/* Move querybuffer position to the next query in the buffer. */
|
||||
c->qb_pos += querylen+linefeed_chars;
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (argc) {
|
||||
@@ -1123,19 +1173,19 @@ int processInlineBuffer(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
/* Helper function. Record protocol erro details in server log,
|
||||
* and set the client as CLIENT_CLOSE_AFTER_REPLY. */
|
||||
#define PROTO_DUMP_LEN 128
|
||||
static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
static void setProtocolError(const char *errstr, client *c) {
|
||||
if (server.verbosity <= LL_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
/* Sample some protocol to given an idea about what was inside. */
|
||||
char buf[256];
|
||||
if (sdslen(c->querybuf) < PROTO_DUMP_LEN) {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf);
|
||||
if (sdslen(c->querybuf)-c->qb_pos < PROTO_DUMP_LEN) {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf+c->qb_pos);
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf, sdslen(c->querybuf)-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
|
||||
snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf+c->qb_pos, sdslen(c->querybuf)-c->qb_pos-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
|
||||
}
|
||||
|
||||
/* Remove non printable chars. */
|
||||
@@ -1151,7 +1201,6 @@ static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
/* Process the query buffer for client 'c', setting up the client argument
|
||||
@@ -1167,7 +1216,6 @@ static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
|
||||
int processMultibulkBuffer(client *c) {
|
||||
char *newline = NULL;
|
||||
long pos = 0;
|
||||
int ok;
|
||||
long long ll;
|
||||
|
||||
@@ -1176,34 +1224,32 @@ int processMultibulkBuffer(client *c) {
|
||||
serverAssertWithInfo(c,NULL,c->argc == 0);
|
||||
|
||||
/* Multi bulk length cannot be read without a \r\n */
|
||||
newline = strchr(c->querybuf,'\r');
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,"Protocol error: too big mbulk count string");
|
||||
setProtocolError("too big mbulk count string",c,0);
|
||||
setProtocolError("too big mbulk count string",c);
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Buffer should also contain \n */
|
||||
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
|
||||
if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
|
||||
return C_ERR;
|
||||
|
||||
/* We know for sure there is a whole line since newline != NULL,
|
||||
* so go ahead and find out the multi bulk length. */
|
||||
serverAssertWithInfo(c,NULL,c->querybuf[0] == '*');
|
||||
ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
|
||||
serverAssertWithInfo(c,NULL,c->querybuf[c->qb_pos] == '*');
|
||||
ok = string2ll(c->querybuf+1+c->qb_pos,newline-(c->querybuf+1+c->qb_pos),&ll);
|
||||
if (!ok || ll > 1024*1024) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError("invalid mbulk count",c,pos);
|
||||
setProtocolError("invalid mbulk count",c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
pos = (newline-c->querybuf)+2;
|
||||
if (ll <= 0) {
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
return C_OK;
|
||||
}
|
||||
c->qb_pos = (newline-c->querybuf)+2;
|
||||
|
||||
if (ll <= 0) return C_OK;
|
||||
|
||||
c->multibulklen = ll;
|
||||
|
||||
@@ -1216,64 +1262,67 @@ int processMultibulkBuffer(client *c) {
|
||||
while(c->multibulklen) {
|
||||
/* Read bulk length if unknown */
|
||||
if (c->bulklen == -1) {
|
||||
newline = strchr(c->querybuf+pos,'\r');
|
||||
newline = strchr(c->querybuf+c->qb_pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,
|
||||
"Protocol error: too big bulk count string");
|
||||
setProtocolError("too big bulk count string",c,0);
|
||||
setProtocolError("too big bulk count string",c);
|
||||
return C_ERR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Buffer should also contain \n */
|
||||
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
|
||||
if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
|
||||
break;
|
||||
|
||||
if (c->querybuf[pos] != '$') {
|
||||
if (c->querybuf[c->qb_pos] != '$') {
|
||||
addReplyErrorFormat(c,
|
||||
"Protocol error: expected '$', got '%c'",
|
||||
c->querybuf[pos]);
|
||||
setProtocolError("expected $ but got something else",c,pos);
|
||||
c->querybuf[c->qb_pos]);
|
||||
setProtocolError("expected $ but got something else",c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
|
||||
ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll);
|
||||
if (!ok || ll < 0 || ll > server.proto_max_bulk_len) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c,pos);
|
||||
setProtocolError("invalid bulk length",c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
pos += newline-(c->querybuf+pos)+2;
|
||||
c->qb_pos = newline-c->querybuf+2;
|
||||
if (ll >= PROTO_MBULK_BIG_ARG) {
|
||||
size_t qblen;
|
||||
|
||||
/* If we are going to read a large object from network
|
||||
* try to make it likely that it will start at c->querybuf
|
||||
* boundary so that we can optimize object creation
|
||||
* avoiding a large copy of data. */
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
pos = 0;
|
||||
qblen = sdslen(c->querybuf);
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
if (qblen < (size_t)ll+2)
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
|
||||
* avoiding a large copy of data.
|
||||
*
|
||||
* But only when the data we have not parsed is less than
|
||||
* or equal to ll+2. If the data length is greater than
|
||||
* ll+2, trimming querybuf is just a waste of time, because
|
||||
* at this time the querybuf contains not only our bulk. */
|
||||
if (sdslen(c->querybuf)-c->qb_pos <= (size_t)ll+2) {
|
||||
sdsrange(c->querybuf,c->qb_pos,-1);
|
||||
c->qb_pos = 0;
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
|
||||
}
|
||||
}
|
||||
c->bulklen = ll;
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
if (sdslen(c->querybuf)-pos < (size_t)(c->bulklen+2)) {
|
||||
if (sdslen(c->querybuf)-c->qb_pos < (size_t)(c->bulklen+2)) {
|
||||
/* Not enough data (+2 == trailing \r\n) */
|
||||
break;
|
||||
} else {
|
||||
/* Optimization: if the buffer contains JUST our bulk element
|
||||
* instead of creating a new object by *copying* the sds we
|
||||
* just use the current sds string. */
|
||||
if (pos == 0 &&
|
||||
if (c->qb_pos == 0 &&
|
||||
c->bulklen >= PROTO_MBULK_BIG_ARG &&
|
||||
sdslen(c->querybuf) == (size_t)(c->bulklen+2))
|
||||
{
|
||||
@@ -1283,20 +1332,16 @@ int processMultibulkBuffer(client *c) {
|
||||
* likely... */
|
||||
c->querybuf = sdsnewlen(SDS_NOINIT,c->bulklen+2);
|
||||
sdsclear(c->querybuf);
|
||||
pos = 0;
|
||||
} else {
|
||||
c->argv[c->argc++] =
|
||||
createStringObject(c->querybuf+pos,c->bulklen);
|
||||
pos += c->bulklen+2;
|
||||
createStringObject(c->querybuf+c->qb_pos,c->bulklen);
|
||||
c->qb_pos += c->bulklen+2;
|
||||
}
|
||||
c->bulklen = -1;
|
||||
c->multibulklen--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (pos) sdsrange(c->querybuf,pos,-1);
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) return C_OK;
|
||||
|
||||
@@ -1310,14 +1355,21 @@ int processMultibulkBuffer(client *c) {
|
||||
* pending query buffer, already representing a full command, to process. */
|
||||
void processInputBuffer(client *c) {
|
||||
server.current_client = c;
|
||||
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
while(sdslen(c->querybuf)) {
|
||||
while(c->qb_pos < sdslen(c->querybuf)) {
|
||||
/* Return if clients are paused. */
|
||||
if (!(c->flags & CLIENT_SLAVE) && clientsArePaused()) break;
|
||||
|
||||
/* Immediately abort if the client is in the middle of something. */
|
||||
if (c->flags & CLIENT_BLOCKED) break;
|
||||
|
||||
/* Don't process input from the master while there is a busy script
|
||||
* condition on the slave. We want just to accumulate the replication
|
||||
* stream (instead of replying -BUSY like we do with other clients) and
|
||||
* later resume the processing. */
|
||||
if (server.lua_timedout && c->flags & CLIENT_MASTER) break;
|
||||
|
||||
/* CLIENT_CLOSE_AFTER_REPLY closes the connection once the reply is
|
||||
* written to the client. Make sure to not let the reply grow after
|
||||
* this flag has been set (i.e. don't process more commands).
|
||||
@@ -1327,7 +1379,7 @@ void processInputBuffer(client *c) {
|
||||
|
||||
/* Determine request type when unknown. */
|
||||
if (!c->reqtype) {
|
||||
if (c->querybuf[0] == '*') {
|
||||
if (c->querybuf[c->qb_pos] == '*') {
|
||||
c->reqtype = PROTO_REQ_MULTIBULK;
|
||||
} else {
|
||||
c->reqtype = PROTO_REQ_INLINE;
|
||||
@@ -1350,7 +1402,7 @@ void processInputBuffer(client *c) {
|
||||
if (processCommand(c) == C_OK) {
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf);
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
@@ -1366,9 +1418,35 @@ void processInputBuffer(client *c) {
|
||||
if (server.current_client == NULL) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (c->qb_pos) {
|
||||
sdsrange(c->querybuf,c->qb_pos,-1);
|
||||
c->qb_pos = 0;
|
||||
}
|
||||
|
||||
server.current_client = NULL;
|
||||
}
|
||||
|
||||
/* This is a wrapper for processInputBuffer that also cares about handling
|
||||
* the replication forwarding to the sub-slaves, 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 {
|
||||
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(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
client *c = (client*) privdata;
|
||||
int nread, readlen;
|
||||
@@ -1388,7 +1466,9 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
{
|
||||
ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);
|
||||
|
||||
if (remaining < readlen) readlen = remaining;
|
||||
/* Note that the 'remaining' variable may be zero in some edge case,
|
||||
* for example once we resume a blocked client after CLIENT PAUSE. */
|
||||
if (remaining > 0 && remaining < readlen) readlen = remaining;
|
||||
}
|
||||
|
||||
qblen = sdslen(c->querybuf);
|
||||
@@ -1436,18 +1516,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* was actually applied to the master state: this quantity, and its
|
||||
* corresponding part of the replication stream, will be propagated to
|
||||
* the sub-slaves and to the replication backlog. */
|
||||
if (!(c->flags & CLIENT_MASTER)) {
|
||||
processInputBuffer(c);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
processInputBufferAndReplicate(c);
|
||||
}
|
||||
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
@@ -1585,14 +1654,14 @@ void clientCommand(client *c) {
|
||||
"kill <ip:port> -- Kill connection made from <ip:port>.",
|
||||
"kill <option> <value> [option value ...] -- Kill connections. Options are:",
|
||||
" addr <ip:port> -- Kill connection made from <ip:port>",
|
||||
" type (normal|master|slave|pubsub) -- Kill connections by type.",
|
||||
" type (normal|master|replica|pubsub) -- Kill connections by type.",
|
||||
" skipme (yes|no) -- Skip killing current connection (default: yes).",
|
||||
"list [options ...] -- Return information about client connections. Options:",
|
||||
" type (normal|master|slave|pubsub) -- Return clients of specified type.",
|
||||
" type (normal|master|replica|pubsub) -- Return clients of specified type.",
|
||||
"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
|
||||
"reply (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"setname <name> -- Assign the name <name> to the current connection.",
|
||||
"unblock -- Unblock the specified blocked client.",
|
||||
"unblock <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -1898,10 +1967,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
* the caller wishes. The main usage of this function currently is
|
||||
* enforcing the client output length limits. */
|
||||
unsigned long getClientOutputBufferMemoryUsage(client *c) {
|
||||
unsigned long list_item_size = sizeof(listNode)+5;
|
||||
/* The +5 above means we assume an sds16 hdr, may not be true
|
||||
* but is not going to be a problem. */
|
||||
|
||||
unsigned long list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
|
||||
return c->reply_bytes + (list_item_size*listLength(c->reply));
|
||||
}
|
||||
|
||||
@@ -1925,6 +1991,7 @@ int getClientType(client *c) {
|
||||
int getClientTypeByName(char *name) {
|
||||
if (!strcasecmp(name,"normal")) return CLIENT_TYPE_NORMAL;
|
||||
else if (!strcasecmp(name,"slave")) return CLIENT_TYPE_SLAVE;
|
||||
else if (!strcasecmp(name,"replica")) return CLIENT_TYPE_SLAVE;
|
||||
else if (!strcasecmp(name,"pubsub")) return CLIENT_TYPE_PUBSUB;
|
||||
else if (!strcasecmp(name,"master")) return CLIENT_TYPE_MASTER;
|
||||
else return -1;
|
||||
@@ -2073,11 +2140,10 @@ int clientsArePaused(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
|
||||
/* Don't touch slaves and blocked clients. The latter pending
|
||||
* requests be processed when unblocked. */
|
||||
/* Don't touch slaves and blocked clients.
|
||||
* The latter pending requests will be processed when unblocked. */
|
||||
if (c->flags & (CLIENT_SLAVE|CLIENT_BLOCKED)) continue;
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
}
|
||||
return server.clients_paused;
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* This file implements keyspace events notification via Pub/Sub ad
|
||||
* described at http://redis.io/topics/keyspace-events. */
|
||||
/* This file implements keyspace events notification via Pub/Sub and
|
||||
* described at https://redis.io/topics/notifications. */
|
||||
|
||||
/* Turn a string representing notification classes into an integer
|
||||
* representing notification classes flags xored.
|
||||
|
||||
+28
-3
@@ -1017,6 +1017,18 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->aof_buffer = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = server.lua_scripts_mem;
|
||||
mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
|
||||
dictSlots(server.lua_scripts) * sizeof(dictEntry*);
|
||||
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
|
||||
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
|
||||
if (listLength(server.repl_scriptcache_fifo) > 0) {
|
||||
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
|
||||
sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
|
||||
}
|
||||
mh->lua_caches = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
long long keyscount = dictSize(db->dict);
|
||||
@@ -1074,6 +1086,7 @@ sds getMemoryDoctorReport(void) {
|
||||
int high_alloc_rss = 0; /* High rss overhead. */
|
||||
int big_slave_buf = 0; /* Slave buffers are too big. */
|
||||
int big_client_buf = 0; /* Client buffers are too big. */
|
||||
int many_scripts = 0; /* Script cache has too many scripts. */
|
||||
int num_reports = 0;
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
@@ -1124,6 +1137,12 @@ sds getMemoryDoctorReport(void) {
|
||||
big_slave_buf = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Too many scripts are cached? */
|
||||
if (dictSize(server.lua_scripts) > 1000) {
|
||||
many_scripts = 1;
|
||||
num_reports++;
|
||||
}
|
||||
}
|
||||
|
||||
sds s;
|
||||
@@ -1153,14 +1172,17 @@ sds getMemoryDoctorReport(void) {
|
||||
s = sdscatprintf(s," * High allocator RSS overhead: This instance has an RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the allocator is much larger than the sum what the allocator actually holds). This problem is usually due to a large peak memory (check if there is a peak memory entry above in the report), you can try the MEMORY PURGE command to reclaim it.\n\n");
|
||||
}
|
||||
if (high_proc_rss) {
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to LUA scripts or Modules.\n\n");
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to Lua scripts or Modules.\n\n");
|
||||
}
|
||||
if (big_slave_buf) {
|
||||
s = sdscat(s," * Big slave buffers: The slave output buffers in this instance are greater than 10MB for each slave (on average). This likely means that there is some slave instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what slave is not receiving data correctly and why. You can use the INFO output in order to check the slaves delays and the CLIENT LIST command to check the output buffers of each slave.\n\n");
|
||||
s = sdscat(s," * Big replica buffers: The replica output buffers in this instance are greater than 10MB for each replica (on average). This likely means that there is some replica instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what replica is not receiving data correctly and why. You can use the INFO output in order to check the replicas delays and the CLIENT LIST command to check the output buffers of each replica.\n\n");
|
||||
}
|
||||
if (big_client_buf) {
|
||||
s = sdscat(s," * Big client buffers: The clients output buffers in this instance are greater than 200K per client (on average). This may result from different causes, like Pub/Sub clients subscribed to channels bot not receiving data fast enough, so that data piles on the Redis instance output buffer, or clients sending commands with large replies or very large sequences of commands in the same pipeline. Please use the CLIENT LIST command in order to investigate the issue if it causes problems in your instance, or to understand better why certain clients are using a big amount of memory.\n\n");
|
||||
}
|
||||
if (many_scripts) {
|
||||
s = sdscat(s," * Many scripts: There seem to be many cached scripts in this instance (more than 1000). This may be because scripts are generated and `EVAL`ed, instead of being parameterized (with KEYS and ARGV), `SCRIPT LOAD`ed and `EVALSHA`ed. Unless `SCRIPT FLUSH` is called periodically, the scripts' caches may end up consuming most of your memory.\n\n");
|
||||
}
|
||||
s = sdscat(s,"I'm here to keep you safe, Sam. I want to help you.\n");
|
||||
}
|
||||
freeMemoryOverheadData(mh);
|
||||
@@ -1293,7 +1315,7 @@ void memoryCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
addReplyMultiBulkLen(c,(24+mh->num_dbs)*2);
|
||||
addReplyMultiBulkLen(c,(25+mh->num_dbs)*2);
|
||||
|
||||
addReplyBulkCString(c,"peak.allocated");
|
||||
addReplyLongLong(c,mh->peak_allocated);
|
||||
@@ -1316,6 +1338,9 @@ void memoryCommand(client *c) {
|
||||
addReplyBulkCString(c,"aof.buffer");
|
||||
addReplyLongLong(c,mh->aof_buffer);
|
||||
|
||||
addReplyBulkCString(c,"lua.caches");
|
||||
addReplyLongLong(c,mh->lua_caches);
|
||||
|
||||
for (size_t j = 0; j < mh->num_dbs; j++) {
|
||||
char dbname[32];
|
||||
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
|
||||
|
||||
@@ -1313,7 +1313,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Seek the grestest key in the subtree at the current node. Return 0 on
|
||||
/* Seek the greatest key in the subtree at the current node. Return 0 on
|
||||
* out of memory, otherwise 1. This is an helper function for different
|
||||
* iteration functions below. */
|
||||
int raxSeekGreatest(raxIterator *it) {
|
||||
|
||||
@@ -286,12 +286,8 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && expiretime != -1 && expiretime < now)
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
if (expiretime != -1) rdbstate.expires++;
|
||||
rdbstate.key = NULL;
|
||||
|
||||
+465
-400
File diff suppressed because it is too large
Load Diff
+106
-1807
File diff suppressed because it is too large
Load Diff
@@ -117,6 +117,10 @@
|
||||
#define REDISMODULE_NODE_FAIL (1<<4)
|
||||
#define REDISMODULE_NODE_NOFAILOVER (1<<5)
|
||||
|
||||
#define REDISMODULE_CLUSTER_FLAG_NONE 0
|
||||
#define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1)
|
||||
#define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2)
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* This type represents a timer handle, and is returned when a timer is
|
||||
@@ -141,6 +145,8 @@ typedef struct RedisModuleType RedisModuleType;
|
||||
typedef struct RedisModuleDigest RedisModuleDigest;
|
||||
typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
|
||||
typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
@@ -273,6 +279,28 @@ long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md);
|
||||
RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
@@ -303,6 +331,7 @@ size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@@ -412,6 +441,28 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
REDISMODULE_GET_API(DigestEndSequence);
|
||||
REDISMODULE_GET_API(CreateDict);
|
||||
REDISMODULE_GET_API(FreeDict);
|
||||
REDISMODULE_GET_API(DictSize);
|
||||
REDISMODULE_GET_API(DictSetC);
|
||||
REDISMODULE_GET_API(DictReplaceC);
|
||||
REDISMODULE_GET_API(DictSet);
|
||||
REDISMODULE_GET_API(DictReplace);
|
||||
REDISMODULE_GET_API(DictGetC);
|
||||
REDISMODULE_GET_API(DictGet);
|
||||
REDISMODULE_GET_API(DictDelC);
|
||||
REDISMODULE_GET_API(DictDel);
|
||||
REDISMODULE_GET_API(DictIteratorStartC);
|
||||
REDISMODULE_GET_API(DictIteratorStart);
|
||||
REDISMODULE_GET_API(DictIteratorStop);
|
||||
REDISMODULE_GET_API(DictIteratorReseekC);
|
||||
REDISMODULE_GET_API(DictIteratorReseek);
|
||||
REDISMODULE_GET_API(DictNextC);
|
||||
REDISMODULE_GET_API(DictPrevC);
|
||||
REDISMODULE_GET_API(DictNext);
|
||||
REDISMODULE_GET_API(DictPrev);
|
||||
REDISMODULE_GET_API(DictCompare);
|
||||
REDISMODULE_GET_API(DictCompareC);
|
||||
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
@@ -440,6 +491,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetClusterSize);
|
||||
REDISMODULE_GET_API(GetRandomBytes);
|
||||
REDISMODULE_GET_API(GetRandomHexChars);
|
||||
REDISMODULE_GET_API(SetClusterFlags);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
||||
+39
-37
@@ -48,7 +48,7 @@ int cancelReplicationHandshake(void);
|
||||
/* Return the pointer to a string representing the slave ip:listening_port
|
||||
* pair. Mostly useful for logging, since we want to log a slave using its
|
||||
* IP address and its listening port which is more clear for the user, for
|
||||
* example: "Closing connection with slave 10.1.2.3:6380". */
|
||||
* example: "Closing connection with replica 10.1.2.3:6380". */
|
||||
char *replicationGetSlaveName(client *c) {
|
||||
static char buf[NET_PEER_ID_LEN];
|
||||
char ip[NET_IP_STR_LEN];
|
||||
@@ -64,7 +64,7 @@ char *replicationGetSlaveName(client *c) {
|
||||
if (c->slave_listening_port)
|
||||
anetFormatAddr(buf,sizeof(buf),ip,c->slave_listening_port);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%s:<unknown-slave-port>",ip);
|
||||
snprintf(buf,sizeof(buf),"%s:<unknown-replica-port>",ip);
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"client id #%llu",
|
||||
(unsigned long long) c->id);
|
||||
@@ -344,7 +344,7 @@ void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv,
|
||||
long long addReplyReplicationBacklog(client *c, long long offset) {
|
||||
long long j, skip, len;
|
||||
|
||||
serverLog(LL_DEBUG, "[PSYNC] Slave request offset: %lld", offset);
|
||||
serverLog(LL_DEBUG, "[PSYNC] Replica request offset: %lld", offset);
|
||||
|
||||
if (server.repl_backlog_histlen == 0) {
|
||||
serverLog(LL_DEBUG, "[PSYNC] Backlog history len is zero");
|
||||
@@ -472,7 +472,7 @@ int masterTryPartialResynchronization(client *c) {
|
||||
strcasecmp(master_replid, server.replid2))
|
||||
{
|
||||
serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
|
||||
"Replication ID mismatch (Slave asked for '%s', my "
|
||||
"Replication ID mismatch (Replica asked for '%s', my "
|
||||
"replication IDs are '%s' and '%s')",
|
||||
master_replid, server.replid, server.replid2);
|
||||
} else {
|
||||
@@ -481,7 +481,7 @@ int masterTryPartialResynchronization(client *c) {
|
||||
"up to %lld", psync_offset, server.second_replid_offset);
|
||||
}
|
||||
} else {
|
||||
serverLog(LL_NOTICE,"Full resync requested by slave %s",
|
||||
serverLog(LL_NOTICE,"Full resync requested by replica %s",
|
||||
replicationGetSlaveName(c));
|
||||
}
|
||||
goto need_full_resync;
|
||||
@@ -493,10 +493,10 @@ int masterTryPartialResynchronization(client *c) {
|
||||
psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
|
||||
{
|
||||
serverLog(LL_NOTICE,
|
||||
"Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).", replicationGetSlaveName(c), psync_offset);
|
||||
"Unable to partial resync with replica %s for lack of backlog (Replica request was: %lld).", replicationGetSlaveName(c), psync_offset);
|
||||
if (psync_offset > server.master_repl_offset) {
|
||||
serverLog(LL_WARNING,
|
||||
"Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
|
||||
"Warning: replica %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
|
||||
}
|
||||
goto need_full_resync;
|
||||
}
|
||||
@@ -567,7 +567,7 @@ int startBgsaveForReplication(int mincapa) {
|
||||
listNode *ln;
|
||||
|
||||
serverLog(LL_NOTICE,"Starting BGSAVE for SYNC with target: %s",
|
||||
socket_target ? "slaves sockets" : "disk");
|
||||
socket_target ? "replicas sockets" : "disk");
|
||||
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
@@ -644,7 +644,7 @@ void syncCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
serverLog(LL_NOTICE,"Slave %s asks for synchronization",
|
||||
serverLog(LL_NOTICE,"Replica %s asks for synchronization",
|
||||
replicationGetSlaveName(c));
|
||||
|
||||
/* Try a partial resynchronization if this is a PSYNC command.
|
||||
@@ -725,7 +725,7 @@ void syncCommand(client *c) {
|
||||
} else {
|
||||
/* No way, we need to wait for the next BGSAVE in order to
|
||||
* register differences. */
|
||||
serverLog(LL_NOTICE,"Can't attach the slave to the current BGSAVE. Waiting for next BGSAVE for SYNC");
|
||||
serverLog(LL_NOTICE,"Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC");
|
||||
}
|
||||
|
||||
/* CASE 2: BGSAVE is in progress, with socket target. */
|
||||
@@ -798,7 +798,7 @@ void replconfCommand(client *c) {
|
||||
memcpy(c->slave_ip,ip,sdslen(ip)+1);
|
||||
} else {
|
||||
addReplyErrorFormat(c,"REPLCONF ip-address provided by "
|
||||
"slave instance is too long: %zd bytes", sdslen(ip));
|
||||
"replica instance is too long: %zd bytes", sdslen(ip));
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
|
||||
@@ -858,12 +858,12 @@ void putSlaveOnline(client *slave) {
|
||||
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
serverLog(LL_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
|
||||
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
refreshGoodSlavesCount();
|
||||
serverLog(LL_NOTICE,"Synchronization with slave %s succeeded",
|
||||
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
||||
replicationGetSlaveName(slave));
|
||||
}
|
||||
|
||||
@@ -880,7 +880,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (slave->replpreamble) {
|
||||
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
|
||||
if (nwritten == -1) {
|
||||
serverLog(LL_VERBOSE,"Write error sending RDB preamble to slave: %s",
|
||||
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
@@ -900,14 +900,14 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
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 slave: %s",
|
||||
serverLog(LL_WARNING,"Read error sending DB to replica: %s",
|
||||
(buflen == 0) ? "premature EOF" : strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
if ((nwritten = write(fd,buf,buflen)) == -1) {
|
||||
if (errno != EAGAIN) {
|
||||
serverLog(LL_WARNING,"Write error sending DB to slave: %s",
|
||||
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
}
|
||||
@@ -961,7 +961,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
* the slave online. */
|
||||
if (type == RDB_CHILD_TYPE_SOCKET) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Streamed RDB transfer with slave %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
|
||||
"Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
|
||||
replicationGetSlaveName(slave));
|
||||
/* Note: we wait for a REPLCONF ACK message from slave in
|
||||
* order to really put it online (install the write handler
|
||||
@@ -1096,7 +1096,7 @@ void restartAOF() {
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
serverLog(LL_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
serverLog(LL_WARNING,"FATAL: this replica instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1161,12 +1161,12 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* at the next call. */
|
||||
server.repl_transfer_size = 0;
|
||||
serverLog(LL_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving streamed RDB from master");
|
||||
"MASTER <-> REPLICA sync: receiving streamed RDB from master");
|
||||
} else {
|
||||
usemark = 0;
|
||||
server.repl_transfer_size = strtol(buf+1,NULL,10);
|
||||
serverLog(LL_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving %lld bytes from master",
|
||||
"MASTER <-> REPLICA sync: receiving %lld bytes from master",
|
||||
(long long) server.repl_transfer_size);
|
||||
}
|
||||
return;
|
||||
@@ -1207,7 +1207,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) {
|
||||
serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s",
|
||||
serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> REPLICA synchronization: %s",
|
||||
(nwritten == -1) ? strerror(errno) : "short write");
|
||||
goto error;
|
||||
}
|
||||
@@ -1246,11 +1246,11 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int aof_is_enabled = server.aof_state != AOF_OFF;
|
||||
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> REPLICA synchronization: %s", strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
return;
|
||||
}
|
||||
serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
|
||||
/* We need to stop any AOFRW fork before flusing and parsing
|
||||
* RDB, otherwise we'll create a copy-on-write disaster. */
|
||||
if(aof_is_enabled) stopAppendOnly();
|
||||
@@ -1264,7 +1264,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* rdbLoad() will call the event loop to process events from time to
|
||||
* time for non blocking loading. */
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
|
||||
serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
@@ -1292,7 +1292,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* masters after a failover. */
|
||||
if (server.repl_backlog == NULL) createReplicationBacklog();
|
||||
|
||||
serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
@@ -1791,7 +1791,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* uninstalling the read handler from the file descriptor. */
|
||||
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.");
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1823,7 +1823,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
sleep(1);
|
||||
}
|
||||
if (dfd == -1) {
|
||||
serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
|
||||
serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -1997,11 +1997,11 @@ void replicationHandleMasterDisconnection(void) {
|
||||
* the slaves only if we'll have to do a full resync with our master. */
|
||||
}
|
||||
|
||||
void slaveofCommand(client *c) {
|
||||
void replicaofCommand(client *c) {
|
||||
/* SLAVEOF is not allowed in cluster mode as replication is automatically
|
||||
* configured using the current address of the master node. */
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"SLAVEOF not allowed in cluster mode.");
|
||||
addReplyError(c,"REPLICAOF not allowed in cluster mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2025,7 +2025,7 @@ void slaveofCommand(client *c) {
|
||||
/* Check if we are already attached to the specified slave */
|
||||
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
|
||||
&& server.masterport == port) {
|
||||
serverLog(LL_NOTICE,"SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.");
|
||||
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization with the master we are already connected with. No operation performed.");
|
||||
addReplySds(c,sdsnew("+OK Already connected to specified master\r\n"));
|
||||
return;
|
||||
}
|
||||
@@ -2033,7 +2033,7 @@ void slaveofCommand(client *c) {
|
||||
* we can continue. */
|
||||
replicationSetMaster(c->argv[1]->ptr, port);
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
serverLog(LL_NOTICE,"SLAVE OF %s:%d enabled (user request from '%s')",
|
||||
serverLog(LL_NOTICE,"REPLICAOF %s:%d enabled (user request from '%s')",
|
||||
server.masterhost, server.masterport, client);
|
||||
sdsfree(client);
|
||||
}
|
||||
@@ -2148,6 +2148,8 @@ void replicationCacheMaster(client *c) {
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
if (c->flags & CLIENT_MULTI) discardTransaction(c);
|
||||
listEmpty(c->reply);
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes = 0;
|
||||
c->bufpos = 0;
|
||||
resetClient(c);
|
||||
|
||||
@@ -2189,7 +2191,7 @@ void replicationCacheMasterUsingMyself(void) {
|
||||
unlinkClient(server.master);
|
||||
server.cached_master = server.master;
|
||||
server.master = NULL;
|
||||
serverLog(LL_NOTICE,"Before turning into a slave, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
|
||||
serverLog(LL_NOTICE,"Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
|
||||
}
|
||||
|
||||
/* Free a cached master, called when there are no longer the conditions for
|
||||
@@ -2405,7 +2407,7 @@ void waitCommand(client *c) {
|
||||
long long offset = c->woff;
|
||||
|
||||
if (server.masterhost) {
|
||||
addReplyError(c,"WAIT cannot be used with slave instances. Please also note that since Redis 4.0 if a slave is configured to be writable (which is not the default) writes to slaves are just local and are not propagated.");
|
||||
addReplyError(c,"WAIT cannot be used with replica instances. Please also note that since Redis 4.0 if a replica is configured to be writable (which is not the default) writes to replicas are just local and are not propagated.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2537,7 +2539,7 @@ void replicationCron(void) {
|
||||
serverLog(LL_NOTICE,"Connecting to MASTER %s:%d",
|
||||
server.masterhost, server.masterport);
|
||||
if (connectWithMaster() == C_OK) {
|
||||
serverLog(LL_NOTICE,"MASTER <-> SLAVE sync started");
|
||||
serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2609,7 +2611,7 @@ void replicationCron(void) {
|
||||
if (slave->flags & CLIENT_PRE_PSYNC) continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
|
||||
{
|
||||
serverLog(LL_WARNING, "Disconnecting timedout slave: %s",
|
||||
serverLog(LL_WARNING, "Disconnecting timedout replica: %s",
|
||||
replicationGetSlaveName(slave));
|
||||
freeClient(slave);
|
||||
}
|
||||
@@ -2639,7 +2641,7 @@ void replicationCron(void) {
|
||||
* be the same as our repl-id.
|
||||
* 3. We, yet as master, receive some updates, that will not
|
||||
* increment the master_repl_offset.
|
||||
* 4. Later we are turned into a slave, connecto to the new
|
||||
* 4. Later we are turned into a slave, connect to the new
|
||||
* master that will accept our PSYNC request by second
|
||||
* replication ID, but there will be data inconsistency
|
||||
* because we received writes. */
|
||||
@@ -2648,7 +2650,7 @@ void replicationCron(void) {
|
||||
freeReplicationBacklog();
|
||||
serverLog(LL_NOTICE,
|
||||
"Replication backlog freed after %d seconds "
|
||||
"without connected slaves.",
|
||||
"without connected replicas.",
|
||||
(int) server.repl_backlog_time_limit);
|
||||
}
|
||||
}
|
||||
|
||||
+53
-22
@@ -483,6 +483,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* command marked as non-deterministic was already called in the context
|
||||
* of this script. */
|
||||
if (cmd->flags & CMD_WRITE) {
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (server.lua_random_dirty && !server.lua_replicate_commands) {
|
||||
luaPushError(lua,
|
||||
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
|
||||
@@ -493,11 +494,16 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
{
|
||||
luaPushError(lua, shared.roslaveerr->ptr);
|
||||
goto cleanup;
|
||||
} else if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR)
|
||||
{
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
} else if (deny_write_type != DISK_ERROR_TYPE_NONE) {
|
||||
if (deny_write_type == DISK_ERROR_TYPE_RDB) {
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
} else {
|
||||
sds aof_write_err = sdscatfmt(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno));
|
||||
luaPushError(lua, aof_write_err);
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@@ -506,10 +512,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* could enlarge the memory usage are not allowed, but only if this is the
|
||||
* first write in the context of this script, otherwise we can't stop
|
||||
* in the middle. */
|
||||
if (server.maxmemory && server.lua_write_dirty == 0 &&
|
||||
if (server.maxmemory && /* Maxmemory is actually enabled. */
|
||||
!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. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (freeMemoryIfNeeded() == C_ERR) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -551,7 +560,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Run the command */
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOQUEUE;
|
||||
if (server.lua_replicate_commands) {
|
||||
/* Set flags according to redis.set_repl() settings. */
|
||||
if (server.lua_repl & PROPAGATE_AOF)
|
||||
@@ -575,9 +584,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
sds o = listNodeValue(listFirst(c->reply));
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatsds(reply,o);
|
||||
reply = sdscatlen(reply,o->buf,o->used);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
}
|
||||
@@ -768,7 +777,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
|
||||
|
||||
flags = lua_tonumber(lua,-1);
|
||||
if ((flags & ~(PROPAGATE_AOF|PROPAGATE_REPL)) != 0) {
|
||||
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_SLAVE, REPL_ALL or REPL_NONE.");
|
||||
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
server.lua_repl = flags;
|
||||
@@ -908,7 +917,6 @@ void scriptingInit(int setup) {
|
||||
server.lua_client = NULL;
|
||||
server.lua_caller = NULL;
|
||||
server.lua_timedout = 0;
|
||||
server.lua_always_replicate_commands = 0; /* Only DEBUG can change it.*/
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
@@ -919,6 +927,7 @@ void scriptingInit(int setup) {
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
* as EVAL, so we need to remember the associated script. */
|
||||
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
|
||||
server.lua_scripts_mem = 0;
|
||||
|
||||
/* Register the redis commands table and fields */
|
||||
lua_newtable(lua);
|
||||
@@ -989,6 +998,10 @@ void scriptingInit(int setup) {
|
||||
lua_pushnumber(lua,PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
lua_pushstring(lua,"REPL_REPLICA");
|
||||
lua_pushnumber(lua,PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
lua_pushstring(lua,"REPL_ALL");
|
||||
lua_pushnumber(lua,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
@@ -1073,6 +1086,7 @@ void scriptingInit(int setup) {
|
||||
* This function is used in order to reset the scripting environment. */
|
||||
void scriptingRelease(void) {
|
||||
dictRelease(server.lua_scripts);
|
||||
server.lua_scripts_mem = 0;
|
||||
lua_close(server.lua);
|
||||
}
|
||||
|
||||
@@ -1207,17 +1221,19 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
|
||||
incrRefCount(body);
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
long long elapsed;
|
||||
long long elapsed = mstime() - server.lua_time_start;
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
elapsed = mstime() - server.lua_time_start;
|
||||
/* Set the timeout condition if not already set and the maximum
|
||||
* execution time was reached. */
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
@@ -1240,6 +1256,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_State *lua = server.lua;
|
||||
char funcname[43];
|
||||
long long numkeys;
|
||||
long long initial_server_dirty = server.dirty;
|
||||
int delhook = 0, err;
|
||||
|
||||
/* When we replicate whole scripts, we want the same PRNG sequence at
|
||||
@@ -1336,9 +1353,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL &&
|
||||
ldb.active == 0)
|
||||
{
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
} else if (ldb.active) {
|
||||
@@ -1359,6 +1374,8 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* script timeout was detected. */
|
||||
aeCreateFileEvent(server.el,c->fd,AE_READABLE,
|
||||
readQueryFromClient,c);
|
||||
if (server.masterhost && server.master)
|
||||
queueClientForReprocessing(server.master);
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
|
||||
@@ -1422,9 +1439,21 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
|
||||
replicationScriptCacheAdd(c->argv[1]->ptr);
|
||||
serverAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
|
||||
/* If the script did not produce any changes in the dataset we want
|
||||
* just to replicate it as SCRIPT LOAD, otherwise we risk running
|
||||
* an aborted script on slaves (that may then produce results there)
|
||||
* or just running a CPU costly read-only script on the slaves. */
|
||||
if (server.dirty == initial_server_dirty) {
|
||||
rewriteClientCommandVector(c,3,
|
||||
resetRefCount(createStringObject("SCRIPT",6)),
|
||||
resetRefCount(createStringObject("LOAD",4)),
|
||||
script);
|
||||
} else {
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
}
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
@@ -1459,7 +1488,7 @@ void scriptCommand(client *c) {
|
||||
const char *help[] = {
|
||||
"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
|
||||
"EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
|
||||
"FLUSH -- Flush the Lua scripts cache. Very dangerous on slaves.",
|
||||
"FLUSH -- Flush the Lua scripts cache. Very dangerous on replicas.",
|
||||
"KILL -- Kill the currently executing Lua script.",
|
||||
"LOAD <script> -- Load a script into the scripts cache, without executing it.",
|
||||
NULL
|
||||
@@ -1488,6 +1517,8 @@ NULL
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
|
||||
} else if (server.lua_write_dirty) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
} else {
|
||||
@@ -1716,7 +1747,7 @@ int ldbRemoveChild(pid_t pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the number of children we still did not received termination
|
||||
/* Return the number of children we still did not receive termination
|
||||
* acknowledge via wait() in the parent process. */
|
||||
int ldbPendingChildren(void) {
|
||||
return listLength(ldb.children);
|
||||
|
||||
@@ -67,8 +67,10 @@ static inline char sdsReqType(size_t string_size) {
|
||||
#if (LONG_MAX == LLONG_MAX)
|
||||
if (string_size < 1ll<<32)
|
||||
return SDS_TYPE_32;
|
||||
#endif
|
||||
return SDS_TYPE_64;
|
||||
#else
|
||||
return SDS_TYPE_32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Create a new sds string with the content specified by the 'init' pointer
|
||||
|
||||
+15
-11
@@ -1687,16 +1687,18 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->leader_epoch = strtoull(argv[2],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
|
||||
} else if ((!strcasecmp(argv[0],"known-slave") ||
|
||||
!strcasecmp(argv[0],"known-replica")) && argc == 4)
|
||||
{
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
/* known-slave <name> <ip> <port> */
|
||||
/* known-replica <name> <ip> <port> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,argv[2],
|
||||
atoi(argv[3]), ri->quorum, ri)) == NULL)
|
||||
{
|
||||
return "Wrong hostname or port for slave.";
|
||||
return "Wrong hostname or port for replica.";
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"known-sentinel") &&
|
||||
(argc == 4 || argc == 5)) {
|
||||
@@ -1854,7 +1856,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
if (sentinelAddrIsEqual(slave_addr,master_addr))
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-slave %s %s %d",
|
||||
"sentinel known-replica %s %s %d",
|
||||
master->name, slave_addr->ip, slave_addr->port);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
@@ -2628,7 +2630,7 @@ int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
ri->link->last_ping_time = mstime();
|
||||
/* We update the active ping time only if we received the pong for
|
||||
* the previous ping, otherwise we are technically waiting since the
|
||||
* first ping that did not received a reply. */
|
||||
* first ping that did not receive a reply. */
|
||||
if (ri->link->act_ping_time == 0)
|
||||
ri->link->act_ping_time = ri->link->last_ping_time;
|
||||
return 1;
|
||||
@@ -2978,8 +2980,10 @@ void sentinelCommand(client *c) {
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
addReplySentinelRedisInstance(c,ri);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves")) {
|
||||
/* SENTINEL SLAVES <master-name> */
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"replicas"))
|
||||
{
|
||||
/* SENTINEL REPLICAS <master-name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
@@ -3079,7 +3083,7 @@ void sentinelCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
if (sentinelSelectSlave(ri) == NULL) {
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable slave to promote\r\n"));
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable replica to promote\r\n"));
|
||||
return;
|
||||
}
|
||||
serverLog(LL_WARNING,"Executing user requested FAILOVER of '%s'",
|
||||
@@ -3261,7 +3265,7 @@ void sentinelCommand(client *c) {
|
||||
sentinel.simfailure_flags |=
|
||||
SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION;
|
||||
serverLog(LL_WARNING,"Failure simulation: this Sentinel "
|
||||
"will crash after promoting the selected slave to master");
|
||||
"will crash after promoting the selected replica to master");
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"help")) {
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyBulkCString(c,"crash-after-election");
|
||||
@@ -3569,7 +3573,7 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
(mstime() - ri->link->cc_conn_time) >
|
||||
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
|
||||
ri->link->act_ping_time != 0 && /* There is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not received
|
||||
/* The pending ping is delayed, and we did not receive
|
||||
* error replies as well. */
|
||||
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
|
||||
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
|
||||
@@ -3725,7 +3729,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
*
|
||||
* 1) We believe it is down, or there is a failover in progress.
|
||||
* 2) Sentinel is connected.
|
||||
* 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */
|
||||
* 3) We did not receive the info within SENTINEL_ASK_PERIOD ms. */
|
||||
if ((master->flags & SRI_S_DOWN) == 0) continue;
|
||||
if (ri->link->disconnected) continue;
|
||||
if (!(flags & SENTINEL_ASK_FORCED) &&
|
||||
|
||||
+252
-86
@@ -214,7 +214,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
@@ -240,9 +240,9 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"a",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"a",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"alt",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
|
||||
@@ -254,16 +254,17 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"flushdb",flushdbCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"flushall",flushallCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
|
||||
{"info",infoCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"info",infoCommand,-1,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"ttl",ttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"touch",touchCommand,-2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"slaveof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"replicaof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"lat",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"last",0,NULL,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
@@ -275,24 +276,24 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"migrate",migrateCommand,-6,"wR",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,1,0,0},
|
||||
{"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"rR",0,NULL,2,2,1,0,0},
|
||||
{"memory",memoryCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"a",0,NULL,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"aR",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"lt",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
@@ -310,18 +311,19 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"xread",xreadCommand,-3,"rs",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xreadgroup",xreadCommand,-3,"ws",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xread",xreadCommand,-4,"rs",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xreadgroup",xreadCommand,-7,"ws",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xgroup",xgroupCommand,-2,"wm",0,NULL,2,2,1,0,0},
|
||||
{"xack",xackCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xack",xackCommand,-4,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xpending",xpendingCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"xclaim",xclaimCommand,-5,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xclaim",xclaimCommand,-6,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xinfo",xinfoCommand,-2,"r",0,NULL,2,2,1,0,0},
|
||||
{"xdel",xdelCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xdel",xdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xtrim",xtrimCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0}
|
||||
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"lolwut",lolwutCommand,-1,"r",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -357,7 +359,7 @@ void serverLogRaw(int level, const char *msg) {
|
||||
gettimeofday(&tv,NULL);
|
||||
struct tm tm;
|
||||
nolocks_localtime(&tm,tv.tv_sec,server.timezone,server.daylight_active);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",&tm);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %Y %H:%M:%S.",&tm);
|
||||
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
|
||||
if (server.sentinel_mode) {
|
||||
role_char = 'X'; /* Sentinel. */
|
||||
@@ -885,12 +887,82 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function is used in order to track clients using the biggest amount
|
||||
* of memory in the latest few seconds. This way we can provide such information
|
||||
* in the INFO output (clients section), without having to do an O(N) scan for
|
||||
* all the clients.
|
||||
*
|
||||
* This is how it works. We have an array of CLIENTS_PEAK_MEM_USAGE_SLOTS slots
|
||||
* where we track, for each, the biggest client output and input buffers we
|
||||
* saw in that slot. Every slot correspond to one of the latest seconds, since
|
||||
* the array is indexed by doing UNIXTIME % CLIENTS_PEAK_MEM_USAGE_SLOTS.
|
||||
*
|
||||
* When we want to know what was recently the peak memory usage, we just scan
|
||||
* such few slots searching for the maximum value. */
|
||||
#define CLIENTS_PEAK_MEM_USAGE_SLOTS 8
|
||||
size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
|
||||
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
|
||||
|
||||
int clientsCronTrackExpansiveClients(client *c) {
|
||||
size_t in_usage = sdsAllocSize(c->querybuf);
|
||||
size_t out_usage = getClientOutputBufferMemoryUsage(c);
|
||||
int i = server.unixtime % CLIENTS_PEAK_MEM_USAGE_SLOTS;
|
||||
int zeroidx = (i+1) % CLIENTS_PEAK_MEM_USAGE_SLOTS;
|
||||
|
||||
/* Always zero the next sample, so that when we switch to that second, we'll
|
||||
* only register samples that are greater in that second without considering
|
||||
* the history of such slot.
|
||||
*
|
||||
* Note: our index may jump to any random position if serverCron() is not
|
||||
* called for some reason with the normal frequency, for instance because
|
||||
* some slow command is called taking multiple seconds to execute. In that
|
||||
* case our array may end containing data which is potentially older
|
||||
* than CLIENTS_PEAK_MEM_USAGE_SLOTS seconds: however this is not a problem
|
||||
* since here we want just to track if "recently" there were very expansive
|
||||
* clients from the POV of memory usage. */
|
||||
ClientsPeakMemInput[zeroidx] = 0;
|
||||
ClientsPeakMemOutput[zeroidx] = 0;
|
||||
|
||||
/* Track the biggest values observed so far in this slot. */
|
||||
if (in_usage > ClientsPeakMemInput[i]) ClientsPeakMemInput[i] = in_usage;
|
||||
if (out_usage > ClientsPeakMemOutput[i]) ClientsPeakMemOutput[i] = out_usage;
|
||||
|
||||
return 0; /* This function never terminates the client. */
|
||||
}
|
||||
|
||||
/* Return the max samples in the memory usage of clients tracked by
|
||||
* the function clientsCronTrackExpansiveClients(). */
|
||||
void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
size_t i = 0, o = 0;
|
||||
for (int j = 0; j < CLIENTS_PEAK_MEM_USAGE_SLOTS; j++) {
|
||||
if (ClientsPeakMemInput[j] > i) i = ClientsPeakMemInput[j];
|
||||
if (ClientsPeakMemOutput[j] > o) o = ClientsPeakMemOutput[j];
|
||||
}
|
||||
*in_usage = i;
|
||||
*out_usage = o;
|
||||
}
|
||||
|
||||
/* This function is called by serverCron() and is used in order to perform
|
||||
* operations on clients that are important to perform constantly. For instance
|
||||
* we use this function in order to disconnect clients after a timeout, including
|
||||
* clients blocked in some blocking command with a non-zero timeout.
|
||||
*
|
||||
* The function makes some effort to process all the clients every second, even
|
||||
* if this cannot be strictly guaranteed, since serverCron() may be called with
|
||||
* an actual frequency lower than server.hz in case of latency events like slow
|
||||
* commands.
|
||||
*
|
||||
* It is very important for this function, and the functions it calls, to be
|
||||
* very fast: sometimes Redis has tens of hundreds of connected clients, and the
|
||||
* default server.hz value is 10, so sometimes here we need to process thousands
|
||||
* of clients per second, turning this function into a source of latency.
|
||||
*/
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* Make sure to process at least numclients/server.hz of clients
|
||||
* per call. Since this function is called server.hz times per second
|
||||
* we are sure that in the worst case we process all the clients in 1
|
||||
* second. */
|
||||
/* Try to process at least numclients/server.hz of clients
|
||||
* per call. Since normally (if there are no big latency events) this
|
||||
* function is called server.hz times per second, in the average case we
|
||||
* process all the clients in 1 second. */
|
||||
int numclients = listLength(server.clients);
|
||||
int iterations = numclients/server.hz;
|
||||
mstime_t now = mstime();
|
||||
@@ -917,6 +989,7 @@ void clientsCron(void) {
|
||||
* terminated. */
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,6 +1098,21 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
server.hz = server.config_hz;
|
||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||
* many clients, we want to call serverCron() with an higher frequency. */
|
||||
if (server.dynamic_hz) {
|
||||
while (listLength(server.clients) / server.hz >
|
||||
MAX_CLIENTS_PER_CLOCK_TICK)
|
||||
{
|
||||
server.hz *= 2;
|
||||
if (server.hz > CONFIG_MAX_HZ) {
|
||||
server.hz = CONFIG_MAX_HZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_with_period(100) {
|
||||
trackInstantaneousMetric(STATS_METRIC_COMMAND,server.stat_numcommands);
|
||||
trackInstantaneousMetric(STATS_METRIC_NET_INPUT,
|
||||
@@ -1106,7 +1194,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if (!server.sentinel_mode) {
|
||||
run_with_period(5000) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"%lu clients connected (%lu slaves), %zu bytes in use",
|
||||
"%lu clients connected (%lu replicas), %zu bytes in use",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
zmalloc_used_memory());
|
||||
@@ -1165,7 +1253,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
} else {
|
||||
/* If there is not a background saving/rewrite in progress check if
|
||||
* we have to save/rewrite now. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
struct saveparam *sp = server.saveparams+j;
|
||||
|
||||
/* Save if we reached the given amount of changes,
|
||||
@@ -1185,23 +1273,23 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
rdbSaveBackground(server.rdb_filename,rsiptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
{
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
{
|
||||
long long base = server.aof_rewrite_base_size ?
|
||||
server.aof_rewrite_base_size : 1;
|
||||
server.aof_rewrite_base_size : 1;
|
||||
long long growth = (server.aof_current_size*100/base) - 100;
|
||||
if (growth >= server.aof_rewrite_perc) {
|
||||
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
|
||||
rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1234,9 +1322,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
|
||||
/* Run the Sentinel timer if we are in sentinel mode. */
|
||||
run_with_period(100) {
|
||||
if (server.sentinel_mode) sentinelTimer();
|
||||
}
|
||||
if (server.sentinel_mode) sentinelTimer();
|
||||
|
||||
/* Cleanup expired MIGRATE cached sockets. */
|
||||
run_with_period(1000) {
|
||||
@@ -1365,11 +1451,11 @@ void createSharedObjects(void) {
|
||||
shared.slowscripterr = createObject(OBJ_STRING,sdsnew(
|
||||
"-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
|
||||
shared.masterdownerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n"));
|
||||
"-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n"));
|
||||
shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));
|
||||
shared.roslaveerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-READONLY You can't write against a read only slave.\r\n"));
|
||||
"-READONLY You can't write against a read only replica.\r\n"));
|
||||
shared.noautherr = createObject(OBJ_STRING,sdsnew(
|
||||
"-NOAUTH Authentication required.\r\n"));
|
||||
shared.oomerr = createObject(OBJ_STRING,sdsnew(
|
||||
@@ -1377,7 +1463,7 @@ void createSharedObjects(void) {
|
||||
shared.execaborterr = createObject(OBJ_STRING,sdsnew(
|
||||
"-EXECABORT Transaction discarded because of previous errors.\r\n"));
|
||||
shared.noreplicaserr = createObject(OBJ_STRING,sdsnew(
|
||||
"-NOREPLICAS Not enough good slaves to write.\r\n"));
|
||||
"-NOREPLICAS Not enough good replicas to write.\r\n"));
|
||||
shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-BUSYKEY Target key name already exists.\r\n"));
|
||||
shared.space = createObject(OBJ_STRING,sdsnew(" "));
|
||||
@@ -1405,6 +1491,7 @@ void createSharedObjects(void) {
|
||||
shared.rpop = createStringObject("RPOP",4);
|
||||
shared.lpop = createStringObject("LPOP",4);
|
||||
shared.lpush = createStringObject("LPUSH",5);
|
||||
shared.rpoplpush = createStringObject("RPOPLPUSH",9);
|
||||
shared.zpopmin = createStringObject("ZPOPMIN",7);
|
||||
shared.zpopmax = createStringObject("ZPOPMAX",7);
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
@@ -1441,7 +1528,8 @@ void initServerConfig(void) {
|
||||
server.timezone = timezone; /* Initialized by tzset(). */
|
||||
server.configfile = NULL;
|
||||
server.executable = NULL;
|
||||
server.hz = CONFIG_DEFAULT_HZ;
|
||||
server.config_hz = CONFIG_DEFAULT_HZ;
|
||||
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
|
||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||
server.port = CONFIG_DEFAULT_SERVER_PORT;
|
||||
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
|
||||
@@ -1533,6 +1621,7 @@ void initServerConfig(void) {
|
||||
server.cluster_announce_ip = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP;
|
||||
server.cluster_announce_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT;
|
||||
server.cluster_announce_bus_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT;
|
||||
server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
|
||||
server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
|
||||
server.next_client_id = 1; /* Client IDs, start from 1 .*/
|
||||
server.loading_process_events_interval_bytes = (1024*1024*2);
|
||||
@@ -1561,6 +1650,7 @@ void initServerConfig(void) {
|
||||
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
||||
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
|
||||
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
|
||||
server.repl_slave_ignore_maxmemory = CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY;
|
||||
server.repl_slave_lazy_flush = CONFIG_DEFAULT_SLAVE_LAZY_FLUSH;
|
||||
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY;
|
||||
@@ -1626,6 +1716,12 @@ void initServerConfig(void) {
|
||||
server.assert_line = 0;
|
||||
server.bug_report_start = 0;
|
||||
server.watchdog_period = 0;
|
||||
|
||||
/* By default we want scripts to be always replicated by effects
|
||||
* (single commands executed by the script), and not by sending the
|
||||
* script to the slave / AOF. This is the new way starting from
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
server.lua_always_replicate_commands = 1;
|
||||
}
|
||||
|
||||
extern char **environ;
|
||||
@@ -1920,6 +2016,7 @@ void initServer(void) {
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
server.hz = server.config_hz;
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.clients = listCreate();
|
||||
@@ -2311,8 +2408,9 @@ void preventCommandReplication(client *c) {
|
||||
void call(client *c, int flags) {
|
||||
long long dirty, start, duration;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
/* Send the command to clients in MONITOR mode, only if the commands are
|
||||
* not generated from reading an AOF. */
|
||||
if (listLength(server.monitors) &&
|
||||
!server.loading &&
|
||||
@@ -2321,6 +2419,25 @@ void call(client *c, int flags) {
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
/* If the client is in the context of a transaction, reply with
|
||||
* +QUEUED and just accumulate the command in the client transaction
|
||||
* commands vector.
|
||||
*
|
||||
* Note that CALL_NOQUEUE can override this check and execute the command
|
||||
* straight away even if the client is in "MULTI" state. This is useful
|
||||
* every time we want to actually execute commands even if the client is
|
||||
* in MULTI state, like in scripting.c or in the implementation of EXEC
|
||||
* itself. */
|
||||
if (!(flags & CMD_CALL_NOQUEUE) &&
|
||||
c->flags & CLIENT_MULTI &&
|
||||
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
|
||||
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
|
||||
{
|
||||
queueMultiCommand(c);
|
||||
addReply(c,shared.queued);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialization: clear the flags that must be set by the command on
|
||||
* demand, and initialize the array for additional commands propagation. */
|
||||
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
|
||||
@@ -2359,8 +2476,11 @@ void call(client *c, int flags) {
|
||||
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
|
||||
}
|
||||
if (flags & CMD_CALL_STATS) {
|
||||
c->lastcmd->microseconds += duration;
|
||||
c->lastcmd->calls++;
|
||||
/* use the real command that was executed (cmd and lastamc) may be
|
||||
* different, in case of MULTI-EXEC or re-written commands such as
|
||||
* EXPIRE, GEOADD, etc. */
|
||||
real_cmd->microseconds += duration;
|
||||
real_cmd->calls++;
|
||||
}
|
||||
|
||||
/* Propagate the command into the AOF and replication link */
|
||||
@@ -2449,8 +2569,13 @@ int processCommand(client *c) {
|
||||
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
|
||||
if (!c->cmd) {
|
||||
flagTransaction(c);
|
||||
addReplyErrorFormat(c,"unknown command '%s'",
|
||||
(char*)c->argv[0]->ptr);
|
||||
sds args = sdsempty();
|
||||
int i;
|
||||
for (i=1; i < c->argc && sdslen(args) < 128; i++)
|
||||
args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
|
||||
addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s",
|
||||
(char*)c->argv[0]->ptr, args);
|
||||
sdsfree(args);
|
||||
return C_OK;
|
||||
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
|
||||
(c->argc < -c->cmd->arity)) {
|
||||
@@ -2498,16 +2623,21 @@ int processCommand(client *c) {
|
||||
*
|
||||
* First we try to free some memory if possible (if there are volatile
|
||||
* keys in the dataset). If there are not the only thing we can do
|
||||
* is returning an error. */
|
||||
if (server.maxmemory) {
|
||||
int retval = freeMemoryIfNeeded();
|
||||
* is returning an error.
|
||||
*
|
||||
* Note that we do not want to reclaim memory if we are here re-entering
|
||||
* the event loop since there is a busy Lua script running in timeout
|
||||
* condition, to avoid mixing the propagation of scripts with the propagation
|
||||
* of DELs due to eviction. */
|
||||
if (server.maxmemory && !server.lua_timedout) {
|
||||
int out_of_memory = freeMemoryIfNeeded() == C_ERR;
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return C_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
* is trying to execute is denied during OOM conditions? Error. */
|
||||
if ((c->cmd->flags & CMD_DENYOOM) && retval == C_ERR) {
|
||||
if ((c->cmd->flags & CMD_DENYOOM) && out_of_memory) {
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
@@ -2516,16 +2646,14 @@ int processCommand(client *c) {
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk
|
||||
* and if this is a master instance. */
|
||||
if (((server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR) ||
|
||||
server.aof_last_write_status == C_ERR) &&
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (deny_write_type != DISK_ERROR_TYPE_NONE &&
|
||||
server.masterhost == NULL &&
|
||||
(c->cmd->flags & CMD_WRITE ||
|
||||
c->cmd->proc == pingCommand))
|
||||
{
|
||||
flagTransaction(c);
|
||||
if (server.aof_last_write_status == C_OK)
|
||||
if (deny_write_type == DISK_ERROR_TYPE_RDB)
|
||||
addReply(c, shared.bgsaveerr);
|
||||
else
|
||||
addReplySds(c,
|
||||
@@ -2605,18 +2733,9 @@ int processCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Exec the command */
|
||||
if (c->flags & CLIENT_MULTI &&
|
||||
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
|
||||
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
|
||||
{
|
||||
queueMultiCommand(c);
|
||||
addReply(c,shared.queued);
|
||||
} else {
|
||||
call(c,CMD_CALL_FULL);
|
||||
c->woff = server.master_repl_offset;
|
||||
if (listLength(server.ready_keys))
|
||||
handleClientsBlockedOnKeys();
|
||||
}
|
||||
call(c,CMD_CALL_FULL);
|
||||
c->woff = server.master_repl_offset;
|
||||
if (listLength(server.ready_keys)) handleClientsBlockedOnKeys();
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -2711,6 +2830,32 @@ int prepareForShutdown(int flags) {
|
||||
|
||||
/*================================== Commands =============================== */
|
||||
|
||||
/* Sometimes Redis cannot accept write commands because there is a perstence
|
||||
* error with the RDB or AOF file, and Redis is configured in order to stop
|
||||
* accepting writes in such situation. This function returns if such a
|
||||
* condition is active, and the type of the condition.
|
||||
*
|
||||
* Function return values:
|
||||
*
|
||||
* DISK_ERROR_TYPE_NONE: No problems, we can accept writes.
|
||||
* DISK_ERROR_TYPE_AOF: Don't accept writes: AOF errors.
|
||||
* DISK_ERROR_TYPE_RDB: Don't accept writes: RDB errors.
|
||||
*/
|
||||
int writeCommandsDeniedByDiskError(void) {
|
||||
if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR)
|
||||
{
|
||||
return DISK_ERROR_TYPE_RDB;
|
||||
} else if (server.aof_state != AOF_OFF &&
|
||||
server.aof_last_write_status == C_ERR)
|
||||
{
|
||||
return DISK_ERROR_TYPE_AOF;
|
||||
} else {
|
||||
return DISK_ERROR_TYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return zero if strings are the same, non-zero if they are not.
|
||||
* The comparison is performed in a way that prevents an attacker to obtain
|
||||
* information about the nature of the strings just monitoring the execution
|
||||
@@ -2920,7 +3065,6 @@ void bytesToHuman(char *s, unsigned long long n) {
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lluB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
sprintf(s,"%.2fK",d);
|
||||
@@ -2950,7 +3094,6 @@ sds genRedisInfoString(char *section) {
|
||||
time_t uptime = server.unixtime-server.stat_starttime;
|
||||
int j;
|
||||
struct rusage self_ru, c_ru;
|
||||
unsigned long lol, bib;
|
||||
int allsections = 0, defsections = 0;
|
||||
int sections = 0;
|
||||
|
||||
@@ -2960,7 +3103,6 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
getrusage(RUSAGE_SELF, &self_ru);
|
||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||
getClientsMaxBuffers(&lol,&bib);
|
||||
|
||||
/* Server */
|
||||
if (allsections || defsections || !strcasecmp(section,"server")) {
|
||||
@@ -3000,6 +3142,7 @@ sds genRedisInfoString(char *section) {
|
||||
"uptime_in_seconds:%jd\r\n"
|
||||
"uptime_in_days:%jd\r\n"
|
||||
"hz:%d\r\n"
|
||||
"configured_hz:%d\r\n"
|
||||
"lru_clock:%ld\r\n"
|
||||
"executable:%s\r\n"
|
||||
"config_file:%s\r\n",
|
||||
@@ -3023,6 +3166,7 @@ sds genRedisInfoString(char *section) {
|
||||
(intmax_t)uptime,
|
||||
(intmax_t)(uptime/(3600*24)),
|
||||
server.hz,
|
||||
server.config_hz,
|
||||
(unsigned long) lruclock,
|
||||
server.executable ? server.executable : "",
|
||||
server.configfile ? server.configfile : "");
|
||||
@@ -3030,15 +3174,17 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
/* Clients */
|
||||
if (allsections || defsections || !strcasecmp(section,"clients")) {
|
||||
size_t maxin, maxout;
|
||||
getExpansiveClientsInfo(&maxin,&maxout);
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
"# Clients\r\n"
|
||||
"connected_clients:%lu\r\n"
|
||||
"client_longest_output_list:%lu\r\n"
|
||||
"client_biggest_input_buf:%lu\r\n"
|
||||
"client_recent_max_input_buffer:%zu\r\n"
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"blocked_clients:%d\r\n",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
lol, bib,
|
||||
maxin, maxout,
|
||||
server.blocked_clients);
|
||||
}
|
||||
|
||||
@@ -3048,6 +3194,7 @@ sds genRedisInfoString(char *section) {
|
||||
char peak_hmem[64];
|
||||
char total_system_hmem[64];
|
||||
char used_memory_lua_hmem[64];
|
||||
char used_memory_scripts_hmem[64];
|
||||
char used_memory_rss_hmem[64];
|
||||
char maxmemory_hmem[64];
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
@@ -3067,6 +3214,7 @@ sds genRedisInfoString(char *section) {
|
||||
bytesToHuman(peak_hmem,server.stat_peak_memory);
|
||||
bytesToHuman(total_system_hmem,total_system_mem);
|
||||
bytesToHuman(used_memory_lua_hmem,memory_lua);
|
||||
bytesToHuman(used_memory_scripts_hmem,mh->lua_caches);
|
||||
bytesToHuman(used_memory_rss_hmem,server.cron_malloc_stats.process_rss);
|
||||
bytesToHuman(maxmemory_hmem,server.maxmemory);
|
||||
|
||||
@@ -3091,6 +3239,9 @@ sds genRedisInfoString(char *section) {
|
||||
"total_system_memory_human:%s\r\n"
|
||||
"used_memory_lua:%lld\r\n"
|
||||
"used_memory_lua_human:%s\r\n"
|
||||
"used_memory_scripts:%lld\r\n"
|
||||
"used_memory_scripts_human:%s\r\n"
|
||||
"number_of_cached_scripts:%lu\r\n"
|
||||
"maxmemory:%lld\r\n"
|
||||
"maxmemory_human:%s\r\n"
|
||||
"maxmemory_policy:%s\r\n"
|
||||
@@ -3102,6 +3253,11 @@ sds genRedisInfoString(char *section) {
|
||||
"rss_overhead_bytes:%zu\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_fragmentation_bytes:%zu\r\n"
|
||||
"mem_not_counted_for_evict:%zu\r\n"
|
||||
"mem_replication_backlog:%zu\r\n"
|
||||
"mem_clients_slaves:%zu\r\n"
|
||||
"mem_clients_normal:%zu\r\n"
|
||||
"mem_aof_buffer:%zu\r\n"
|
||||
"mem_allocator:%s\r\n"
|
||||
"active_defrag_running:%d\r\n"
|
||||
"lazyfree_pending_objects:%zu\r\n",
|
||||
@@ -3123,6 +3279,9 @@ sds genRedisInfoString(char *section) {
|
||||
total_system_hmem,
|
||||
memory_lua,
|
||||
used_memory_lua_hmem,
|
||||
(long long) mh->lua_caches,
|
||||
used_memory_scripts_hmem,
|
||||
dictSize(server.lua_scripts),
|
||||
server.maxmemory,
|
||||
maxmemory_hmem,
|
||||
evict_policy,
|
||||
@@ -3134,6 +3293,11 @@ sds genRedisInfoString(char *section) {
|
||||
mh->rss_extra_bytes,
|
||||
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */
|
||||
mh->total_frag_bytes, /* named so for backwards compatibility */
|
||||
freeMemoryGetNotCountedMemory(),
|
||||
mh->repl_backlog,
|
||||
mh->clients_slaves,
|
||||
mh->clients_normal,
|
||||
mh->aof_buffer,
|
||||
ZMALLOC_LIB,
|
||||
server.active_defrag_running,
|
||||
lazyfreeGetPendingObjectsCount()
|
||||
@@ -3423,14 +3587,14 @@ sds genRedisInfoString(char *section) {
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
"# CPU\r\n"
|
||||
"used_cpu_sys:%.2f\r\n"
|
||||
"used_cpu_user:%.2f\r\n"
|
||||
"used_cpu_sys_children:%.2f\r\n"
|
||||
"used_cpu_user_children:%.2f\r\n",
|
||||
(float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
|
||||
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000);
|
||||
"used_cpu_sys:%ld.%06ld\r\n"
|
||||
"used_cpu_user:%ld.%06ld\r\n"
|
||||
"used_cpu_sys_children:%ld.%06ld\r\n"
|
||||
"used_cpu_user_children:%ld.%06ld\r\n",
|
||||
(long)self_ru.ru_stime.tv_sec, (long)self_ru.ru_stime.tv_usec,
|
||||
(long)self_ru.ru_utime.tv_sec, (long)self_ru.ru_utime.tv_usec,
|
||||
(long)c_ru.ru_stime.tv_sec, (long)c_ru.ru_stime.tv_usec,
|
||||
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
||||
}
|
||||
|
||||
/* Command statistics */
|
||||
@@ -3578,7 +3742,7 @@ void usage(void) {
|
||||
fprintf(stderr," ./redis-server (run the server with default conf)\n");
|
||||
fprintf(stderr," ./redis-server /etc/redis/6379.conf\n");
|
||||
fprintf(stderr," ./redis-server --port 7777\n");
|
||||
fprintf(stderr," ./redis-server --port 7777 --slaveof 127.0.0.1 8888\n");
|
||||
fprintf(stderr," ./redis-server --port 7777 --replicaof 127.0.0.1 8888\n");
|
||||
fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n\n");
|
||||
fprintf(stderr,"Sentinel mode:\n");
|
||||
fprintf(stderr," ./redis-server /etc/sentinel.conf --sentinel\n");
|
||||
@@ -3865,6 +4029,8 @@ int main(int argc, char **argv) {
|
||||
return endianconvTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "crc64")) {
|
||||
return crc64Test(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "zmalloc")) {
|
||||
return zmalloc_test(argc, argv);
|
||||
}
|
||||
|
||||
return -1; /* test not found */
|
||||
|
||||
+44
-7
@@ -78,12 +78,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define C_ERR -1
|
||||
|
||||
/* Static server configuration */
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_DEFAULT_DYNAMIC_HZ 1 /* Adapt hz to # of clients.*/
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_MIN_HZ 1
|
||||
#define CONFIG_MAX_HZ 500
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* default client timeout: infinite */
|
||||
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
||||
#define CONFIG_DEFAULT_DBNUM 16
|
||||
#define CONFIG_MAX_LINE 1024
|
||||
#define CRON_DBS_PER_CALL 16
|
||||
@@ -91,7 +93,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define PROTO_SHARED_SELECT_CMDS 10
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define AOF_REWRITE_PERC 100
|
||||
#define AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
@@ -129,6 +131,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP NULL
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT 0
|
||||
#define CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
@@ -413,6 +416,9 @@ typedef long long mstime_t; /* millisecond 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)
|
||||
/* --- call() special semantics that can be requested by the caller
|
||||
* --- but are not included in CALL_FULL. */
|
||||
#define CMD_CALL_NOQUEUE (1<<4)
|
||||
|
||||
/* Command propagation flags, see propagate() function */
|
||||
#define PROPAGATE_NONE 0
|
||||
@@ -619,6 +625,13 @@ typedef struct redisObject {
|
||||
|
||||
struct evictionPoolEntry; /* Defined in evict.c */
|
||||
|
||||
/* This structure is used in order to represent the output buffer of a client,
|
||||
* which is actually a linked list of blocks like that, that is: client->reply. */
|
||||
typedef struct clientReplyBlock {
|
||||
size_t size, used;
|
||||
char buf[];
|
||||
} clientReplyBlock;
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
@@ -665,6 +678,7 @@ typedef struct blockingState {
|
||||
robj *xread_group; /* XREADGROUP group name. */
|
||||
robj *xread_consumer; /* XREADGROUP consumer name. */
|
||||
mstime_t xread_retry_time, xread_retry_ttl;
|
||||
int xread_group_noack;
|
||||
|
||||
/* BLOCKED_WAIT */
|
||||
int numreplicas; /* Number of replicas we are waiting for ACK. */
|
||||
@@ -700,6 +714,7 @@ typedef struct client {
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
size_t qb_pos; /* The position we have read in querybuf. */
|
||||
sds pending_querybuf; /* If this client is flagged as master, this buffer
|
||||
represents the yet not applied portion of the
|
||||
replication stream that we are receiving from
|
||||
@@ -771,7 +786,7 @@ struct sharedObjectsStruct {
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
|
||||
*rpop, *lpop, *lpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -843,6 +858,7 @@ struct redisMemOverhead {
|
||||
size_t clients_slaves;
|
||||
size_t clients_normal;
|
||||
size_t aof_buffer;
|
||||
size_t lua_caches;
|
||||
size_t overhead_total;
|
||||
size_t dataset;
|
||||
size_t total_keys;
|
||||
@@ -915,6 +931,10 @@ struct redisServer {
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
char *executable; /* Absolute executable file path. */
|
||||
char **exec_argv; /* Executable argv vector (copy). */
|
||||
int dynamic_hz; /* Change hz value depending on # of clients. */
|
||||
int config_hz; /* Configured HZ value. May be different than
|
||||
the actual 'hz' field value if dynamic-hz
|
||||
is enabled. */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
@@ -1141,6 +1161,7 @@ struct redisServer {
|
||||
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
|
||||
int repl_serve_stale_data; /* Serve stale data when link is down? */
|
||||
int repl_slave_ro; /* Slave is read only? */
|
||||
int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */
|
||||
time_t repl_down_since; /* Unix time at which link with master went down */
|
||||
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
|
||||
int slave_priority; /* Reported in INFO and used by Sentinel. */
|
||||
@@ -1214,11 +1235,16 @@ struct redisServer {
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
|
||||
int cluster_module_flags; /* Set of flags that Redis modules are able
|
||||
to set in order to suppress certain
|
||||
native Redis Cluster features. Check the
|
||||
REDISMODULE_CLUSTER_FLAG_*. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
client *lua_caller; /* The client running EVAL right now, or NULL */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
@@ -1399,6 +1425,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void *addDeferredMultiBulkLength(client *c);
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length);
|
||||
void processInputBuffer(client *c);
|
||||
void processInputBufferAndReplicate(client *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
@@ -1422,6 +1449,7 @@ void addReplySubcommandSyntaxError(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
void freeClientReplyValue(void *o);
|
||||
void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
@@ -1574,6 +1602,11 @@ void startLoading(FILE *fp);
|
||||
void loadingProgress(off_t pos);
|
||||
void stopLoading(void);
|
||||
|
||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||
#define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */
|
||||
int writeCommandsDeniedByDiskError(void);
|
||||
|
||||
/* RDB persistence */
|
||||
#include "rdb.h"
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
@@ -1663,6 +1696,7 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
|
||||
|
||||
/* Core functions */
|
||||
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
|
||||
size_t freeMemoryGetNotCountedMemory();
|
||||
int freeMemoryIfNeeded(void);
|
||||
int processCommand(client *c);
|
||||
void setupSignalHandlers(void);
|
||||
@@ -1812,6 +1846,7 @@ int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
void slotToKeyFlushAsync(void);
|
||||
size_t lazyfreeGetPendingObjectsCount(void);
|
||||
void freeObjAsync(robj *o);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
@@ -1856,6 +1891,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body);
|
||||
void processUnblockedClients(void);
|
||||
void blockClient(client *c, int btype);
|
||||
void unblockClient(client *c);
|
||||
void queueClientForReprocessing(client *c);
|
||||
void replyToBlockedClientTimedOut(client *c);
|
||||
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit);
|
||||
void disconnectAllBlockedClients(void);
|
||||
@@ -1969,7 +2005,7 @@ void ttlCommand(client *c);
|
||||
void touchCommand(client *c);
|
||||
void pttlCommand(client *c);
|
||||
void persistCommand(client *c);
|
||||
void slaveofCommand(client *c);
|
||||
void replicaofCommand(client *c);
|
||||
void roleCommand(client *c);
|
||||
void debugCommand(client *c);
|
||||
void msetCommand(client *c);
|
||||
@@ -2080,6 +2116,7 @@ void xclaimCommand(client *c);
|
||||
void xinfoCommand(client *c);
|
||||
void xdelCommand(client *c);
|
||||
void xtrimCommand(client *c);
|
||||
void lolwutCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
@@ -108,5 +108,6 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
|
||||
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
|
||||
streamNACK *streamCreateNACK(streamConsumer *consumer);
|
||||
void streamDecodeID(void *buf, streamID *id);
|
||||
int streamCompareID(streamID *a, streamID *b);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -596,6 +596,9 @@ void rpoplpushCommand(client *c) {
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
if (c->cmd->proc == brpoplpushCommand) {
|
||||
rewriteClientCommandVector(c,3,shared.rpoplpush,c->argv[1],c->argv[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-6
@@ -516,11 +516,7 @@ void spopWithCountCommand(client *c) {
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
|
||||
/* Assign the new set as the key value. */
|
||||
incrRefCount(set); /* Protect the old set value. */
|
||||
dbOverwrite(c->db,c->argv[1],newset);
|
||||
|
||||
/* Tranfer the old set to the client and release it. */
|
||||
/* Transfer the old set to the client. */
|
||||
setTypeIterator *si;
|
||||
si = setTypeInitIterator(set);
|
||||
while((encoding = setTypeNext(si,&sdsele,&llele)) != -1) {
|
||||
@@ -539,7 +535,9 @@ void spopWithCountCommand(client *c) {
|
||||
decrRefCount(objele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
decrRefCount(set);
|
||||
|
||||
/* Assign the new set as the key value. */
|
||||
dbOverwrite(c->db,c->argv[1],newset);
|
||||
}
|
||||
|
||||
/* Don't propagate the command itself even if we incremented the
|
||||
|
||||
+170
-59
@@ -349,7 +349,8 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
lp = lpAppendInteger(lp,lp_count);
|
||||
|
||||
/* Insert back into the tree in order to update the listpack pointer. */
|
||||
raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);
|
||||
if (ri.data != lp)
|
||||
raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);
|
||||
s->length++;
|
||||
s->last_id = id;
|
||||
if (added_id) *added_id = id;
|
||||
@@ -548,13 +549,20 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
si->master_fields_count = lpGetInteger(si->lp_ele);
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek first field. */
|
||||
si->master_fields_start = si->lp_ele;
|
||||
/* Skip master fileds to seek the first entry. */
|
||||
for (uint64_t i = 0; i < si->master_fields_count; i++)
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele);
|
||||
/* We are now pointing the zero term of the master entry. If
|
||||
* we are iterating in reverse order, we need to seek the
|
||||
* end of the listpack. */
|
||||
if (si->rev) si->lp_ele = lpLast(si->lp);
|
||||
/* We are now pointing to the first field of the master entry.
|
||||
* We need to seek either the first or the last entry depending
|
||||
* on the direction of the iteration. */
|
||||
if (!si->rev) {
|
||||
/* If we are iterating in normal order, skip the master fields
|
||||
* to seek the first actual entry. */
|
||||
for (uint64_t i = 0; i < si->master_fields_count; i++)
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele);
|
||||
} else {
|
||||
/* If we are iterating in reverse direction, just seek the
|
||||
* last part of the last entry in the listpack (that is, the
|
||||
* fields count). */
|
||||
si->lp_ele = lpLast(si->lp);
|
||||
}
|
||||
} else if (si->rev) {
|
||||
/* If we are itereating in the reverse order, and this is not
|
||||
* the first entry emitted for this listpack, then we already
|
||||
@@ -705,10 +713,23 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
|
||||
/* Change the valid/deleted entries count in the master entry. */
|
||||
unsigned char *p = lpFirst(lp);
|
||||
aux = lpGetInteger(p);
|
||||
lp = lpReplaceInteger(lp,&p,aux-1);
|
||||
p = lpNext(lp,p); /* Seek deleted field. */
|
||||
aux = lpGetInteger(p);
|
||||
lp = lpReplaceInteger(lp,&p,aux+1);
|
||||
|
||||
if (aux == 1) {
|
||||
/* If this is the last element in the listpack, we can remove the whole
|
||||
* node. */
|
||||
lpFree(lp);
|
||||
raxRemove(si->stream->rax,si->ri.key,si->ri.key_len,NULL);
|
||||
} else {
|
||||
/* In the base case we alter the counters of valid/deleted entries. */
|
||||
lp = lpReplaceInteger(lp,&p,aux-1);
|
||||
p = lpNext(lp,p); /* Seek deleted field. */
|
||||
aux = lpGetInteger(p);
|
||||
lp = lpReplaceInteger(lp,&p,aux+1);
|
||||
|
||||
/* Update the listpack with the new pointer. */
|
||||
if (si->lp != lp)
|
||||
raxInsert(si->stream->rax,si->ri.key,si->ri.key_len,lp,NULL);
|
||||
}
|
||||
|
||||
/* Update the number of entries counter. */
|
||||
si->stream->length--;
|
||||
@@ -748,6 +769,7 @@ int streamDeleteItem(stream *s, streamID *id) {
|
||||
streamIteratorRemoveEntry(&si,&myid);
|
||||
deleted = 1;
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -905,24 +927,26 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
* will not require extra lookups. We'll fix the problem later
|
||||
* if we find that there is already a entry for this ID. */
|
||||
streamNACK *nack = streamCreateNACK(consumer);
|
||||
int retval = 0;
|
||||
retval += raxTryInsert(group->pel,buf,sizeof(buf),nack,NULL);
|
||||
retval += raxTryInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
int group_inserted =
|
||||
raxTryInsert(group->pel,buf,sizeof(buf),nack,NULL);
|
||||
int consumer_inserted =
|
||||
raxTryInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
|
||||
/* Now we can check if the entry was already busy, and
|
||||
* in that case reassign the entry to the new consumer. */
|
||||
if (retval == 0) {
|
||||
* in that case reassign the entry to the new consumer,
|
||||
* or update it if the consumer is the same as before. */
|
||||
if (group_inserted == 0) {
|
||||
streamFreeNACK(nack);
|
||||
nack = raxFind(group->pel,buf,sizeof(buf));
|
||||
serverAssert(nack != raxNotFound);
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
/* Update the consumer and idle time. */
|
||||
/* Update the consumer and NACK metadata. */
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = mstime();
|
||||
nack->delivery_count++;
|
||||
nack->delivery_count = 1;
|
||||
/* Add the entry in the new consumer local PEL. */
|
||||
raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
} else if (retval == 1) {
|
||||
} else if (group_inserted == 1 && consumer_inserted == 0) {
|
||||
serverPanic("NACK half-created. Should not be possible.");
|
||||
}
|
||||
|
||||
@@ -1040,15 +1064,20 @@ int string2ull(const char *s, unsigned long long *value) {
|
||||
* to the client, otherwise C_OK is returned. The ID may be in incomplete
|
||||
* form, just stating the milliseconds time part of the stream. In such a case
|
||||
* the missing part is set according to the value of 'missing_seq' parameter.
|
||||
*
|
||||
* The IDs "-" and "+" specify respectively the minimum and maximum IDs
|
||||
* that can be represented.
|
||||
* that can be represented. If 'strict' is set to 1, "-" and "+" will be
|
||||
* treated as an invalid ID.
|
||||
*
|
||||
* If 'c' is set to NULL, no reply is sent to the client. */
|
||||
int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
int streamGenericParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq, int strict) {
|
||||
char buf[128];
|
||||
if (sdslen(o->ptr) > sizeof(buf)-1) goto invalid;
|
||||
memcpy(buf,o->ptr,sdslen(o->ptr)+1);
|
||||
|
||||
if (strict && (buf[0] == '-' || buf[0] == '+') && buf[1] == '\0')
|
||||
goto invalid;
|
||||
|
||||
/* Handle the "-" and "+" special cases. */
|
||||
if (buf[0] == '-' && buf[1] == '\0') {
|
||||
id->ms = 0;
|
||||
@@ -1077,7 +1106,33 @@ invalid:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* XADD key [MAXLEN <count>] <ID or *> [field value] [field value] ... */
|
||||
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
|
||||
* 0, to be used when - and + are accetable IDs. */
|
||||
int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
return streamGenericParseIDOrReply(c,o,id,missing_seq,0);
|
||||
}
|
||||
|
||||
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
|
||||
* 1, to be used when we want to return an error if the special IDs + or -
|
||||
* are provided. */
|
||||
int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
return streamGenericParseIDOrReply(c,o,id,missing_seq,1);
|
||||
}
|
||||
|
||||
/* We propagate MAXLEN ~ <count> as MAXLEN = <resulting-len-of-stream>
|
||||
* otherwise trimming is no longer determinsitic on replicas / AOF. */
|
||||
void streamRewriteApproxMaxlen(client *c, stream *s, int maxlen_arg_idx) {
|
||||
robj *maxlen_obj = createStringObjectFromLongLong(s->length);
|
||||
robj *equal_obj = createStringObject("=",1);
|
||||
|
||||
rewriteClientCommandArgument(c,maxlen_arg_idx,maxlen_obj);
|
||||
rewriteClientCommandArgument(c,maxlen_arg_idx-1,equal_obj);
|
||||
|
||||
decrRefCount(equal_obj);
|
||||
decrRefCount(maxlen_obj);
|
||||
}
|
||||
|
||||
/* XADD key [MAXLEN [~|=] <count>] <ID or *> [field value] [field value] ... */
|
||||
void xaddCommand(client *c) {
|
||||
streamID id;
|
||||
int id_given = 0; /* Was an ID different than "*" specified? */
|
||||
@@ -1097,11 +1152,14 @@ void xaddCommand(client *c) {
|
||||
* creation. */
|
||||
break;
|
||||
} else if (!strcasecmp(opt,"maxlen") && moreargs) {
|
||||
approx_maxlen = 0;
|
||||
char *next = c->argv[i+1]->ptr;
|
||||
/* Check for the form MAXLEN ~ <count>. */
|
||||
if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {
|
||||
approx_maxlen = 1;
|
||||
i++;
|
||||
} else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {
|
||||
i++;
|
||||
}
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[i+1],&maxlen,NULL)
|
||||
!= C_OK) return;
|
||||
@@ -1114,7 +1172,7 @@ void xaddCommand(client *c) {
|
||||
maxlen_arg_idx = i;
|
||||
} else {
|
||||
/* If we are here is a syntax error or a valid ID. */
|
||||
if (streamParseIDOrReply(c,c->argv[i],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[i],&id,0) != C_OK) return;
|
||||
id_given = 1;
|
||||
break;
|
||||
}
|
||||
@@ -1148,18 +1206,12 @@ void xaddCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Remove older elements if MAXLEN was specified. */
|
||||
if (maxlen >= 0) {
|
||||
if (!streamTrimByLength(s,maxlen,approx_maxlen)) {
|
||||
/* If no trimming was performed, for instance because approximated
|
||||
* trimming length was specified, rewrite the MAXLEN argument
|
||||
* as zero, so that the command is propagated without trimming. */
|
||||
robj *zeroobj = createStringObjectFromLongLong(0);
|
||||
rewriteClientCommandArgument(c,maxlen_arg_idx,zeroobj);
|
||||
decrRefCount(zeroobj);
|
||||
} else {
|
||||
/* Notify xtrim event if needed. */
|
||||
if (streamTrimByLength(s,maxlen,approx_maxlen)) {
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
|
||||
}
|
||||
if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
|
||||
}
|
||||
|
||||
/* Let's rewrite the ID argument with the one actually generated for
|
||||
@@ -1340,6 +1392,14 @@ void xreadCommand(client *c) {
|
||||
}
|
||||
|
||||
if (strcmp(c->argv[i]->ptr,"$") == 0) {
|
||||
if (xreadgroup) {
|
||||
addReplyError(c,"The $ ID is meaningless in the context of "
|
||||
"XREADGROUP: you want to read the history of "
|
||||
"this consumer by specifying a proper ID, or "
|
||||
"use the > ID to get new messages. The $ ID would "
|
||||
"just return an empty result set.");
|
||||
goto cleanup;
|
||||
}
|
||||
if (o) {
|
||||
stream *s = o->ptr;
|
||||
ids[id_idx] = s->last_id;
|
||||
@@ -1349,16 +1409,20 @@ void xreadCommand(client *c) {
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(c->argv[i]->ptr,">") == 0) {
|
||||
if (!xreadgroup || groupname == NULL) {
|
||||
if (!xreadgroup) {
|
||||
addReplyError(c,"The > ID can be specified only when calling "
|
||||
"XREADGROUP using the GROUP <group> "
|
||||
"<consumer> option.");
|
||||
goto cleanup;
|
||||
}
|
||||
ids[id_idx] = group->last_id;
|
||||
/* We use just the maximum ID to signal this is a ">" ID, anyway
|
||||
* the code handling the blocking clients will have to update the
|
||||
* ID later in order to match the changing consumer group last ID. */
|
||||
ids[id_idx].ms = UINT64_MAX;
|
||||
ids[id_idx].seq = UINT64_MAX;
|
||||
continue;
|
||||
}
|
||||
if (streamParseIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
|
||||
if (streamParseStrictIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -1370,9 +1434,36 @@ void xreadCommand(client *c) {
|
||||
if (o == NULL) continue;
|
||||
stream *s = o->ptr;
|
||||
streamID *gt = ids+i; /* ID must be greater than this. */
|
||||
if (s->last_id.ms > gt->ms ||
|
||||
(s->last_id.ms == gt->ms && s->last_id.seq > gt->seq))
|
||||
{
|
||||
int serve_synchronously = 0;
|
||||
|
||||
/* Check if there are the conditions to serve the client synchronously. */
|
||||
if (groups) {
|
||||
/* If the consumer is blocked on a group, we always serve it
|
||||
* synchronously (serving its local history) if the ID specified
|
||||
* was not the special ">" ID. */
|
||||
if (gt->ms != UINT64_MAX ||
|
||||
gt->seq != UINT64_MAX)
|
||||
{
|
||||
serve_synchronously = 1;
|
||||
} else {
|
||||
/* We also want to serve a consumer in a consumer group
|
||||
* synchronously in case the group top item delivered is smaller
|
||||
* than what the stream has inside. */
|
||||
streamID *last = &groups[i]->last_id;
|
||||
if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
|
||||
serve_synchronously = 1;
|
||||
*gt = *last;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* For consumers without a group, we serve synchronously if we can
|
||||
* actually provide at least one item from the stream. */
|
||||
if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
|
||||
serve_synchronously = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (serve_synchronously) {
|
||||
arraylen++;
|
||||
if (arraylen == 1) arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
/* streamReplyWithRange() handles the 'start' ID as inclusive,
|
||||
@@ -1426,6 +1517,7 @@ void xreadCommand(client *c) {
|
||||
incrRefCount(consumername);
|
||||
c->bpop.xread_group = groupname;
|
||||
c->bpop.xread_consumer = consumername;
|
||||
c->bpop.xread_group_noack = noack;
|
||||
} else {
|
||||
c->bpop.xread_group = NULL;
|
||||
c->bpop.xread_consumer = NULL;
|
||||
@@ -1563,7 +1655,7 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
/* XGROUP CREATE <key> <groupname> <id or $>
|
||||
* XGROUP SETID <key> <id or $>
|
||||
* XGROUP SETID <key> <groupname> <id or $>
|
||||
* XGROUP DESTROY <key> <groupname>
|
||||
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
|
||||
void xgroupCommand(client *c) {
|
||||
@@ -1604,7 +1696,7 @@ NULL
|
||||
streamID id;
|
||||
if (!strcmp(c->argv[4]->ptr,"$")) {
|
||||
id = s->last_id;
|
||||
} else if (streamParseIDOrReply(c,c->argv[4],&id,0) != C_OK) {
|
||||
} else if (streamParseStrictIDOrReply(c,c->argv[4],&id,0) != C_OK) {
|
||||
return;
|
||||
}
|
||||
streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
|
||||
@@ -1681,7 +1773,7 @@ void xackCommand(client *c) {
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL: it will have a reference to the
|
||||
@@ -1937,9 +2029,9 @@ void xclaimCommand(client *c) {
|
||||
* the client successfully claimed some message, so it should be
|
||||
* executed in a "all or nothing" fashion. */
|
||||
int j;
|
||||
for (j = 4; j < c->argc; j++) {
|
||||
for (j = 5; j < c->argc; j++) {
|
||||
streamID id;
|
||||
if (streamParseIDOrReply(NULL,c->argv[j],&id,0) != C_OK) break;
|
||||
if (streamParseStrictIDOrReply(NULL,c->argv[j],&id,0) != C_OK) break;
|
||||
}
|
||||
int last_id_arg = j-1; /* Next time we iterate the IDs we now the range. */
|
||||
|
||||
@@ -1962,12 +2054,12 @@ void xclaimCommand(client *c) {
|
||||
} else if (!strcasecmp(opt,"TIME") && moreargs) {
|
||||
j++;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&deliverytime,
|
||||
"Invalid IDLE option argument for XCLAIM")
|
||||
"Invalid TIME option argument for XCLAIM")
|
||||
!= C_OK) return;
|
||||
} else if (!strcasecmp(opt,"RETRYCOUNT") && moreargs) {
|
||||
j++;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&retrycount,
|
||||
"Invalid IDLE option argument for XCLAIM")
|
||||
"Invalid RETRYCOUNT option argument for XCLAIM")
|
||||
!= C_OK) return;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unrecognized XCLAIM option '%s'",opt);
|
||||
@@ -1980,9 +2072,9 @@ void xclaimCommand(client *c) {
|
||||
* do some sanity check on it, and set the deliverytime to now
|
||||
* (which is a sane choice usually) if the value is bogus.
|
||||
* To raise an error here is not wise because clients may compute
|
||||
* the idle time doing some math startin from their local time,
|
||||
* the idle time doing some math starting from their local time,
|
||||
* and this is not a good excuse to fail in case, for instance,
|
||||
* the computed time is a bit in the future from our POV. */
|
||||
* the computer time is a bit in the future from our POV. */
|
||||
if (deliverytime < 0 || deliverytime > now) deliverytime = now;
|
||||
} else {
|
||||
/* If no IDLE/TIME option was passed, we want the last delivery
|
||||
@@ -1998,7 +2090,7 @@ void xclaimCommand(client *c) {
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL. */
|
||||
@@ -2081,18 +2173,22 @@ void xdelCommand(client *c) {
|
||||
* executed because at some point an invalid ID is parsed. */
|
||||
streamID id;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
if (streamParseIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
}
|
||||
|
||||
/* Actually apply the command. */
|
||||
int deleted = 0;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
streamParseIDOrReply(c,c->argv[j],&id,0); /* Retval already checked. */
|
||||
streamParseStrictIDOrReply(c,c->argv[j],&id,0); /* Retval already checked. */
|
||||
deleted += streamDeleteItem(s,&id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
|
||||
/* Propagate the write if needed. */
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
@@ -2100,7 +2196,7 @@ void xdelCommand(client *c) {
|
||||
*
|
||||
* List of options:
|
||||
*
|
||||
* MAXLEN [~] <count> -- Trim so that the stream will be capped at
|
||||
* MAXLEN [~|=] <count> -- Trim so that the stream will be capped at
|
||||
* the specified length. Use ~ before the
|
||||
* count in order to demand approximated trimming
|
||||
* (like XADD MAXLEN option).
|
||||
@@ -2119,9 +2215,10 @@ void xtrimCommand(client *c) {
|
||||
|
||||
/* Argument parsing. */
|
||||
int trim_strategy = TRIM_STRATEGY_NONE;
|
||||
long long maxlen = 0; /* 0 means no maximum length. */
|
||||
long long maxlen = -1; /* If left to -1 no trimming is performed. */
|
||||
int approx_maxlen = 0; /* If 1 only delete whole radix tree nodes, so
|
||||
the maxium length is not applied verbatim. */
|
||||
int maxlen_arg_idx = 0; /* Index of the count in MAXLEN, for rewriting. */
|
||||
|
||||
/* Parse options. */
|
||||
int i = 2; /* Start of options. */
|
||||
@@ -2129,16 +2226,25 @@ void xtrimCommand(client *c) {
|
||||
int moreargs = (c->argc-1) - i; /* Number of additional arguments. */
|
||||
char *opt = c->argv[i]->ptr;
|
||||
if (!strcasecmp(opt,"maxlen") && moreargs) {
|
||||
approx_maxlen = 0;
|
||||
trim_strategy = TRIM_STRATEGY_MAXLEN;
|
||||
char *next = c->argv[i+1]->ptr;
|
||||
/* Check for the form MAXLEN ~ <count>. */
|
||||
if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {
|
||||
approx_maxlen = 1;
|
||||
i++;
|
||||
} else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {
|
||||
i++;
|
||||
}
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[i+1],&maxlen,NULL)
|
||||
!= C_OK) return;
|
||||
|
||||
if (maxlen < 0) {
|
||||
addReplyError(c,"The MAXLEN argument must be >= 0.");
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
maxlen_arg_idx = i;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
@@ -2159,11 +2265,12 @@ void xtrimCommand(client *c) {
|
||||
signalModifiedKey(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);
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
/* XINFO CONSUMERS key group
|
||||
/* XINFO CONSUMERS <key> <group>
|
||||
* XINFO GROUPS <key>
|
||||
* XINFO STREAM <key>
|
||||
* XINFO HELP. */
|
||||
@@ -2241,18 +2348,20 @@ NULL
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
streamCG *cg = ri.data;
|
||||
addReplyMultiBulkLen(c,6);
|
||||
addReplyMultiBulkLen(c,8);
|
||||
addReplyStatus(c,"name");
|
||||
addReplyBulkCBuffer(c,ri.key,ri.key_len);
|
||||
addReplyStatus(c,"consumers");
|
||||
addReplyLongLong(c,raxSize(cg->consumers));
|
||||
addReplyStatus(c,"pending");
|
||||
addReplyLongLong(c,raxSize(cg->pel));
|
||||
addReplyStatus(c,"last-delivered-id");
|
||||
addReplyStreamID(c,&cg->last_id);
|
||||
}
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
|
||||
/* XINFO STREAM <key> (or the alias XINFO <key>). */
|
||||
addReplyMultiBulkLen(c,12);
|
||||
addReplyMultiBulkLen(c,14);
|
||||
addReplyStatus(c,"length");
|
||||
addReplyLongLong(c,s->length);
|
||||
addReplyStatus(c,"radix-tree-keys");
|
||||
@@ -2261,6 +2370,8 @@ NULL
|
||||
addReplyLongLong(c,s->rax->numnodes);
|
||||
addReplyStatus(c,"groups");
|
||||
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
|
||||
addReplyStatus(c,"last-generated-id");
|
||||
addReplyStreamID(c,&s->last_id);
|
||||
|
||||
/* To emit the first/last entry we us the streamReplyWithRange()
|
||||
* API. */
|
||||
|
||||
+56
-7
@@ -244,6 +244,61 @@ int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* Update the score of an elmenent inside the sorted set skiplist.
|
||||
* Note that the element must exist and must match 'score'.
|
||||
* This function does not update the score in the hash table side, the
|
||||
* caller should take care of it.
|
||||
*
|
||||
* Note that this function attempts to just update the node, in case after
|
||||
* the score update, the node would be exactly at the same position.
|
||||
* Otherwise the skiplist is modified by removing and re-adding a new
|
||||
* element, which is more costly.
|
||||
*
|
||||
* The function returns the updated element skiplist node pointer. */
|
||||
zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
||||
int i;
|
||||
|
||||
/* We need to seek to element to update to start: this is useful anyway,
|
||||
* we'll have to update or remove it. */
|
||||
x = zsl->header;
|
||||
for (i = zsl->level-1; i >= 0; i--) {
|
||||
while (x->level[i].forward &&
|
||||
(x->level[i].forward->score < curscore ||
|
||||
(x->level[i].forward->score == curscore &&
|
||||
sdscmp(x->level[i].forward->ele,ele) < 0)))
|
||||
{
|
||||
x = x->level[i].forward;
|
||||
}
|
||||
update[i] = x;
|
||||
}
|
||||
|
||||
/* Jump to our element: note that this function assumes that the
|
||||
* element with the matching score exists. */
|
||||
x = x->level[0].forward;
|
||||
serverAssert(x && curscore == x->score && sdscmp(x->ele,ele) == 0);
|
||||
|
||||
/* If the node, after the score update, would be still exactly
|
||||
* at the same position, we can just update the score without
|
||||
* actually removing and re-inserting the element in the skiplist. */
|
||||
if ((x->backward == NULL || x->backward->score < newscore) &&
|
||||
(x->level[0].forward == NULL || x->level[0].forward->score > newscore))
|
||||
{
|
||||
x->score = newscore;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* No way to reuse the old node: we need to remove and insert a new
|
||||
* one at a different place. */
|
||||
zslDeleteNode(zsl, x, update);
|
||||
zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele);
|
||||
/* We reused the old node x->ele SDS string, free the node now
|
||||
* since zslInsert created a new one. */
|
||||
x->ele = NULL;
|
||||
zslFreeNode(x);
|
||||
return newnode;
|
||||
}
|
||||
|
||||
int zslValueGteMin(double value, zrangespec *spec) {
|
||||
return spec->minex ? (value > spec->min) : (value >= spec->min);
|
||||
}
|
||||
@@ -1341,13 +1396,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
|
||||
/* Remove and re-insert when score changes. */
|
||||
if (score != curscore) {
|
||||
zskiplistNode *node;
|
||||
serverAssert(zslDelete(zs->zsl,curscore,ele,&node));
|
||||
znode = zslInsert(zs->zsl,score,node->ele);
|
||||
/* We reused the node->ele SDS string, free the node now
|
||||
* since zslInsert created a new one. */
|
||||
node->ele = NULL;
|
||||
zslFreeNode(node);
|
||||
znode = zslUpdateScore(zs->zsl,curscore,ele,score);
|
||||
/* Note that we did not removed the original element from
|
||||
* the hash table representing the sorted set, so we just
|
||||
* update the score. */
|
||||
|
||||
+6
-3
@@ -346,6 +346,7 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
int negative = 0;
|
||||
unsigned long long v;
|
||||
|
||||
/* A zero length string is not a valid number. */
|
||||
if (plen == slen)
|
||||
return 0;
|
||||
|
||||
@@ -355,6 +356,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Handle negative numbers: just set a flag and continue like if it
|
||||
* was a positive number. Later convert into negative. */
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
@@ -368,13 +371,11 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
if (p[0] >= '1' && p[0] <= '9') {
|
||||
v = p[0]-'0';
|
||||
p++; plen++;
|
||||
} else if (p[0] == '0' && slen == 1) {
|
||||
*value = 0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse all the other digits, checking for overflow at every step. */
|
||||
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||
return 0;
|
||||
@@ -391,6 +392,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
if (plen < slen)
|
||||
return 0;
|
||||
|
||||
/* Convert to negative if needed, and do the final overflow check when
|
||||
* converting from unsigned long long to long long. */
|
||||
if (negative) {
|
||||
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||
return 0;
|
||||
|
||||
+20
-1
@@ -164,7 +164,7 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
if (!newptr) zmalloc_oom_handler(size);
|
||||
|
||||
*((size_t*)newptr) = size;
|
||||
update_zmalloc_stat_free(oldsize);
|
||||
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
|
||||
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
|
||||
return (char*)newptr+PREFIX_SIZE;
|
||||
#endif
|
||||
@@ -182,6 +182,9 @@ size_t zmalloc_size(void *ptr) {
|
||||
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
|
||||
return size+PREFIX_SIZE;
|
||||
}
|
||||
size_t zmalloc_usable(void *ptr) {
|
||||
return zmalloc_size(ptr)-PREFIX_SIZE;
|
||||
}
|
||||
#endif
|
||||
|
||||
void zfree(void *ptr) {
|
||||
@@ -435,4 +438,20 @@ size_t zmalloc_get_memory_size(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
#define UNUSED(x) ((void)(x))
|
||||
int zmalloc_test(int argc, char **argv) {
|
||||
void *ptr;
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
printf("Initial used memory: %zu\n", zmalloc_used_memory());
|
||||
ptr = zmalloc(123);
|
||||
printf("Allocated 123 bytes; used: %zu\n", zmalloc_used_memory());
|
||||
ptr = zrealloc(ptr, 456);
|
||||
printf("Reallocated to 456 bytes; used: %zu\n", zmalloc_used_memory());
|
||||
zfree(ptr);
|
||||
printf("Freed pointer; used: %zu\n", zmalloc_used_memory());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -98,6 +98,13 @@ void *zmalloc_no_tcache(size_t size);
|
||||
|
||||
#ifndef HAVE_MALLOC_SIZE
|
||||
size_t zmalloc_size(void *ptr);
|
||||
size_t zmalloc_usable(void *ptr);
|
||||
#else
|
||||
#define zmalloc_usable(p) zmalloc_size(p)
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int zmalloc_test(int argc, char **argv);
|
||||
#endif
|
||||
|
||||
#endif /* __ZMALLOC_H */
|
||||
|
||||
@@ -49,7 +49,7 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
|
||||
@@ -29,7 +29,7 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(1) dbsize] == 1 && [$R(2) dbsize] == 1
|
||||
} else {
|
||||
fail "Slaves not replicating from master"
|
||||
fail "Replicas not replicating from master"
|
||||
}
|
||||
$R(0) config set repl-backlog-size 10mb
|
||||
$R(1) config set repl-backlog-size 10mb
|
||||
@@ -41,12 +41,12 @@ start_server {} {
|
||||
set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
|
||||
if {$elapsed > $duration*1000} break
|
||||
if {rand() < .05} {
|
||||
test "PSYNC2 #3899 regression: kill first slave" {
|
||||
test "PSYNC2 #3899 regression: kill first replica" {
|
||||
$R(1) client kill type master
|
||||
}
|
||||
}
|
||||
if {rand() < .05} {
|
||||
test "PSYNC2 #3899 regression: kill chained slave" {
|
||||
test "PSYNC2 #3899 regression: kill chained replica" {
|
||||
$R(2) client kill type master
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,8 @@ start_server {} {
|
||||
|
||||
set cycle 1
|
||||
while {([clock seconds]-$start_time) < $duration} {
|
||||
test "PSYNC2: --- CYCLE $cycle ---" {
|
||||
incr cycle
|
||||
}
|
||||
test "PSYNC2: --- CYCLE $cycle ---" {}
|
||||
incr cycle
|
||||
|
||||
# Create a random replication layout.
|
||||
# Start with switching master (this simulates a failover).
|
||||
@@ -96,7 +95,7 @@ start_server {} {
|
||||
if {$disconnect} {
|
||||
$R($slave_id) client kill type master
|
||||
if {$debug_msg} {
|
||||
puts "+++ Breaking link for slave #$slave_id"
|
||||
puts "+++ Breaking link for replica #$slave_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +138,11 @@ start_server {} {
|
||||
}
|
||||
assert {$sum == 4}
|
||||
}
|
||||
|
||||
# Limit anyway the maximum number of cycles. This is useful when the
|
||||
# test is skipped via --only option of the test suite. In that case
|
||||
# we don't want to see many seconds of this test being just skipped.
|
||||
if {$cycle > 50} break
|
||||
}
|
||||
|
||||
test "PSYNC2: Bring the master back again for next test" {
|
||||
@@ -154,7 +158,7 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,13 +173,13 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
set new_sync_count [status $R($master_id) sync_full]
|
||||
assert {$sync_count == $new_sync_count}
|
||||
}
|
||||
|
||||
test "PSYNC2: Slave RDB restart with EVALSHA in backlog issue #4483" {
|
||||
test "PSYNC2: Replica RDB restart with EVALSHA in backlog issue #4483" {
|
||||
# Pick a random slave
|
||||
set slave_id [expr {($master_id+1)%5}]
|
||||
set sync_count [status $R($master_id) sync_full]
|
||||
@@ -190,7 +194,7 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
|
||||
# Prevent the slave from receiving master updates, and at
|
||||
@@ -224,7 +228,7 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
set new_sync_count [status $R($master_id) sync_full]
|
||||
assert {$sync_count == $new_sync_count}
|
||||
@@ -234,7 +238,7 @@ start_server {} {
|
||||
wait_for_condition 50 1000 {
|
||||
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
|
||||
} else {
|
||||
fail "Debug digest mismatch between master and slave in post-restart handshake"
|
||||
fail "Debug digest mismatch between master and replica in post-restart handshake"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
fail "Write did not reached replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
fail "Write did not reached replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {aaabbb}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
fail "Write did not reached replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
|
||||
@@ -25,7 +25,7 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
@@ -98,7 +98,7 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
|
||||
@@ -60,7 +60,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
if ($reconnect) {
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
# catch {puts "MASTER [$master dbsize] keys, REPLICA [$slave dbsize] keys"}
|
||||
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
@@ -96,7 +96,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
|
||||
@@ -32,7 +32,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 1000 {
|
||||
[string match *handshake* [$slave role]]
|
||||
} else {
|
||||
fail "Slave does not enter handshake state"
|
||||
fail "Replica does not enter handshake state"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 1000 {
|
||||
[log_file_matches $slave_log "*Timeout connecting to the MASTER*"]
|
||||
} else {
|
||||
fail "Slave is not able to detect timeout"
|
||||
fail "Replica is not able to detect timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ start_server {tags {"repl"}} {
|
||||
[lindex [$A role] 0] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [$A info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
fail "Can't turn the instance into a replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[$A debug digest] eq [$B debug digest]
|
||||
} else {
|
||||
fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
fail "Master and replica have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ start_server {tags {"repl"}} {
|
||||
[lindex [$B role] 0] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [$B info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
fail "Can't turn the instance into a replica"
|
||||
}
|
||||
|
||||
# Push elements into the "foo" list of the new slave.
|
||||
# Push elements into the "foo" list of the new replica.
|
||||
# If the client is still attached to the instance, we'll get
|
||||
# a desync between the two instances.
|
||||
$A rpush foo a b c
|
||||
@@ -116,7 +116,7 @@ start_server {tags {"repl"}} {
|
||||
[$A lrange foo 0 -1] eq {a b c} &&
|
||||
[$B lrange foo 0 -1] eq {a b c}
|
||||
} else {
|
||||
fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
fail "Master and replica have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ start_server {tags {"repl"}} {
|
||||
s master_link_status
|
||||
} {down}
|
||||
|
||||
test {The role should immediately be changed to "slave"} {
|
||||
test {The role should immediately be changed to "replica"} {
|
||||
s role
|
||||
} {slave}
|
||||
|
||||
@@ -154,7 +154,7 @@ start_server {tags {"repl"}} {
|
||||
wait_for_condition 500 100 {
|
||||
[r 0 get mykey] eq {bar}
|
||||
} else {
|
||||
fail "SET on master did not propagated on slave"
|
||||
fail "SET on master did not propagated on replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ foreach dl {no yes} {
|
||||
lappend slaves [srv 0 client]
|
||||
start_server {} {
|
||||
lappend slaves [srv 0 client]
|
||||
test "Connect multiple slaves at the same time (issue #141), diskless=$dl" {
|
||||
test "Connect multiple replicas at the same time (issue #141), diskless=$dl" {
|
||||
# Send SLAVEOF commands to slaves
|
||||
[lindex $slaves 0] slaveof $master_host $master_port
|
||||
[lindex $slaves 1] slaveof $master_host $master_port
|
||||
@@ -220,7 +220,7 @@ foreach dl {no yes} {
|
||||
}
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "assertion:Slaves not correctly synchronized"
|
||||
error "assertion:Replicas not correctly synchronized"
|
||||
}
|
||||
|
||||
# Wait that slaves acknowledge they are online so
|
||||
@@ -231,7 +231,7 @@ foreach dl {no yes} {
|
||||
[lindex [[lindex $slaves 1] role] 3] eq {connected} &&
|
||||
[lindex [[lindex $slaves 2] role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Slaves still not connected after some time"
|
||||
fail "Replicas still not connected after some time"
|
||||
}
|
||||
|
||||
# Stop the write load
|
||||
@@ -248,7 +248,7 @@ foreach dl {no yes} {
|
||||
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
|
||||
[$master dbsize] == [[lindex $slaves 2] dbsize]
|
||||
} else {
|
||||
fail "Different number of keys between masted and slave after too long time."
|
||||
fail "Different number of keys between masted and replica after too long time."
|
||||
}
|
||||
|
||||
# Check digests
|
||||
@@ -266,3 +266,47 @@ foreach dl {no yes} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set load_handle0 [start_write_load $master_host $master_port 3]
|
||||
start_server {} {
|
||||
test "Master stream is correctly processed while the replica has a script in -BUSY state" {
|
||||
set slave [srv 0 client]
|
||||
puts [srv 0 port]
|
||||
$slave config set lua-time-limit 500
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
# Wait for the slave to be online
|
||||
wait_for_condition 500 100 {
|
||||
[lindex [$slave role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Replica still not connected after some time"
|
||||
}
|
||||
|
||||
# Wait some time to make sure the master is sending data
|
||||
# to the slave.
|
||||
after 5000
|
||||
|
||||
# Stop the ability of the slave to process data by sendig
|
||||
# a script that will put it in BUSY state.
|
||||
$slave eval {for i=1,3000000000 do end} 0
|
||||
|
||||
# Wait some time again so that more master stream will
|
||||
# be processed.
|
||||
after 2000
|
||||
|
||||
# Stop the write load
|
||||
stop_write_load $load_handle0
|
||||
|
||||
# number of keys
|
||||
wait_for_condition 500 100 {
|
||||
[$master debug digest] eq [$slave debug digest]
|
||||
} else {
|
||||
fail "Different datasets between replica and master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ test "Basic failover works if the master is down" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
@@ -108,7 +108,7 @@ test "Failover works if we configure for absolute agreement" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
|
||||
@@ -16,7 +16,7 @@ test "We can failover with Sentinel 1 crashed" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Sentinel $id did not received failover info"
|
||||
fail "Sentinel $id did not receive failover info"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ test "After Sentinel 1 is restarted, its config gets updated" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Restarted Sentinel did not received failover info"
|
||||
fail "Restarted Sentinel did not receive failover info"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ proc 02_crash_and_failover {} {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
|
||||
@@ -12,7 +12,7 @@ test "Manual failover works" {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
fail "At least one Sentinel did not receive failover info"
|
||||
}
|
||||
}
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
|
||||
@@ -4,7 +4,7 @@ set ::valgrind_errors {}
|
||||
|
||||
proc start_server_error {config_file error} {
|
||||
set err {}
|
||||
append err "Cant' start the Redis server\n"
|
||||
append err "Can't start the Redis server\n"
|
||||
append err "CONFIGURATION:"
|
||||
append err [exec cat $config_file]
|
||||
append err "\nERROR:"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
set ::num_tests 0
|
||||
set ::num_passed 0
|
||||
set ::num_failed 0
|
||||
set ::num_skipped 0
|
||||
set ::num_aborted 0
|
||||
set ::tests_failed {}
|
||||
|
||||
proc fail {msg} {
|
||||
@@ -68,10 +70,26 @@ proc test {name code {okpattern undefined}} {
|
||||
# abort if tagged with a tag to deny
|
||||
foreach tag $::denytags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
incr ::num_aborted
|
||||
send_data_packet $::test_server_fd ignore $name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# abort if test name in skiptests
|
||||
if {[lsearch $::skiptests $name] >= 0} {
|
||||
incr ::num_skipped
|
||||
send_data_packet $::test_server_fd skip $name
|
||||
return
|
||||
}
|
||||
|
||||
# abort if test name in skiptests
|
||||
if {[llength $::only_tests] > 0 && [lsearch $::only_tests $name] < 0} {
|
||||
incr ::num_skipped
|
||||
send_data_packet $::test_server_fd skip $name
|
||||
return
|
||||
}
|
||||
|
||||
# check if tagged with at least 1 tag to allow when there *is* a list
|
||||
# of tags to allow, because default policy is to run everything
|
||||
if {[llength $::allowtags] > 0} {
|
||||
@@ -82,6 +100,8 @@ proc test {name code {okpattern undefined}} {
|
||||
}
|
||||
}
|
||||
if {$matched < 1} {
|
||||
incr ::num_aborted
|
||||
send_data_packet $::test_server_fd ignore $name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,3 +375,19 @@ proc start_write_load {host port seconds} {
|
||||
proc stop_write_load {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
proc K { x y } { set x }
|
||||
|
||||
# Shuffle a list. From Tcl wiki. Originally from Steve Cohen that improved
|
||||
# other versions. Code should be under public domain.
|
||||
proc lshuffle {list} {
|
||||
set n [llength $list]
|
||||
while {$n>0} {
|
||||
set j [expr {int(rand()*$n)}]
|
||||
lappend slist [lindex $list $j]
|
||||
incr n -1
|
||||
set temp [lindex $list $n]
|
||||
set list [lreplace [K $list [set list {}]] $j $j $temp]
|
||||
}
|
||||
return $slist
|
||||
}
|
||||
|
||||
+79
-4
@@ -74,7 +74,11 @@ set ::stack_logging 0
|
||||
set ::verbose 0
|
||||
set ::quiet 0
|
||||
set ::denytags {}
|
||||
set ::skiptests {}
|
||||
set ::allowtags {}
|
||||
set ::only_tests {}
|
||||
set ::single_tests {}
|
||||
set ::skip_till ""
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
@@ -85,6 +89,8 @@ set ::last_progress [clock seconds]
|
||||
set ::active_servers {} ; # Pids of active Redis instances.
|
||||
set ::dont_clean 0
|
||||
set ::wait_server 0
|
||||
set ::stop_on_failure 0
|
||||
set ::loop 0
|
||||
|
||||
# Set to 1 when we are running in client mode. The Redis test uses a
|
||||
# server-client model to run tests simultaneously. The server instance
|
||||
@@ -255,6 +261,8 @@ proc accept_test_clients {fd addr port} {
|
||||
# testing: just used to signal that a given test started.
|
||||
# ok: a test was executed with success.
|
||||
# err: a test was executed with an error.
|
||||
# skip: a test was skipped by skipfile or individual test options.
|
||||
# ignore: a test was skipped by a group tag.
|
||||
# exception: there was a runtime exception while executing the test.
|
||||
# done: all the specified test file was processed, this test client is
|
||||
# ready to accept a new task.
|
||||
@@ -283,11 +291,24 @@ proc read_from_test_client fd {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
}
|
||||
set ::active_clients_task($fd) "(OK) $data"
|
||||
} elseif {$status eq {skip}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr yellow $status]\]: $data"
|
||||
}
|
||||
} elseif {$status eq {ignore}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr cyan $status]\]: $data"
|
||||
}
|
||||
} elseif {$status eq {err}} {
|
||||
set err "\[[colorstr red $status]\]: $data"
|
||||
puts $err
|
||||
lappend ::failed_tests $err
|
||||
set ::active_clients_task($fd) "(ERR) $data"
|
||||
if {$::stop_on_failure} {
|
||||
puts -nonewline "(Test stopped, press enter to continue)"
|
||||
flush stdout
|
||||
gets stdin
|
||||
}
|
||||
} elseif {$status eq {exception}} {
|
||||
puts "\[[colorstr red $status]\]: $data"
|
||||
kill_clients
|
||||
@@ -350,6 +371,9 @@ proc signal_idle_client fd {
|
||||
send_data_packet $fd run [lindex $::all_tests $::next_test]
|
||||
lappend ::active_clients $fd
|
||||
incr ::next_test
|
||||
if {$::loop && $::next_test == [llength $::all_tests]} {
|
||||
set ::next_test 0
|
||||
}
|
||||
} else {
|
||||
lappend ::idle_clients $fd
|
||||
if {[llength $::active_clients] == 0} {
|
||||
@@ -412,13 +436,19 @@ proc print_help_screen {} {
|
||||
"--stack-logging Enable OSX leaks/malloc stack logging."
|
||||
"--accurate Run slow randomized tests for more iterations."
|
||||
"--quiet Don't show individual tests."
|
||||
"--single <unit> Just execute the specified unit (see next option)."
|
||||
"--single <unit> Just execute the specified unit (see next option). this option can be repeated."
|
||||
"--list-tests List all the available test units."
|
||||
"--only <test> Just execute the specified test by test name. this option can be repeated."
|
||||
"--skip-till <unit> Skip all units until (and including) the specified one."
|
||||
"--clients <num> Number of test clients (default 16)."
|
||||
"--timeout <sec> Test timeout in seconds (default 10 min)."
|
||||
"--force-failure Force the execution of a test that always fails."
|
||||
"--dont-clean don't delete redis log files after the run"
|
||||
"--wait-server wait after server is started (so that you can attach a debugger)"
|
||||
"--config <k> <v> Extra config file argument."
|
||||
"--skipfile <file> Name of a file containing test names that should be skipped (one per line)."
|
||||
"--dont-clean Don't delete redis log files after the run."
|
||||
"--stop Blocks once the first test fails."
|
||||
"--loop Execute the specified set of tests forever."
|
||||
"--wait-server Wait after server is started (so that you can attach a debugger)."
|
||||
"--help Print this help screen."
|
||||
} "\n"]
|
||||
}
|
||||
@@ -436,6 +466,17 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
incr j
|
||||
} elseif {$opt eq {--config}} {
|
||||
set arg2 [lindex $argv [expr $j+2]]
|
||||
lappend ::global_overrides $arg
|
||||
lappend ::global_overrides $arg2
|
||||
incr j 2
|
||||
} elseif {$opt eq {--skipfile}} {
|
||||
incr j
|
||||
set fp [open $arg r]
|
||||
set file_data [read $fp]
|
||||
close $fp
|
||||
set ::skiptests [split $file_data "\n"]
|
||||
} elseif {$opt eq {--valgrind}} {
|
||||
set ::valgrind 1
|
||||
} elseif {$opt eq {--stack-logging}} {
|
||||
@@ -456,7 +497,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--force-failure}} {
|
||||
set ::force_failure 1
|
||||
} elseif {$opt eq {--single}} {
|
||||
set ::all_tests $arg
|
||||
lappend ::single_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--only}} {
|
||||
lappend ::only_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--skiptill}} {
|
||||
set ::skip_till $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--list-tests}} {
|
||||
foreach t $::all_tests {
|
||||
@@ -476,6 +523,10 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
set ::dont_clean 1
|
||||
} elseif {$opt eq {--wait-server}} {
|
||||
set ::wait_server 1
|
||||
} elseif {$opt eq {--stop}} {
|
||||
set ::stop_on_failure 1
|
||||
} elseif {$opt eq {--loop}} {
|
||||
set ::loop 1
|
||||
} elseif {$opt eq {--timeout}} {
|
||||
set ::timeout $arg
|
||||
incr j
|
||||
@@ -488,6 +539,30 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
|
||||
# If --skil-till option was given, we populate the list of single tests
|
||||
# to run with everything *after* the specified unit.
|
||||
if {$::skip_till != ""} {
|
||||
set skipping 1
|
||||
foreach t $::all_tests {
|
||||
if {$skipping == 0} {
|
||||
lappend ::single_tests $t
|
||||
}
|
||||
if {$t == $::skip_till} {
|
||||
set skipping 0
|
||||
}
|
||||
}
|
||||
if {$skipping} {
|
||||
puts "test $::skip_till not found"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Override the list of tests with the specific tests we want to run
|
||||
# in case there was some filter, that is --single or --skip-till options.
|
||||
if {[llength $::single_tests] > 0} {
|
||||
set ::all_tests $::single_tests
|
||||
}
|
||||
|
||||
proc attach_to_replication_stream {} {
|
||||
set s [socket [srv 0 "host"] [srv 0 "port"]]
|
||||
fconfigure $s -translation binary
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ start_server {tags {"dump"}} {
|
||||
set now [clock milliseconds]
|
||||
r restore foo [expr $now+3000] $encoded absttl
|
||||
set ttl [r pttl foo]
|
||||
assert {$ttl >= 2998 && $ttl <= 3000}
|
||||
assert {$ttl >= 2990 && $ttl <= 3000}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
@@ -44,7 +44,7 @@ start_server {tags {"dump"}} {
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r restore foo 0 $encoded idletime 1000
|
||||
set idle [r object idletime foo]
|
||||
assert {$idle >= 1000 && $idle <= 1002}
|
||||
assert {$idle >= 1000 && $idle <= 1010}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
proc cmdstat {cmd} {
|
||||
if {[regexp "\r\ncmdstat_$cmd:(.*?)\r\n" [r info commandstats] _ value]} {
|
||||
set _ $value
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"introspection"}} {
|
||||
test {TTL and TYPYE do not alter the last access time of a key} {
|
||||
r set foo bar
|
||||
@@ -20,4 +26,55 @@ start_server {tags {"introspection"}} {
|
||||
r set key2 2
|
||||
r touch key0 key1 key2 key3
|
||||
} 2
|
||||
|
||||
test {command stats for GEOADD} {
|
||||
r config resetstat
|
||||
r GEOADD foo 0 0 bar
|
||||
assert_match {*calls=1,*} [cmdstat geoadd]
|
||||
assert_match {} [cmdstat zadd]
|
||||
}
|
||||
|
||||
test {command stats for EXPIRE} {
|
||||
r config resetstat
|
||||
r SET foo bar
|
||||
r EXPIRE foo 0
|
||||
assert_match {*calls=1,*} [cmdstat expire]
|
||||
assert_match {} [cmdstat del]
|
||||
}
|
||||
|
||||
test {command stats for BRPOP} {
|
||||
r config resetstat
|
||||
r LPUSH list foo
|
||||
r BRPOP list 0
|
||||
assert_match {*calls=1,*} [cmdstat brpop]
|
||||
assert_match {} [cmdstat rpop]
|
||||
}
|
||||
|
||||
test {command stats for MULTI} {
|
||||
r config resetstat
|
||||
r MULTI
|
||||
r set foo bar
|
||||
r GEOADD foo2 0 0 bar
|
||||
r EXPIRE foo2 0
|
||||
r EXEC
|
||||
assert_match {*calls=1,*} [cmdstat multi]
|
||||
assert_match {*calls=1,*} [cmdstat exec]
|
||||
assert_match {*calls=1,*} [cmdstat set]
|
||||
assert_match {*calls=1,*} [cmdstat expire]
|
||||
assert_match {*calls=1,*} [cmdstat geoadd]
|
||||
}
|
||||
|
||||
test {command stats for scripts} {
|
||||
r config resetstat
|
||||
r set mykey myval
|
||||
r eval {
|
||||
redis.call('set', KEYS[1], 0)
|
||||
redis.call('expire', KEYS[1], 0)
|
||||
redis.call('geoadd', KEYS[1], 0, 0, "bar")
|
||||
} 1 mykey
|
||||
assert_match {*calls=1,*} [cmdstat eval]
|
||||
assert_match {*calls=2,*} [cmdstat set]
|
||||
assert_match {*calls=1,*} [cmdstat expire]
|
||||
assert_match {*calls=1,*} [cmdstat geoadd]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
start_server {tags {"introspection"}} {
|
||||
test {CLIENT LIST} {
|
||||
r client list
|
||||
} {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
|
||||
} {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
|
||||
|
||||
test {MONITOR can log executed commands} {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
@@ -142,3 +142,101 @@ start_server {tags {"maxmemory"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline} {
|
||||
start_server {tags {"maxmemory"}} {
|
||||
start_server {} {
|
||||
set slave_pid [s process_id]
|
||||
test "$test_name" {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
set slave_port [srv 0 port]
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
|
||||
# add 100 keys of 100k (10MB total)
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
$master setrange "key:$j" 100000 asdf
|
||||
}
|
||||
|
||||
# make sure master doesn't disconnect slave because of timeout
|
||||
$master config set repl-timeout 300 ;# 5 minutes
|
||||
$master config set maxmemory-policy allkeys-random
|
||||
$master config set client-output-buffer-limit "replica 100000000 100000000 300"
|
||||
$master config set repl-backlog-size [expr {10*1024}]
|
||||
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
|
||||
# measure used memory after the slave connected and set maxmemory
|
||||
set orig_used [s -1 used_memory]
|
||||
set orig_client_buf [s -1 mem_clients_normal]
|
||||
set orig_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set orig_used_no_repl [expr {$orig_used - $orig_mem_not_counted_for_evict}]
|
||||
set limit [expr {$orig_used - $orig_mem_not_counted_for_evict + 20*1024}]
|
||||
|
||||
if {$limit_memory==1} {
|
||||
$master config set maxmemory $limit
|
||||
}
|
||||
|
||||
# put the slave to sleep
|
||||
set rd_slave [redis_deferring_client]
|
||||
exec kill -SIGSTOP $slave_pid
|
||||
|
||||
# send some 10mb worth of commands that don't increase the memory usage
|
||||
if {$pipeline == 1} {
|
||||
set rd_master [redis_deferring_client -1]
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
$rd_master setrange key:0 0 [string repeat A $payload_len]
|
||||
}
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
#$rd_master read
|
||||
}
|
||||
} else {
|
||||
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||
$master setrange key:0 0 [string repeat A $payload_len]
|
||||
}
|
||||
}
|
||||
|
||||
set new_used [s -1 used_memory]
|
||||
set slave_buf [s -1 mem_clients_slaves]
|
||||
set client_buf [s -1 mem_clients_normal]
|
||||
set mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set used_no_repl [expr {$new_used - $mem_not_counted_for_evict}]
|
||||
set delta [expr {($used_no_repl - $client_buf) - ($orig_used_no_repl - $orig_client_buf)}]
|
||||
|
||||
assert {[$master dbsize] == 100}
|
||||
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
|
||||
assert {$delta < 50*1024 && $delta > -50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
|
||||
$master client kill type slave
|
||||
set killed_used [s -1 used_memory]
|
||||
set killed_slave_buf [s -1 mem_clients_slaves]
|
||||
set killed_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
|
||||
set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict}]
|
||||
set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}]
|
||||
assert {$killed_slave_buf == 0}
|
||||
assert {$delta_no_repl > -50*1024 && $delta_no_repl < 50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
|
||||
}
|
||||
# unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server
|
||||
exec kill -SIGCONT $slave_pid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# test that slave buffer are counted correctly
|
||||
# we wanna use many small commands, and we don't wanna wait long
|
||||
# so we need to use a pipeline (redis_deferring_client)
|
||||
# that may cause query buffer to fill and induce eviction, so we disable it
|
||||
test_slave_buffers {slave buffer are counted correctly} 1000000 10 0 1
|
||||
|
||||
# test that slave buffer don't induce eviction
|
||||
# test again with fewer (and bigger) commands without pipeline, but with eviction
|
||||
test_slave_buffers "replica buffer don't induce eviction" 100000 100 1 0
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ start_server {tags {"defrag"}} {
|
||||
test "Active defrag" {
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 25
|
||||
r config set active-defrag-cycle-min 65
|
||||
r config set active-defrag-cycle-max 75
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 100mb
|
||||
@@ -66,9 +66,10 @@ start_server {tags {"defrag"}} {
|
||||
}
|
||||
|
||||
# Wait for the active defrag to stop working.
|
||||
wait_for_condition 100 100 {
|
||||
wait_for_condition 150 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
@@ -175,6 +176,7 @@ start_server {tags {"defrag"}} {
|
||||
wait_for_condition 500 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
@@ -199,7 +201,7 @@ start_server {tags {"defrag"}} {
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 75ms
|
||||
assert {$max_latency <= 80}
|
||||
assert {$max_latency <= 120}
|
||||
}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
|
||||
@@ -29,7 +29,7 @@ start_server {} {
|
||||
set m_usedmemory [info_memory $master used_memory]
|
||||
set s_usedmemory [info_memory $slave used_memory]
|
||||
if { $s_usedmemory > $m_usedmemory + 10*1024*1024 } {
|
||||
fail "the used_memory of slave is too larger than master.Master:$m_usedmemory Slave:$s_usedmemory"
|
||||
fail "the used_memory of replica is much larger than master. Master:$m_usedmemory Replica:$s_usedmemory"
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
+17
-10
@@ -148,9 +148,11 @@ start_server {tags {"scripting"}} {
|
||||
|
||||
test {EVAL - Scripts can't run certain commands} {
|
||||
set e {}
|
||||
r debug lua-always-replicate-commands 0
|
||||
catch {
|
||||
r eval "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
|
||||
} e
|
||||
r debug lua-always-replicate-commands 1
|
||||
set e
|
||||
} {*not allowed after*}
|
||||
|
||||
@@ -299,9 +301,12 @@ start_server {tags {"scripting"}} {
|
||||
} {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
|
||||
|
||||
test "In the context of Lua the output of random commands gets ordered" {
|
||||
r debug lua-always-replicate-commands 0
|
||||
r del myset
|
||||
r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
|
||||
r eval {return redis.call('smembers',KEYS[1])} 1 myset
|
||||
set res [r eval {return redis.call('smembers',KEYS[1])} 1 myset]
|
||||
r debug lua-always-replicate-commands 1
|
||||
set res
|
||||
} {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
|
||||
|
||||
test "SORT is normally not alpha re-ordered for the scripting engine" {
|
||||
@@ -537,7 +542,7 @@ foreach cmdrepl {0 1} {
|
||||
r debug lua-always-replicate-commands 1
|
||||
}
|
||||
|
||||
test "Before the slave connects we issue two EVAL commands $rt" {
|
||||
test "Before the replica connects we issue two EVAL commands $rt" {
|
||||
# One with an error, but still executing a command.
|
||||
# SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876
|
||||
catch {
|
||||
@@ -548,13 +553,13 @@ foreach cmdrepl {0 1} {
|
||||
r eval {return redis.call('incr',KEYS[1])} 1 x
|
||||
} {2}
|
||||
|
||||
test "Connect a slave to the master instance $rt" {
|
||||
test "Connect a replica to the master instance $rt" {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 role] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [r -1 info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
fail "Can't turn the instance into a replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +592,7 @@ foreach cmdrepl {0 1} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 lrange a 0 -1] eq [r lrange a 0 -1]
|
||||
} else {
|
||||
fail "Expected list 'a' in slave and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
|
||||
fail "Expected list 'a' in replica and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
|
||||
}
|
||||
set res
|
||||
} {a 1}
|
||||
@@ -622,7 +627,7 @@ foreach cmdrepl {0 1} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 debug digest] eq [r debug digest]
|
||||
} else {
|
||||
fail "Master-Slave desync after Lua script using SELECT."
|
||||
fail "Master-Replica desync after Lua script using SELECT."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -631,13 +636,13 @@ foreach cmdrepl {0 1} {
|
||||
|
||||
start_server {tags {"scripting repl"}} {
|
||||
start_server {overrides {appendonly yes aof-use-rdb-preamble no}} {
|
||||
test "Connect a slave to the master instance" {
|
||||
test "Connect a replica to the master instance" {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 role] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [r -1 info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
fail "Can't turn the instance into a replica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,11 +660,13 @@ start_server {tags {"scripting repl"}} {
|
||||
} {1}
|
||||
|
||||
test "Redis.set_repl() must be issued after replicate_commands()" {
|
||||
r debug lua-always-replicate-commands 0
|
||||
catch {
|
||||
r eval {
|
||||
redis.set_repl(redis.REPL_ALL);
|
||||
} 0
|
||||
} e
|
||||
r debug lua-always-replicate-commands 1
|
||||
set e
|
||||
} {*only after turning on*}
|
||||
|
||||
@@ -689,7 +696,7 @@ start_server {tags {"scripting repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 mget a b c d] eq {1 {} {} 4}
|
||||
} else {
|
||||
fail "Only a and c should be replicated to slave"
|
||||
fail "Only a and c should be replicated to replica"
|
||||
}
|
||||
|
||||
# Master should have everything right now
|
||||
@@ -728,7 +735,7 @@ start_server {tags {"scripting repl"}} {
|
||||
wait_for_condition 50 100 {
|
||||
[r get time] eq [r -1 get time]
|
||||
} else {
|
||||
fail "Time key does not match between master and slave"
|
||||
fail "Time key does not match between master and replica"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +78,14 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
|
||||
set e [lindex [r slowlog get] 0]
|
||||
assert_equal {lastentry_client} [lindex $e 5]
|
||||
}
|
||||
|
||||
test {SLOWLOG - can be disabled} {
|
||||
r config set slowlog-log-slower-than 1
|
||||
r slowlog reset
|
||||
assert_equal [r slowlog len] 1
|
||||
r config set slowlog-log-slower-than -1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +81,19 @@ start_server {
|
||||
# just ID2.
|
||||
assert {[r XACK mystream mygroup $id1 $id2] eq 1}
|
||||
}
|
||||
|
||||
test {PEL NACK reassignment after XGROUP SETID event} {
|
||||
r del events
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xadd events * f1 v1
|
||||
r xgroup create events g1 $
|
||||
r xadd events * f1 v1
|
||||
set c [llength [lindex [r xreadgroup group g1 c1 streams events >] 0 1]]
|
||||
assert {$c == 1}
|
||||
r xgroup setid events g1 -
|
||||
set c [llength [lindex [r xreadgroup group g1 c2 streams events >] 0 1]]
|
||||
assert {$c == 5}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,53 @@ start_server {
|
||||
assert {[lindex $res 0 1 1 1] eq {field two}}
|
||||
}
|
||||
|
||||
test {XDEL basic test} {
|
||||
r del somestream
|
||||
r xadd somestream * foo value0
|
||||
set id [r xadd somestream * foo value1]
|
||||
r xadd somestream * foo value2
|
||||
r xdel somestream $id
|
||||
assert {[r xlen somestream] == 2}
|
||||
set result [r xrange somestream - +]
|
||||
assert {[lindex $result 0 1 1] eq {value0}}
|
||||
assert {[lindex $result 1 1 1] eq {value2}}
|
||||
}
|
||||
|
||||
# Here the idea is to check the consistency of the stream data structure
|
||||
# as we remove all the elements down to zero elements.
|
||||
test {XDEL fuzz test} {
|
||||
r del somestream
|
||||
set ids {}
|
||||
set x 0; # Length of the stream
|
||||
while 1 {
|
||||
lappend ids [r xadd somestream * item $x]
|
||||
incr x
|
||||
# Add enough elements to have a few radix tree nodes inside the stream.
|
||||
if {[dict get [r xinfo stream somestream] radix-tree-keys] > 20} break
|
||||
}
|
||||
|
||||
# Now remove all the elements till we reach an empty stream
|
||||
# and after every deletion, check that the stream is sane enough
|
||||
# to report the right number of elements with XRANGE: this will also
|
||||
# force accessing the whole data structure to check sanity.
|
||||
assert {[r xlen somestream] == $x}
|
||||
|
||||
# We want to remove elements in random order to really test the
|
||||
# implementation in a better way.
|
||||
set ids [lshuffle $ids]
|
||||
foreach id $ids {
|
||||
assert {[r xdel somestream $id] == 1}
|
||||
incr x -1
|
||||
assert {[r xlen somestream] == $x}
|
||||
# The test would be too slow calling XRANGE for every iteration.
|
||||
# Do it every 100 removal.
|
||||
if {$x % 100 == 0} {
|
||||
set res [r xrange somestream - +]
|
||||
assert {[llength $res] == $x}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {XRANGE fuzzing} {
|
||||
set low_id [lindex $items 0 0]
|
||||
set high_id [lindex $items end 0]
|
||||
@@ -270,3 +317,49 @@ start_server {
|
||||
assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||
test {XADD with MAXLEN > xlen can propagate correctly} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
r XADD mystream * xitem v
|
||||
}
|
||||
r XADD mystream MAXLEN 200 * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == $j}
|
||||
r debug loadaof
|
||||
r XADD mystream * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == $j}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||
test {XADD with ~ MAXLEN can propagate correctly} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
r XADD mystream * xitem v
|
||||
}
|
||||
r XADD mystream MAXLEN ~ $j * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == $j}
|
||||
r config set stream-node-max-entries 1
|
||||
r debug loadaof
|
||||
r XADD mystream * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == $j}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
|
||||
test {XTRIM with ~ MAXLEN can propagate correctly} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
r XADD mystream * xitem v
|
||||
}
|
||||
r XTRIM mystream MAXLEN ~ 85
|
||||
assert {[r xlen mystream] == 89}
|
||||
r config set stream-node-max-entries 1
|
||||
r debug loadaof
|
||||
r XADD mystream * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == 90}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,4 +1185,30 @@ start_server {tags {"zset"}} {
|
||||
stressers ziplist
|
||||
stressers skiplist
|
||||
}
|
||||
|
||||
test {ZSET skiplist order consistency when elements are moved} {
|
||||
set original_max [lindex [r config get zset-max-ziplist-entries] 1]
|
||||
r config set zset-max-ziplist-entries 0
|
||||
for {set times 0} {$times < 10} {incr times} {
|
||||
r del zset
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
r zadd zset [randomInt 50] ele-[randomInt 10]
|
||||
}
|
||||
|
||||
# Make sure that element ordering is correct
|
||||
set prev_element {}
|
||||
set prev_score -1
|
||||
foreach {element score} [r zrange zset 0 -1 WITHSCORES] {
|
||||
# Assert that elements are in increasing ordering
|
||||
assert {
|
||||
$prev_score < $score ||
|
||||
($prev_score == $score &&
|
||||
[string compare $prev_element $element] == -1)
|
||||
}
|
||||
set prev_element $element
|
||||
set prev_score $score
|
||||
}
|
||||
}
|
||||
r config set zset-max-ziplist-entries $original_max
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user