Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a87de130f | ||
|
|
d3eb0028e9 | ||
|
|
9403b3d7a3 | ||
|
|
6bb8cdaebe | ||
|
|
27f6e9bb9b | ||
|
|
850b64c116 | ||
|
|
ab37289fa6 | ||
|
|
009a929269 | ||
|
|
129f2d2746 | ||
|
|
7ae184bfea | ||
|
|
03e2bb0cfd | ||
|
|
b9cd89d108 | ||
|
|
0110e46f6e | ||
|
|
a31ca8d753 | ||
|
|
c710d4afdc | ||
|
|
f77edf4234 | ||
|
|
e8529d1b1e | ||
|
|
11d399dded | ||
|
|
6100be7d82 | ||
|
|
1530c0a7dd | ||
|
|
274531396c | ||
|
|
086363babf | ||
|
|
27ddb2ba3a | ||
|
|
0c1336caf4 | ||
|
|
1a56fc913e | ||
|
|
d5f7703367 | ||
|
|
eaac9f9e93 | ||
|
|
5bf13eaaf8 | ||
|
|
5bfd8ae253 | ||
|
|
03629ba0b6 | ||
|
|
cdab4c3e09 | ||
|
|
7e0cc2bb91 | ||
|
|
c6341ce373 | ||
|
|
5a0b7212c9 | ||
|
|
e9400e8efd | ||
|
|
0006c989d2 | ||
|
|
28c4281495 | ||
|
|
74f942cfa5 | ||
|
|
9f87ef9399 | ||
|
|
75c181ee2b | ||
|
|
6aa9606995 | ||
|
|
b1e35d3244 | ||
|
|
430a987f4e | ||
|
|
dfe81b33fe | ||
|
|
e2c1f80b46 | ||
|
|
b587c54c24 | ||
|
|
0da171b3a6 | ||
|
|
46a51cdcdc | ||
|
|
4c544394eb | ||
|
|
04a5189b50 | ||
|
|
d56c631343 | ||
|
|
d403208d51 | ||
|
|
fc022031d3 | ||
|
|
c99f1206b7 | ||
|
|
ef00633759 | ||
|
|
1eca933d6f | ||
|
|
1dd8d2c7ef | ||
|
|
6a6471aad5 | ||
|
|
edd3939bef | ||
|
|
ac086b1932 | ||
|
|
69ca907868 | ||
|
|
fbff351406 | ||
|
|
9cfcf37968 | ||
|
|
221dfbd322 | ||
|
|
2961c89161 | ||
|
|
0c12ebf6e7 | ||
|
|
f3601fcb13 | ||
|
|
2f76829dc7 | ||
|
|
8acc07e6f0 | ||
|
|
30a455f14a | ||
|
|
2bd6802fa1 | ||
|
|
18d0759b14 | ||
|
|
db8d7d3dc4 | ||
|
|
29251f58e2 | ||
|
|
3830ef2483 | ||
|
|
64324901f2 | ||
|
|
cabc06076b | ||
|
|
d0850369c4 | ||
|
|
4e0af5efd4 | ||
|
|
7721fe83e9 | ||
|
|
1d98666327 | ||
|
|
0cb798ea2b | ||
|
|
560cdf359f | ||
|
|
249cd9381d | ||
|
|
e93387c1d3 | ||
|
|
1423abd15b | ||
|
|
d9e822a14b | ||
|
|
0e2f56d0a1 | ||
|
|
4e74d9cf55 | ||
|
|
3a2d82ae8e | ||
|
|
18ddbf0352 | ||
|
|
d6f0a9ac72 | ||
|
|
2e9859cbfc | ||
|
|
be3a9dbb6f | ||
|
|
ab270a9777 | ||
|
|
6ba50784b5 | ||
|
|
514bbdd670 | ||
|
|
e7c579e1fe | ||
|
|
eae8d05826 | ||
|
|
d790e2db2e | ||
|
|
5fa41e0c84 | ||
|
|
e039c85bb4 | ||
|
|
edce5150bf | ||
|
|
48d8b3d8ac | ||
|
|
fa675256c1 | ||
|
|
666b3437e6 | ||
|
|
06a4acb7d3 | ||
|
|
6204d8c139 | ||
|
|
e84d327fc1 | ||
|
|
cf2f5e19d9 | ||
|
|
9d6fbf0e00 | ||
|
|
a21d1522c7 | ||
|
|
6534b3e09e | ||
|
|
0c875c7751 | ||
|
|
f1408b1729 | ||
|
|
ae3bfe583e | ||
|
|
0b73d0a8d2 | ||
|
|
b8febe60b4 | ||
|
|
f5494b1862 | ||
|
|
6dde56bf93 | ||
|
|
998001fbf2 | ||
|
|
12d5be1bf2 | ||
|
|
6e11ef30e0 | ||
|
|
f06e8c331c | ||
|
|
e8c108e938 | ||
|
|
a2131f907a | ||
|
|
8749fc3f67 | ||
|
|
3c89fb5ffd | ||
|
|
54e8dd11ff | ||
|
|
0f18d8e0ed | ||
|
|
3f6893a4e2 | ||
|
|
c33ef454f0 | ||
|
|
2f8912c36c | ||
|
|
e610dbce14 | ||
|
|
63e41ee192 | ||
|
|
f24ad5d831 | ||
|
|
5ddd507624 | ||
|
|
7ab9cba59b | ||
|
|
f30b18f4de | ||
|
|
a6499ecac2 | ||
|
|
edeaf85cab | ||
|
|
144832ee67 | ||
|
|
acb3b55280 | ||
|
|
fdb575993f | ||
|
|
ad78b50f62 | ||
|
|
56871aeb60 | ||
|
|
492fd5c011 | ||
|
|
2e3d403349 | ||
|
|
ab11c5ebd1 | ||
|
|
cb27dd1a68 | ||
|
|
e2de7e7524 | ||
|
|
3f7bc5a5d2 | ||
|
|
29e488cbb0 | ||
|
|
2f8f29aa0e | ||
|
|
c2e4c64db4 | ||
|
|
a273ce9578 | ||
|
|
b4f8268371 | ||
|
|
c1689166b7 | ||
|
|
ea78a1db32 | ||
|
|
e3446fea9e | ||
|
|
af09df08d7 | ||
|
|
297950e8b8 | ||
|
|
1caabd59b2 | ||
|
|
3640e029d6 | ||
|
|
0b1784b188 | ||
|
|
820b1e6e7d | ||
|
|
440574d624 | ||
|
|
2c26278222 | ||
|
|
b2fd9f7e7c | ||
|
|
f54cde3a15 | ||
|
|
3c140e7bcd | ||
|
|
7f999d2925 | ||
|
|
5cc0522303 | ||
|
|
08ae522ff9 | ||
|
|
183ef7ae9b | ||
|
|
4dc48a0d11 | ||
|
|
91685eeeb1 | ||
|
|
fbfd61b471 | ||
|
|
cfad5e4587 | ||
|
|
1048b5d13a | ||
|
|
c9d9ae7baa | ||
|
|
4eedb0bf94 | ||
|
|
29bfe4655c | ||
|
|
35b7296ff4 | ||
|
|
8e2bbe9105 | ||
|
|
929c686cce | ||
|
|
69c30965eb | ||
|
|
6660458a4c | ||
|
|
db86dfb985 | ||
|
|
1a476b69ad | ||
|
|
480e299436 | ||
|
|
3094eb3626 | ||
|
|
b3e80d2f65 | ||
|
|
5f3adbee33 | ||
|
|
ec511fa709 | ||
|
|
3e78344d87 | ||
|
|
e5f1de1448 | ||
|
|
8507c3f26e | ||
|
|
534c3bc21a | ||
|
|
3d07ed983e | ||
|
|
ce8b6ede34 | ||
|
|
68c0e6e331 | ||
|
|
87264235cc | ||
|
|
32e0d2376f | ||
|
|
9d4f51e1ff | ||
|
|
73a78adf13 | ||
|
|
c7c3b23787 | ||
|
|
9fe7cd8f14 | ||
|
|
2b09884faf | ||
|
|
e19a06d2dd | ||
|
|
fe43406929 | ||
|
|
9ce638695c | ||
|
|
cd2ee8b113 | ||
|
|
1da93f85cc | ||
|
|
86fb7b20bf | ||
|
|
9e0e5ccbf4 | ||
|
|
8fd1031b10 | ||
|
|
f03aed3ca9 | ||
|
|
ecc48369ce | ||
|
|
08c26591e3 | ||
|
|
fc9b4e7958 | ||
|
|
466c277b4f | ||
|
|
5aa347b377 | ||
|
|
0aecb135c9 | ||
|
|
80bde9844b | ||
|
|
7af83a0c11 | ||
|
|
20f047965c | ||
|
|
880ca07719 | ||
|
|
bbe18eacda | ||
|
|
6c3bfb00fc | ||
|
|
bec4bfaa0b | ||
|
|
1e585d01de | ||
|
|
1d6711a764 | ||
|
|
cf7475b9b8 | ||
|
|
3ff82790e1 | ||
|
|
fb1d5717de | ||
|
|
58ac1f8bbe | ||
|
|
b6c794acf6 | ||
|
|
448d696549 | ||
|
|
c7e0c410d6 | ||
|
|
3968550135 | ||
|
|
3a27b3d0d8 | ||
|
|
14b2f7b033 | ||
|
|
bb64c7d8b2 | ||
|
|
c5e0bc1070 | ||
|
|
8b87876094 | ||
|
|
98a64523c4 | ||
|
|
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 | ||
|
|
8d24f8b46b | ||
|
|
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 | ||
|
|
eb87da6127 | ||
|
|
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 | ||
|
|
de5ca516ae | ||
|
|
49890c8ee9 | ||
|
|
fbef85ca5a | ||
|
|
290a63dc54 | ||
|
|
d49bfc4080 | ||
|
|
7820377d00 | ||
|
|
4317e2131f | ||
|
|
82ae4f30ed | ||
|
|
eaeba1b2c8 | ||
|
|
3447062d5e |
+11
-3425
File diff suppressed because it is too large
Load Diff
+5
-15
@@ -14,7 +14,9 @@ each source file that you contribute.
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support in the mailing list.
|
||||
all the support at the Reddit sub:
|
||||
|
||||
http://reddit.com/r/redis
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
@@ -22,12 +24,7 @@ each source file that you contribute.
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. If it is a major feature or a semantical change, please don't start coding
|
||||
straight away: if your feature is not a conceptual fit you'll lose a lot of
|
||||
time writing the code without any reason. Start by posting in the mailing list
|
||||
and creating an issue at Github with the description of, exactly, what you want
|
||||
to accomplish and why. Use cases are important for features to be accepted.
|
||||
Here you'll see if there is consensus about your idea.
|
||||
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
|
||||
|
||||
2. If in step 1 you get an acknowledgment from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -38,13 +35,6 @@ Here you'll see if there is consensus about your idea.
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
e. Done :)
|
||||
|
||||
3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
|
||||
for a *very* long time. However this is not lack of interest, as the project
|
||||
gets more and more users, we find ourselves in a constant need to prioritize
|
||||
certain issues/PRs over others. If you think your issue/PR is very important
|
||||
try to popularize it, have other users commenting and sharing their point of
|
||||
view and so forth. This helps.
|
||||
|
||||
4. For minor fixes just open a pull request on Github.
|
||||
For minor fixes just open a pull request on Github.
|
||||
|
||||
Thanks!
|
||||
|
||||
@@ -34,21 +34,7 @@ Redis Manifesto
|
||||
so that the complexity is obvious and more complex operations can be
|
||||
performed as the sum of the basic operations.
|
||||
|
||||
4 - We believe in code efficiency. Computers get faster and faster, yet we
|
||||
believe that abusing computing capabilities is not wise: the amount of
|
||||
operations you can do for a given amount of energy remains anyway a
|
||||
significant parameter: it allows to do more with less computers and, at
|
||||
the same time, having a smaller environmental impact. Similarly Redis is
|
||||
able to "scale down" to smaller devices. It is perfectly usable in a
|
||||
Raspberry Pi and other small ARM based computers. Faster code having
|
||||
just the layers of abstractions that are really needed will also result,
|
||||
often, in more predictable performances. We think likewise about memory
|
||||
usage, one of the fundamental goals of the Redis project is to
|
||||
incrementally build more and more memory efficient data structures, so that
|
||||
problems that were not approachable in RAM in the past will be perfectly
|
||||
fine to handle in the future.
|
||||
|
||||
5 - Code is like a poem; it's not just something we write to reach some
|
||||
4 - Code is like a poem; it's not just something we write to reach some
|
||||
practical result. Sometimes people that are far from the Redis philosophy
|
||||
suggest using other code written by other authors (frequently in other
|
||||
languages) in order to implement something Redis currently lacks. But to us
|
||||
@@ -59,48 +45,23 @@ Redis Manifesto
|
||||
when needed. At the same time, when writing the Redis story we're trying to
|
||||
write smaller stories that will fit in to other code.
|
||||
|
||||
6 - We're against complexity. We believe designing systems is a fight against
|
||||
5 - We're against complexity. We believe designing systems is a fight against
|
||||
complexity. We'll accept to fight the complexity when it's worthwhile but
|
||||
we'll try hard to recognize when a small feature is not worth 1000s of lines
|
||||
of code. Most of the time the best way to fight complexity is by not
|
||||
creating it at all. Complexity is also a form of lock-in: code that is
|
||||
very hard to understand cannot be modified by users in an independent way
|
||||
regardless of the license. One of the main Redis goals is to remain
|
||||
understandable, enough for a single programmer to have a clear idea of how
|
||||
it works in detail just reading the source code for a couple of weeks.
|
||||
creating it at all.
|
||||
|
||||
7 - Threading is not a silver bullet. Instead of making Redis threaded we
|
||||
believe on the idea of an efficient (mostly) single threaded Redis core.
|
||||
Multiple of such cores, that may run in the same computer or may run
|
||||
in multiple computers, are abstracted away as a single big system by
|
||||
higher order protocols and features: Redis Cluster and the upcoming
|
||||
Redis Proxy are our main goals. A shared nothing approach is not just
|
||||
much simpler (see the previous point in this document), is also optimal
|
||||
in NUMA systems. In the specific case of Redis it allows for each instance
|
||||
to have a more limited amount of data, making the Redis persist-by-fork
|
||||
approach more sounding. In the future we may explore parallelism only for
|
||||
I/O, which is the low hanging fruit: minimal complexity could provide an
|
||||
improved single process experience.
|
||||
|
||||
8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
naturally into a distributed version of Redis and 2) a more complex API that
|
||||
supports multi-key operations. Both are useful if used judiciously but
|
||||
there's no way to make the more complex multi-keys API distributed in an
|
||||
opaque way without violating our other principles. We don't want to provide
|
||||
the illusion of something that will work magically when actually it can't in
|
||||
all cases. Instead we'll provide commands to quickly migrate keys from one
|
||||
instance to another to perform multi-key operations and expose the
|
||||
trade-offs to the user.
|
||||
instance to another to perform multi-key operations and expose the tradeoffs
|
||||
to the user.
|
||||
|
||||
9 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
only way it can be worth is by enjoying it. When there is no longer joy in
|
||||
writing code, the best thing to do is stop. To prevent this, we'll avoid
|
||||
taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
10 - All the above points are put together in what we call opportunistic
|
||||
programming: trying to get the most for the user with minimal increases
|
||||
in complexity (hanging fruits). Solve 95% of the problem with 5% of the
|
||||
code when it is acceptable. Avoid a fixed schedule but follow the flow of
|
||||
user requests, inspiration, Redis internal readiness for certain features
|
||||
(sometimes many past changes reach a critical point making a previously
|
||||
complex feature very easy to obtain).
|
||||
|
||||
Vendored
+4
-6
@@ -89,14 +89,12 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
static int getnum (const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -117,9 +115,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -152,7 +150,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
+7
-1
@@ -824,7 +824,13 @@ aof-use-rdb-preamble yes
|
||||
lua-time-limit 5000
|
||||
|
||||
################################ REDIS CLUSTER ###############################
|
||||
|
||||
#
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
|
||||
# in order to mark it as "mature" we need to wait for a non trivial percentage
|
||||
# of users to deploy it in production.
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#
|
||||
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a Redis instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
||||
@@ -77,15 +77,6 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM needs -latomic at linking time
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
else
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
@@ -319,6 +310,3 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
uninstall:
|
||||
rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)}
|
||||
|
||||
+3
-17
@@ -327,11 +327,12 @@ listNode *listIndex(list *list, long index) {
|
||||
}
|
||||
|
||||
/* Rotate the list removing the tail node and inserting it to the head. */
|
||||
void listRotateTailToHead(list *list) {
|
||||
void listRotate(list *list) {
|
||||
listNode *tail = list->tail;
|
||||
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
/* Detach current tail */
|
||||
listNode *tail = list->tail;
|
||||
list->tail = tail->prev;
|
||||
list->tail->next = NULL;
|
||||
/* Move it as head */
|
||||
@@ -341,21 +342,6 @@ void listRotateTailToHead(list *list) {
|
||||
list->head = tail;
|
||||
}
|
||||
|
||||
/* Rotate the list removing the head node and inserting it to the tail. */
|
||||
void listRotateHeadToTail(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
listNode *head = list->head;
|
||||
/* Detach current head */
|
||||
list->head = head->next;
|
||||
list->head->prev = NULL;
|
||||
/* Move it as tail */
|
||||
list->tail->next = head;
|
||||
head->next = NULL;
|
||||
head->prev = list->tail;
|
||||
list->tail = head;
|
||||
}
|
||||
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
|
||||
+1
-2
@@ -85,8 +85,7 @@ listNode *listSearchKey(list *list, void *key);
|
||||
listNode *listIndex(list *list, long index);
|
||||
void listRewind(list *list, listIter *li);
|
||||
void listRewindTail(list *list, listIter *li);
|
||||
void listRotateTailToHead(list *list);
|
||||
void listRotateHeadToTail(list *list);
|
||||
void listRotate(list *list);
|
||||
void listJoin(list *l, list *o);
|
||||
|
||||
/* Directions for iterators */
|
||||
|
||||
@@ -197,12 +197,6 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
* AOF file implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Return true if an AOf fsync is currently already in progress in a
|
||||
* BIO thread. */
|
||||
int aofFsyncInProgress(void) {
|
||||
return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
}
|
||||
|
||||
/* Starts a background task that performs fsync() against the specified
|
||||
* file descriptor (the one of the AOF file) in another thread. */
|
||||
void aof_background_fsync(int fd) {
|
||||
@@ -339,24 +333,10 @@ void flushAppendOnlyFile(int force) {
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.aof_fsync_offset != server.aof_current_size &&
|
||||
server.unixtime > server.aof_last_fsync &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sdslen(server.aof_buf) == 0) return;
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
sync_in_progress = aofFsyncInProgress();
|
||||
sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
|
||||
/* With this append fsync policy we do background fsyncing.
|
||||
@@ -488,7 +468,6 @@ void flushAppendOnlyFile(int force) {
|
||||
server.aof_buf = sdsempty();
|
||||
}
|
||||
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (server.aof_no_fsync_on_rewrite &&
|
||||
@@ -503,14 +482,10 @@ try_fsync:
|
||||
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.unixtime > server.aof_last_fsync)) {
|
||||
if (!sync_in_progress) {
|
||||
aof_background_fsync(server.aof_fd);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
}
|
||||
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
}
|
||||
@@ -715,7 +690,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* operation is received. */
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -774,26 +748,18 @@ int loadAppendOnlyFile(char *filename) {
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
/* Load the next command in the AOF as our fake client
|
||||
* argv. */
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
/* Parse the argument len. */
|
||||
char *readres = fgets(buf,sizeof(buf),fp);
|
||||
if (readres == NULL || buf[0] != '$') {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
if (readres == NULL)
|
||||
goto readerr;
|
||||
else
|
||||
goto fmterr;
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
|
||||
/* Read it into a string object. */
|
||||
argsds = sdsnewlen(SDS_NOINIT,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
@@ -802,12 +768,10 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(OBJ_STRING,argsds);
|
||||
|
||||
/* Discard CRLF. */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
goto readerr; /* discard CRLF */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,7 +828,6 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
return C_OK;
|
||||
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
@@ -1137,7 +1100,7 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
|
||||
int retval;
|
||||
|
||||
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
|
||||
retval = rioWriteBulkString(r,replyid,sdslen(replyid));
|
||||
if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0;
|
||||
sdsfree(replyid);
|
||||
return retval;
|
||||
}
|
||||
@@ -1272,7 +1235,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,r,key);
|
||||
moduleInitIOContext(io,mt,r);
|
||||
mt->aof_rewrite(&io,key,mv->value);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -1771,7 +1734,6 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@@ -1792,15 +1754,14 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+2
-8
@@ -994,18 +994,12 @@ void bitfieldCommand(client *c) {
|
||||
/* Lookup for read is ok if key doesn't exit, but errors
|
||||
* if it's not a string. */
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
||||
} else {
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
highest_write_offset)) == NULL) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
highest_write_offset)) == NULL) return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
|
||||
+17
-42
@@ -67,21 +67,6 @@
|
||||
|
||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
||||
|
||||
/* This structure represents the blocked key information that we store
|
||||
* in the client structure. Each client blocked on keys, has a
|
||||
* client->bpop.keys hash table. The keys of the hash table are Redis
|
||||
* keys pointers to 'robj' structures. The value is this structure.
|
||||
* The structure has two goals: firstly we store the list node that this
|
||||
* client uses to be listed in the database "blocked clients for this key"
|
||||
* list, so we can later unblock in O(1) without a list scan.
|
||||
* Secondly for certain blocking types, we have additional info. Right now
|
||||
* the only use for additional info we have is when clients are blocked
|
||||
* on streams, as we have to remember the ID it blocked for. */
|
||||
typedef struct bkinfo {
|
||||
listNode *listnode; /* List node for db->blocking_keys[key] list. */
|
||||
streamID stream_id; /* Stream ID if we blocked in a stream. */
|
||||
} bkinfo;
|
||||
|
||||
/* Get a timeout value from an object and store it into 'timeout'.
|
||||
* The final timeout is always stored as milliseconds as a time where the
|
||||
* timeout will expire, however the parsing is performed according to
|
||||
@@ -277,16 +262,6 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* we can safely call signalKeyAsReady() against this key. */
|
||||
dictDelete(rl->db->ready_keys,rl->key);
|
||||
|
||||
/* Even if we are not inside call(), increment the call depth
|
||||
* in order to make sure that keys are expired against a fixed
|
||||
* reference time, and not against the wallclock time. This
|
||||
* way we can lookup an object multiple times (BRPOPLPUSH does
|
||||
* that) without the risk of it being freed in the second
|
||||
* lookup, invalidating the first one.
|
||||
* See https://github.com/antirez/redis/pull/6554. */
|
||||
server.fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
if (o != NULL && o->type == OBJ_LIST) {
|
||||
@@ -306,7 +281,8 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listRotateHeadToTail(clients);
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -367,7 +343,8 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listRotateHeadToTail(clients);
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -411,9 +388,8 @@ void handleClientsBlockedOnKeys(void) {
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
bkinfo *bki = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
streamID *gt = &bki->stream_id;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
@@ -443,7 +419,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
streamIncrID(&start);
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
@@ -481,7 +457,6 @@ void handleClientsBlockedOnKeys(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
server.fixed_time_expire--;
|
||||
|
||||
/* Free this item. */
|
||||
decrRefCount(rl->key);
|
||||
@@ -530,15 +505,17 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
if (target != NULL) incrRefCount(target);
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
/* Allocate our bkinfo structure, associated to each key the client
|
||||
* is blocked for. */
|
||||
bkinfo *bki = zmalloc(sizeof(*bki));
|
||||
if (btype == BLOCKED_STREAM)
|
||||
bki->stream_id = ids[j];
|
||||
/* The value associated with the key name in the bpop.keys dictionary
|
||||
* is NULL for lists and sorted sets, or the stream ID for streams. */
|
||||
void *key_data = NULL;
|
||||
if (btype == BLOCKED_STREAM) {
|
||||
key_data = zmalloc(sizeof(streamID));
|
||||
memcpy(key_data,ids+j,sizeof(streamID));
|
||||
}
|
||||
|
||||
/* If the key already exists in the dictionary ignore it. */
|
||||
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
|
||||
zfree(bki);
|
||||
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
|
||||
zfree(key_data);
|
||||
continue;
|
||||
}
|
||||
incrRefCount(keys[j]);
|
||||
@@ -557,7 +534,6 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
l = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
bki->listnode = listLast(l);
|
||||
}
|
||||
blockClient(c,btype);
|
||||
}
|
||||
@@ -574,12 +550,11 @@ void unblockClientWaitingData(client *c) {
|
||||
/* The client may wait for multiple keys, so unblock it for every key. */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
bkinfo *bki = dictGetVal(de);
|
||||
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
l = dictFetchValue(c->db->blocking_keys,key);
|
||||
serverAssertWithInfo(c,key,l != NULL);
|
||||
listDelNode(l,bki->listnode);
|
||||
listDelNode(l,listSearchKey(l,c));
|
||||
/* If the list is empty we need to remove it to avoid wasting memory */
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->blocking_keys,key);
|
||||
|
||||
+10
-25
@@ -138,7 +138,6 @@ int clusterLoadConfig(char *filename) {
|
||||
/* Handle the special "vars" line. Don't pretend it is the last
|
||||
* line even if it actually is when generated by Redis. */
|
||||
if (strcasecmp(argv[0],"vars") == 0) {
|
||||
if (!(argc % 2)) goto fmterr;
|
||||
for (j = 1; j < argc; j += 2) {
|
||||
if (strcasecmp(argv[j],"currentEpoch") == 0) {
|
||||
server.cluster->currentEpoch =
|
||||
@@ -157,10 +156,7 @@ int clusterLoadConfig(char *filename) {
|
||||
}
|
||||
|
||||
/* Regular config lines have at least eight fields */
|
||||
if (argc < 8) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
if (argc < 8) goto fmterr;
|
||||
|
||||
/* Create this node if it does not exist */
|
||||
n = clusterLookupNode(argv[0]);
|
||||
@@ -169,10 +165,7 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(n);
|
||||
}
|
||||
/* Address and port */
|
||||
if ((p = strrchr(argv[1],':')) == NULL) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
|
||||
*p = '\0';
|
||||
memcpy(n->ip,argv[1],strlen(argv[1])+1);
|
||||
char *port = p+1;
|
||||
@@ -253,10 +246,7 @@ int clusterLoadConfig(char *filename) {
|
||||
*p = '\0';
|
||||
direction = p[1]; /* Either '>' or '<' */
|
||||
slot = atoi(argv[j]+1);
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
|
||||
p += 3;
|
||||
cn = clusterLookupNode(p);
|
||||
if (!cn) {
|
||||
@@ -276,12 +266,8 @@ int clusterLoadConfig(char *filename) {
|
||||
} else {
|
||||
start = stop = atoi(argv[j]);
|
||||
}
|
||||
if (start < 0 || start >= CLUSTER_SLOTS ||
|
||||
stop < 0 || stop >= CLUSTER_SLOTS)
|
||||
{
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
|
||||
while(start <= stop) clusterAddSlot(n, start++);
|
||||
}
|
||||
|
||||
@@ -3045,7 +3031,6 @@ void clusterHandleSlaveFailover(void) {
|
||||
if (server.cluster->mf_end) {
|
||||
server.cluster->failover_auth_time = mstime();
|
||||
server.cluster->failover_auth_rank = 0;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Start of election delayed for %lld milliseconds "
|
||||
@@ -4790,7 +4775,7 @@ NULL
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
void createDumpPayload(rio *payload, robj *o) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
@@ -4798,7 +4783,7 @@ void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
serverAssert(rdbSaveObject(payload,o));
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@@ -4856,7 +4841,7 @@ void dumpCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Create the DUMP encoded representation. */
|
||||
createDumpPayload(&payload,o,c->argv[1]);
|
||||
createDumpPayload(&payload,o);
|
||||
|
||||
/* Transfer to the client */
|
||||
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
|
||||
@@ -4929,7 +4914,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -5217,7 +5202,7 @@ try_again:
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,ov[j],kv[j]);
|
||||
createDumpPayload(&payload,ov[j]);
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
|
||||
+6
-5
@@ -1042,8 +1042,8 @@ void configSetCommand(client *c) {
|
||||
int soft_seconds;
|
||||
|
||||
class = getClientTypeByName(v[j]);
|
||||
hard = memtoll(v[j+1],NULL);
|
||||
soft = memtoll(v[j+2],NULL);
|
||||
hard = strtoll(v[j+1],NULL,10);
|
||||
soft = strtoll(v[j+2],NULL,10);
|
||||
soft_seconds = strtoll(v[j+3],NULL,10);
|
||||
|
||||
server.client_obuf_limits[class].hard_limit_bytes = hard;
|
||||
@@ -1654,11 +1654,12 @@ void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *
|
||||
* If the old file does not exist at all, an empty state is returned. */
|
||||
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
FILE *fp = fopen(path,"r");
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
struct rewriteConfigState *state = zmalloc(sizeof(*state));
|
||||
char buf[CONFIG_MAX_LINE+1];
|
||||
int linenum = -1;
|
||||
struct rewriteConfigState *state = zmalloc(sizeof(*state));
|
||||
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
|
||||
state->rewritten = dictCreate(&optionSetDictType,NULL);
|
||||
state->numlines = 0;
|
||||
|
||||
@@ -542,7 +542,7 @@ void keysCommand(client *c) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj;
|
||||
@@ -1129,7 +1129,6 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
/* Check if the key is expired. */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
@@ -1141,26 +1140,8 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
if (server.lua_caller) {
|
||||
now = server.lua_time_start;
|
||||
}
|
||||
/* If we are in the middle of a command execution, we still want to use
|
||||
* a reference time that does not change: in that case we just use the
|
||||
* cached time, that we update before each call in the call() function.
|
||||
* This way we avoid that commands such as RPOPLPUSH or similar, that
|
||||
* may re-open the same key multiple times, can invalidate an already
|
||||
* open object in a next call, if the next call will see the key expired,
|
||||
* while the first did not. */
|
||||
else if (server.fixed_time_expire > 0) {
|
||||
now = server.mstime;
|
||||
}
|
||||
/* For the other cases, we want to use the most fresh time we have. */
|
||||
else {
|
||||
now = mstime();
|
||||
}
|
||||
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
|
||||
/* The key expired if the current (virtual or real) time is greater
|
||||
* than the expire time of the key. */
|
||||
return now > when;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@@ -355,7 +355,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
sdsele = ln->value;
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
unsigned int hash = dictGetHash(d, sdsele);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
@@ -392,7 +392,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
|
||||
@@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
|
||||
+2
-10
@@ -614,7 +614,6 @@ int hllSparseToDense(robj *o) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@@ -700,7 +699,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
p += oplen;
|
||||
first += span;
|
||||
}
|
||||
if (span == 0 || p >= end) return -1; /* Invalid format. */
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@@ -1014,12 +1013,7 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
double m = HLL_REGISTERS;
|
||||
double E;
|
||||
int j;
|
||||
/* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
|
||||
* the maximum frequency of the "000...1" sequence the hash function is
|
||||
* able to return. However it is slow to check for sanity of the
|
||||
* input: instead we history array at a safe size: overflows will
|
||||
* just write data to wrong, but correctly allocated, places. */
|
||||
int reghisto[64] = {0};
|
||||
int reghisto[HLL_Q+2] = {0};
|
||||
|
||||
/* Compute register histogram */
|
||||
if (hdr->encoding == HLL_DENSE) {
|
||||
@@ -1094,7 +1088,6 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
@@ -1535,7 +1528,6 @@ void pfdebugCommand(client *c) {
|
||||
sds decoded = sdsempty();
|
||||
|
||||
if (hdr->encoding != HLL_SPARSE) {
|
||||
sdsfree(decoded);
|
||||
addReplyError(c,"HLL encoding is not sparse");
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n
|
||||
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)
|
||||
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) && \
|
||||
|
||||
+59
-512
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "rdb.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
@@ -50,8 +49,6 @@ struct RedisModule {
|
||||
list *types; /* Module data types. */
|
||||
list *usedby; /* List of modules using APIs from this one. */
|
||||
list *using; /* List of modules we use some APIs of. */
|
||||
list *filters; /* List of filters the module has registered. */
|
||||
int in_call; /* RM_Call() nesting level */
|
||||
};
|
||||
typedef struct RedisModule RedisModule;
|
||||
|
||||
@@ -133,14 +130,10 @@ struct RedisModuleCtx {
|
||||
int keys_count;
|
||||
|
||||
struct RedisModulePoolAllocBlock *pa_head;
|
||||
redisOpArray saved_oparray; /* When propagating commands in a callback
|
||||
we reallocate the "also propagate" op
|
||||
array. Here we save the old one to
|
||||
restore it later. */
|
||||
};
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
||||
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
||||
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
||||
@@ -148,7 +141,6 @@ typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
||||
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
|
||||
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
|
||||
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
|
||||
|
||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||
struct RedisModuleKey {
|
||||
@@ -278,30 +270,6 @@ typedef struct RedisModuleDictIter {
|
||||
raxIterator ri;
|
||||
} RedisModuleDictIter;
|
||||
|
||||
typedef struct RedisModuleCommandFilterCtx {
|
||||
RedisModuleString **argv;
|
||||
int argc;
|
||||
} RedisModuleCommandFilterCtx;
|
||||
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
typedef struct RedisModuleCommandFilter {
|
||||
/* The module that registered the filter */
|
||||
RedisModule *module;
|
||||
/* Filter callback function */
|
||||
RedisModuleCommandFilterFunc callback;
|
||||
/* REDISMODULE_CMDFILTER_* flags */
|
||||
int flags;
|
||||
} RedisModuleCommandFilter;
|
||||
|
||||
/* Registered filters */
|
||||
static list *moduleCommandFilters;
|
||||
|
||||
/* Flags for moduleCreateArgvFromUserFormat(). */
|
||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||
#define REDISMODULE_ARGV_NO_AOF (1<<1)
|
||||
#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -507,47 +475,8 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
* details needed to correctly replicate commands. */
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
/* We don't need to do anything here if the context was never used
|
||||
* in order to propagate commands. */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
|
||||
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrapped around MULTI/EXEC. */
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
|
||||
/* If this is not a module command context (but is instead a simple
|
||||
* callback context), we have to handle directly the "also propagate"
|
||||
* array and emit it. In a module command call this will be handled
|
||||
* directly by call(). */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
|
||||
server.also_propagate.numops)
|
||||
{
|
||||
for (int j = 0; j < server.also_propagate.numops; j++) {
|
||||
redisOp *rop = &server.also_propagate.ops[j];
|
||||
int target = rop->target;
|
||||
if (target)
|
||||
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
|
||||
}
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
/* Restore the previous oparray in case of nexted use of the API. */
|
||||
server.also_propagate = ctx->saved_oparray;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the context after the user function was called. */
|
||||
void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
moduleHandlePropagationAfterCommandCallback(ctx);
|
||||
autoMemoryCollect(ctx);
|
||||
poolAllocRelease(ctx);
|
||||
if (ctx->postponed_arrays) {
|
||||
@@ -563,33 +492,35 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
* details needed to correctly replicate commands. */
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrapped around MULTI/EXEC. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* This Redis command binds the normal Redis command invocation with commands
|
||||
* exported by modules. */
|
||||
void RedisModuleCommandDispatcher(client *c) {
|
||||
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
|
||||
ctx.module = cp->module;
|
||||
ctx.client = c;
|
||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
|
||||
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
||||
* expand the query buffer, and in order to avoid a big object copy
|
||||
* the query buffer SDS may be used directly as the SDS string backing
|
||||
* the client argument vectors: sometimes this will result in the SDS
|
||||
* string having unused space at the end. Later if a module takes ownership
|
||||
* of the RedisString, such space will be wasted forever. Inside the
|
||||
* Redis core this is not a problem because tryObjectEncoding() is called
|
||||
* before storing strings in the key space. Here we need to do it
|
||||
* for the module. */
|
||||
for (int i = 0; i < c->argc; i++) {
|
||||
/* Only do the work if the module took ownership of the object:
|
||||
* in that case the refcount is no longer 1. */
|
||||
if (c->argv[i]->refcount > 1)
|
||||
trimStringObjectIfNeeded(c->argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the list of keys, with the same interface as the
|
||||
@@ -647,9 +578,9 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
|
||||
* flags into the command flags used by the Redis core.
|
||||
*
|
||||
* It returns the set of flags, or -1 if unknown flags are found. */
|
||||
int64_t commandFlagsFromString(char *s) {
|
||||
int commandFlagsFromString(char *s) {
|
||||
int count, j;
|
||||
int64_t flags = 0;
|
||||
int flags = 0;
|
||||
sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count);
|
||||
for (j = 0; j < count; j++) {
|
||||
char *t = tokens[j];
|
||||
@@ -727,7 +658,7 @@ int64_t commandFlagsFromString(char *s) {
|
||||
* other reason.
|
||||
*/
|
||||
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
||||
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
if (flags == -1) return REDISMODULE_ERR;
|
||||
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
|
||||
return REDISMODULE_ERR;
|
||||
@@ -783,8 +714,6 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
module->types = listCreate();
|
||||
module->usedby = listCreate();
|
||||
module->using = listCreate();
|
||||
module->filters = listCreate();
|
||||
module->in_call = 0;
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
@@ -1273,17 +1202,6 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a C buffer pointer that is
|
||||
* assumed to be null-terminated.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyBulkCString(c,(char*)buf);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a RedisModuleString object.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
@@ -1346,16 +1264,9 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
* executed into MULTI/EXEC, they are executed as single commands
|
||||
* executed into MUTLI/EXEC, they are executed as single commands
|
||||
* from an external client in essence. */
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
|
||||
/* If this is a callback context, and not a module command execution
|
||||
* context, we have to setup the op array for the "also propagate" API
|
||||
* so that RM_Replicate() will work. */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
|
||||
ctx->saved_oparray = server.also_propagate;
|
||||
redisOpArrayInit(&server.also_propagate);
|
||||
}
|
||||
execCommandPropagateMulti(ctx->client);
|
||||
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
||||
}
|
||||
@@ -1377,24 +1288,6 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* Please refer to RedisModule_Call() for more information.
|
||||
*
|
||||
* Using the special "A" and "R" modifiers, the caller can exclude either
|
||||
* the AOF or the replicas from the propagation of the specified command.
|
||||
* Otherwise, by default, the command will be propagated in both channels.
|
||||
*
|
||||
* ## Note about calling this function from a thread safe context:
|
||||
*
|
||||
* Normally when you call this function from the callback implementing a
|
||||
* module command, or any other callback provided by the Redis Module API,
|
||||
* Redis will accumulate all the calls to this function in the context of
|
||||
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
|
||||
* transaction. However when calling this function from a threaded safe context
|
||||
* that can live an undefined amount of time, and can be locked/unlocked in
|
||||
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
|
||||
* and the command specified is inserted in the AOF and replication stream
|
||||
* immediately.
|
||||
*
|
||||
* ## Return value
|
||||
*
|
||||
* The command returns REDISMODULE_ERR if the format specifiers are invalid
|
||||
* or the command name does not belong to a known command. */
|
||||
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||
@@ -1412,23 +1305,10 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
||||
va_end(ap);
|
||||
if (argv == NULL) return REDISMODULE_ERR;
|
||||
|
||||
/* Select the propagation target. Usually is AOF + replicas, however
|
||||
* the caller can exclude one or the other using the "A" or "R"
|
||||
* modifiers. */
|
||||
int target = 0;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
|
||||
|
||||
/* Replicate! When we are in a threaded context, we want to just insert
|
||||
* the replicated command ASAP, since it is not clear when the context
|
||||
* will stop being used, so accumulating stuff does not make much sense,
|
||||
* nor we could easily use the alsoPropagate() API from threads. */
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
|
||||
propagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||
} else {
|
||||
moduleReplicateMultiIfNeeded(ctx);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||
}
|
||||
/* Replicate! */
|
||||
moduleReplicateMultiIfNeeded(ctx);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
|
||||
/* Release the argv. */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
@@ -1492,9 +1372,6 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICATED: The command was sent over the replication
|
||||
* link by the MASTER
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
|
||||
@@ -1517,23 +1394,6 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before
|
||||
* reaching the maxmemory level.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_LOADING: Server is loading RDB/AOF
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to
|
||||
* connect with the master.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB
|
||||
* transfer is in progress.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link
|
||||
* with its master. This is the
|
||||
* contrary of STALE state.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background
|
||||
* process active (RDB, AUX or module).
|
||||
*/
|
||||
int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
|
||||
@@ -1544,17 +1404,11 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_LUA;
|
||||
if (ctx->client->flags & CLIENT_MULTI)
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI;
|
||||
/* Module command recieved from MASTER, is replicated. */
|
||||
if (ctx->client->flags & CLIENT_MASTER)
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
|
||||
}
|
||||
|
||||
if (server.cluster_enabled)
|
||||
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
|
||||
|
||||
if (server.loading)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LOADING;
|
||||
|
||||
/* Maxmemory and eviction policy */
|
||||
if (server.maxmemory > 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
|
||||
@@ -1576,20 +1430,6 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
|
||||
if (server.repl_slave_ro)
|
||||
flags |= REDISMODULE_CTX_FLAGS_READONLY;
|
||||
|
||||
/* Replica state flags. */
|
||||
if (server.repl_state == REPL_STATE_CONNECT ||
|
||||
server.repl_state == REPL_STATE_CONNECTING)
|
||||
{
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING;
|
||||
} else if (server.repl_state == REPL_STATE_TRANSFER) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING;
|
||||
} else if (server.repl_state == REPL_STATE_CONNECTED) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE;
|
||||
}
|
||||
|
||||
if (server.repl_state != REPL_STATE_CONNECTED)
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE;
|
||||
}
|
||||
|
||||
/* OOM flag. */
|
||||
@@ -1598,9 +1438,6 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
if (retval == C_ERR) flags |= REDISMODULE_CTX_FLAGS_OOM;
|
||||
if (level > 0.75) flags |= REDISMODULE_CTX_FLAGS_OOM_WARNING;
|
||||
|
||||
/* Presence of children processes. */
|
||||
if (hasActiveChildProcess()) flags |= REDISMODULE_CTX_FLAGS_ACTIVE_CHILD;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -2780,11 +2617,12 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
|
||||
* to special modifiers in "fmt". For now only one exists:
|
||||
*
|
||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||
* "A" -> REDISMODULE_ARGV_NO_AOF
|
||||
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
|
||||
*
|
||||
* On error (format specifier error) NULL is returned and nothing is
|
||||
* allocated. On success the argument vector is returned. */
|
||||
|
||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||
|
||||
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
|
||||
int argc = 0, argv_size, j;
|
||||
robj **argv = NULL;
|
||||
@@ -2813,7 +2651,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
size_t len = va_arg(ap,size_t);
|
||||
argv[argc++] = createStringObject(buf,len);
|
||||
} else if (*p == 'l') {
|
||||
long long ll = va_arg(ap,long long);
|
||||
long ll = va_arg(ap,long long);
|
||||
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
|
||||
} else if (*p == 'v') {
|
||||
/* A vector of strings */
|
||||
@@ -2833,10 +2671,6 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
}
|
||||
} else if (*p == '!') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
|
||||
} else if (*p == 'A') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
|
||||
} else if (*p == 'R') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
|
||||
} else {
|
||||
goto fmterr;
|
||||
}
|
||||
@@ -2857,10 +2691,7 @@ fmterr:
|
||||
* NULL is returned and errno is set to the following values:
|
||||
*
|
||||
* EINVAL: command non existing, wrong arity, wrong format specifier.
|
||||
* EPERM: operation in Cluster instance with key in non local slot.
|
||||
*
|
||||
* This API is documented here: https://redis.io/topics/modules-intro
|
||||
*/
|
||||
* EPERM: operation in Cluster instance with key in non local slot. */
|
||||
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||
struct redisCommand *cmd;
|
||||
client *c = NULL;
|
||||
@@ -2870,6 +2701,12 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
RedisModuleCallReply *reply = NULL;
|
||||
int replicate = 0; /* Replicate this command? */
|
||||
|
||||
cmd = lookupCommandByCString((char*)cmdname);
|
||||
if (!cmd) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create the client and dispatch the command. */
|
||||
va_start(ap, fmt);
|
||||
c = createClient(-1);
|
||||
@@ -2882,25 +2719,11 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
c->db = ctx->client->db;
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
if (ctx->module) ctx->module->in_call++;
|
||||
|
||||
c->cmd = c->lastcmd = cmd;
|
||||
/* We handle the above format error only when the client is setup so that
|
||||
* we can free it normally. */
|
||||
if (argv == NULL) goto cleanup;
|
||||
|
||||
/* Call command filters */
|
||||
moduleCallCommandFilters(c);
|
||||
|
||||
/* Lookup command now, after filters had a chance to make modifications
|
||||
* if necessary.
|
||||
*/
|
||||
cmd = lookupCommand(c->argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
errno = EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
c->cmd = c->lastcmd = cmd;
|
||||
|
||||
/* Basic arity checks. */
|
||||
if ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity)) {
|
||||
errno = EINVAL;
|
||||
@@ -2930,10 +2753,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
/* Run the command */
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||
if (replicate) {
|
||||
if (!(flags & REDISMODULE_ARGV_NO_AOF))
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
}
|
||||
call(c,call_flags);
|
||||
|
||||
@@ -2952,7 +2773,6 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
|
||||
|
||||
cleanup:
|
||||
if (ctx->module) ctx->module->in_call--;
|
||||
freeClient(c);
|
||||
return reply;
|
||||
}
|
||||
@@ -3188,11 +3008,6 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
struct {
|
||||
moduleTypeAuxLoadFunc aux_load;
|
||||
moduleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
} v2;
|
||||
} *tms = (struct typemethods*) typemethods_ptr;
|
||||
|
||||
moduleType *mt = zcalloc(sizeof(*mt));
|
||||
@@ -3204,11 +3019,6 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
||||
mt->mem_usage = tms->mem_usage;
|
||||
mt->digest = tms->digest;
|
||||
mt->free = tms->free;
|
||||
if (tms->version >= 2) {
|
||||
mt->aux_load = tms->v2.aux_load;
|
||||
mt->aux_save = tms->v2.aux_save;
|
||||
mt->aux_save_triggers = tms->v2.aux_save_triggers;
|
||||
}
|
||||
memcpy(mt->name,name,sizeof(mt->name));
|
||||
listAddNodeTail(ctx->module->types,mt);
|
||||
return mt;
|
||||
@@ -3475,36 +3285,6 @@ loaderr:
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
|
||||
* who asked for it. */
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when) {
|
||||
size_t total_written = 0;
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(module->types,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
moduleType *mt = ln->value;
|
||||
if (!mt->aux_save || !(mt->aux_save_triggers & when))
|
||||
continue;
|
||||
ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
|
||||
if (ret==-1) {
|
||||
dictReleaseIterator(di);
|
||||
return -1;
|
||||
}
|
||||
total_written += ret;
|
||||
}
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
return total_written;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Key digest API (DEBUG DIGEST interface for modules types)
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3634,14 +3414,6 @@ RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
|
||||
return io->ctx;
|
||||
}
|
||||
|
||||
/* Returns a RedisModuleString with the name of the key currently saving or
|
||||
* loading, when an IO data type callback is called. There is no guarantee
|
||||
* that the key name is always available, so this may return NULL.
|
||||
*/
|
||||
const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) {
|
||||
return io->key;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Logging
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3663,9 +3435,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
else if (!strcasecmp(levelstr,"warning")) level = LL_WARNING;
|
||||
else level = LL_VERBOSE; /* Default. */
|
||||
|
||||
if (level < server.verbosity) return;
|
||||
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module? module->name: "module");
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
|
||||
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
|
||||
serverLogRaw(level,msg);
|
||||
}
|
||||
@@ -3683,15 +3453,13 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
* There is a fixed limit to the length of the log line this function is able
|
||||
* to emit, this limit is not specified but is guaranteed to be more than
|
||||
* a few lines of text.
|
||||
*
|
||||
* The ctx argument may be NULL if cannot be provided in the context of the
|
||||
* caller for instance threads or callbacks, in which case a generic "module"
|
||||
* will be used instead of the module name.
|
||||
*/
|
||||
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
|
||||
if (!ctx->module) return; /* Can only log if module is initialized */
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
RM_LogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap);
|
||||
RM_LogRaw(ctx->module,levelstr,fmt,ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -3913,7 +3681,14 @@ void moduleHandleBlockedClients(void) {
|
||||
* replies to send to the client in a thread safe context.
|
||||
* We need to glue such replies to the client output buffer and
|
||||
* free the temporary client we just used for the replies. */
|
||||
if (c) AddReplyFromClient(c, bc->reply_client);
|
||||
if (c) {
|
||||
if (bc->reply_client->bufpos)
|
||||
addReplyString(c,bc->reply_client->buf,
|
||||
bc->reply_client->bufpos);
|
||||
if (listLength(bc->reply_client->reply))
|
||||
listJoin(c->reply,bc->reply_client->reply);
|
||||
c->reply_bytes += bc->reply_client->reply_bytes;
|
||||
}
|
||||
freeClient(bc->reply_client);
|
||||
|
||||
if (c != NULL) {
|
||||
@@ -4008,9 +3783,9 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* To call non-reply APIs, the thread safe context must be prepared with:
|
||||
*
|
||||
* RedisModule_ThreadSafeContextLock(ctx);
|
||||
* RedisModule_ThreadSafeCallStart(ctx);
|
||||
* ... make your call here ...
|
||||
* RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
* RedisModule_ThreadSafeCallStop(ctx);
|
||||
*
|
||||
* This is not needed when using `RedisModule_Reply*` functions, assuming
|
||||
* that a blocked client was used when the context was created, otherwise
|
||||
@@ -4032,10 +3807,7 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
|
||||
* in order to keep things like the currently selected database and similar
|
||||
* things. */
|
||||
ctx->client = createClient(-1);
|
||||
if (bc) {
|
||||
selectDb(ctx->client,bc->dbid);
|
||||
if (bc->client) ctx->client->id = bc->client->id;
|
||||
}
|
||||
if (bc) selectDb(ctx->client,bc->dbid);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -4378,13 +4150,10 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
UNUSED(ctx);
|
||||
|
||||
clusterNode *node = clusterLookupNode(id);
|
||||
if (node == NULL ||
|
||||
node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
{
|
||||
if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (ip) strncpy(ip,node->ip,NET_IP_STR_LEN);
|
||||
if (ip) memcpy(ip,node->name,REDISMODULE_NODE_ID_LEN);
|
||||
|
||||
if (master_id) {
|
||||
/* If the information is not available, the function will set the
|
||||
@@ -4975,214 +4744,6 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Unregister all filters registered by a module.
|
||||
* This is called when a module is being unloaded.
|
||||
*
|
||||
* Returns the number of filters unregistered. */
|
||||
int moduleUnregisterFilters(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
int count = 0;
|
||||
|
||||
listRewind(module->filters,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleCommandFilter *filter = ln->value;
|
||||
listNode *ln = listSearchKey(moduleCommandFilters,filter);
|
||||
if (ln) {
|
||||
listDelNode(moduleCommandFilters,ln);
|
||||
count++;
|
||||
}
|
||||
zfree(filter);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Module Command Filter API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Register a new command filter function.
|
||||
*
|
||||
* Command filtering makes it possible for modules to extend Redis by plugging
|
||||
* into the execution flow of all commands.
|
||||
*
|
||||
* A registered filter gets called before Redis executes *any* command. This
|
||||
* includes both core Redis commands and commands registered by any module. The
|
||||
* filter applies in all execution paths including:
|
||||
*
|
||||
* 1. Invocation by a client.
|
||||
* 2. Invocation through `RedisModule_Call()` by any module.
|
||||
* 3. Invocation through Lua 'redis.call()`.
|
||||
* 4. Replication of a command from a master.
|
||||
*
|
||||
* The filter executes in a special filter context, which is different and more
|
||||
* limited than a RedisModuleCtx. Because the filter affects any command, it
|
||||
* must be implemented in a very efficient way to reduce the performance impact
|
||||
* on Redis. All Redis Module API calls that require a valid context (such as
|
||||
* `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a
|
||||
* filter context.
|
||||
*
|
||||
* The `RedisModuleCommandFilterCtx` can be used to inspect or modify the
|
||||
* executed command and its arguments. As the filter executes before Redis
|
||||
* begins processing the command, any change will affect the way the command is
|
||||
* processed. For example, a module can override Redis commands this way:
|
||||
*
|
||||
* 1. Register a `MODULE.SET` command which implements an extended version of
|
||||
* the Redis `SET` command.
|
||||
* 2. Register a command filter which detects invocation of `SET` on a specific
|
||||
* pattern of keys. Once detected, the filter will replace the first
|
||||
* argument from `SET` to `MODULE.SET`.
|
||||
* 3. When filter execution is complete, Redis considers the new command name
|
||||
* and therefore executes the module's own command.
|
||||
*
|
||||
* Note that in the above use case, if `MODULE.SET` itself uses
|
||||
* `RedisModule_Call()` the filter will be applied on that call as well. If
|
||||
* that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when
|
||||
* registering the filter.
|
||||
*
|
||||
* The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that
|
||||
* originate from the module's own `RM_Call()` from reaching the filter. This
|
||||
* flag is effective for all execution flows, including nested ones, as long as
|
||||
* the execution begins from the module's command context or a thread-safe
|
||||
* context that is associated with a blocking command.
|
||||
*
|
||||
* Detached thread-safe contexts are *not* associated with the module and cannot
|
||||
* be protected by this flag.
|
||||
*
|
||||
* If multiple filters are registered (by the same or different modules), they
|
||||
* are executed in the order of registration.
|
||||
*/
|
||||
|
||||
RedisModuleCommandFilter *RM_RegisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc callback, int flags) {
|
||||
RedisModuleCommandFilter *filter = zmalloc(sizeof(*filter));
|
||||
filter->module = ctx->module;
|
||||
filter->callback = callback;
|
||||
filter->flags = flags;
|
||||
|
||||
listAddNodeTail(moduleCommandFilters, filter);
|
||||
listAddNodeTail(ctx->module->filters, filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
/* Unregister a command filter.
|
||||
*/
|
||||
int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) {
|
||||
listNode *ln;
|
||||
|
||||
/* A module can only remove its own filters */
|
||||
if (filter->module != ctx->module) return REDISMODULE_ERR;
|
||||
|
||||
ln = listSearchKey(moduleCommandFilters,filter);
|
||||
if (!ln) return REDISMODULE_ERR;
|
||||
listDelNode(moduleCommandFilters,ln);
|
||||
|
||||
ln = listSearchKey(ctx->module->filters,filter);
|
||||
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
|
||||
listDelNode(ctx->module->filters,ln);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void moduleCallCommandFilters(client *c) {
|
||||
if (listLength(moduleCommandFilters) == 0) return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleCommandFilters,&li);
|
||||
|
||||
RedisModuleCommandFilterCtx filter = {
|
||||
.argv = c->argv,
|
||||
.argc = c->argc
|
||||
};
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleCommandFilter *f = ln->value;
|
||||
|
||||
/* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is
|
||||
* currently processing a command.
|
||||
*/
|
||||
if ((f->flags & REDISMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue;
|
||||
|
||||
/* Call filter */
|
||||
f->callback(&filter);
|
||||
}
|
||||
|
||||
c->argv = filter.argv;
|
||||
c->argc = filter.argc;
|
||||
}
|
||||
|
||||
/* Return the number of arguments a filtered command has. The number of
|
||||
* arguments include the command itself.
|
||||
*/
|
||||
int RM_CommandFilterArgsCount(RedisModuleCommandFilterCtx *fctx)
|
||||
{
|
||||
return fctx->argc;
|
||||
}
|
||||
|
||||
/* Return the specified command argument. The first argument (position 0) is
|
||||
* the command itself, and the rest are user-provided args.
|
||||
*/
|
||||
const RedisModuleString *RM_CommandFilterArgGet(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= fctx->argc) return NULL;
|
||||
return fctx->argv[pos];
|
||||
}
|
||||
|
||||
/* Modify the filtered command by inserting a new argument at the specified
|
||||
* position. The specified RedisModuleString argument may be used by Redis
|
||||
* after the filter context is destroyed, so it must not be auto-memory
|
||||
* allocated, freed or used elsewhere.
|
||||
*/
|
||||
|
||||
int RM_CommandFilterArgInsert(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (pos < 0 || pos > fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
fctx->argv = zrealloc(fctx->argv, (fctx->argc+1)*sizeof(RedisModuleString *));
|
||||
for (i = fctx->argc; i > pos; i--) {
|
||||
fctx->argv[i] = fctx->argv[i-1];
|
||||
}
|
||||
fctx->argv[pos] = arg;
|
||||
fctx->argc++;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Modify the filtered command by replacing an existing argument with a new one.
|
||||
* The specified RedisModuleString argument may be used by Redis after the
|
||||
* filter context is destroyed, so it must not be auto-memory allocated, freed
|
||||
* or used elsewhere.
|
||||
*/
|
||||
|
||||
int RM_CommandFilterArgReplace(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
|
||||
{
|
||||
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
decrRefCount(fctx->argv[pos]);
|
||||
fctx->argv[pos] = arg;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Modify the filtered command by deleting an argument at the specified
|
||||
* position.
|
||||
*/
|
||||
int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
{
|
||||
int i;
|
||||
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
decrRefCount(fctx->argv[pos]);
|
||||
for (i = pos; i < fctx->argc-1; i++) {
|
||||
fctx->argv[i] = fctx->argv[i+1];
|
||||
}
|
||||
fctx->argc--;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -5228,9 +4789,6 @@ void moduleInitModulesSystem(void) {
|
||||
moduleFreeContextReusedClient = createClient(-1);
|
||||
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
|
||||
|
||||
/* Set up filter list */
|
||||
moduleCommandFilters = listCreate();
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -5280,7 +4838,6 @@ void moduleLoadFromQueue(void) {
|
||||
|
||||
void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
listRelease(module->types);
|
||||
listRelease(module->filters);
|
||||
sdsfree(module->name);
|
||||
zfree(module);
|
||||
}
|
||||
@@ -5372,7 +4929,6 @@ int moduleUnload(sds name) {
|
||||
moduleUnregisterCommands(module);
|
||||
moduleUnregisterSharedAPI(module);
|
||||
moduleUnregisterUsedAPI(module);
|
||||
moduleUnregisterFilters(module);
|
||||
|
||||
/* Remove any notification subscribers this module might have */
|
||||
moduleUnsubscribeNotifications(module);
|
||||
@@ -5494,7 +5050,6 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ReplySetArrayLength);
|
||||
REGISTER_API(ReplyWithString);
|
||||
REGISTER_API(ReplyWithStringBuffer);
|
||||
REGISTER_API(ReplyWithCString);
|
||||
REGISTER_API(ReplyWithNull);
|
||||
REGISTER_API(ReplyWithCallReply);
|
||||
REGISTER_API(ReplyWithDouble);
|
||||
@@ -5576,7 +5131,6 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(RetainString);
|
||||
REGISTER_API(StringCompare);
|
||||
REGISTER_API(GetContextFromIO);
|
||||
REGISTER_API(GetKeyNameFromIO);
|
||||
REGISTER_API(BlockClient);
|
||||
REGISTER_API(UnblockClient);
|
||||
REGISTER_API(IsBlockedReplyRequest);
|
||||
@@ -5632,11 +5186,4 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(DictCompare);
|
||||
REGISTER_API(ExportSharedAPI);
|
||||
REGISTER_API(GetSharedAPI);
|
||||
REGISTER_API(RegisterCommandFilter);
|
||||
REGISTER_API(UnregisterCommandFilter);
|
||||
REGISTER_API(CommandFilterArgsCount);
|
||||
REGISTER_API(CommandFilterArgGet);
|
||||
REGISTER_API(CommandFilterArgInsert);
|
||||
REGISTER_API(CommandFilterArgReplace);
|
||||
REGISTER_API(CommandFilterArgDelete);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ hellotimer.so: hellotimer.xo
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ void execCommand(client *c) {
|
||||
{
|
||||
addReplyError(c,
|
||||
"Transaction contains write commands but instance "
|
||||
"is now a read-only slave. EXEC aborted.");
|
||||
"is now a read-only replica. EXEC aborted.");
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
+29
-54
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "atomicvar.h"
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
@@ -629,19 +628,6 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
/* Append 'src' client output buffers into 'dst' client output buffers.
|
||||
* This function clears the output buffers of 'src' */
|
||||
void AddReplyFromClient(client *dst, client *src) {
|
||||
if (prepareClientToWrite(dst) != C_OK)
|
||||
return;
|
||||
addReplyString(dst,src->buf, src->bufpos);
|
||||
if (listLength(src->reply))
|
||||
listJoin(dst->reply,src->reply);
|
||||
dst->reply_bytes += src->reply_bytes;
|
||||
src->reply_bytes = 0;
|
||||
src->bufpos = 0;
|
||||
}
|
||||
|
||||
/* Copy 'src' client output buffers into 'dst' client output buffers.
|
||||
* The function takes care of freeing the old output buffers of the
|
||||
* destination client. */
|
||||
@@ -809,16 +795,6 @@ void unlinkClient(client *c) {
|
||||
c->client_list_node = NULL;
|
||||
}
|
||||
|
||||
/* In the case of diskless replication the fork is writing to the
|
||||
* sockets and just closing the fd isn't enough, if we don't also
|
||||
* shutdown the socket the fork will continue to write to the slave
|
||||
* and the salve will only find out that it was disconnected when
|
||||
* it will finish reading the rdb. */
|
||||
if ((c->flags & CLIENT_SLAVE) &&
|
||||
(c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) {
|
||||
shutdown(c->fd, SHUT_RDWR);
|
||||
}
|
||||
|
||||
/* Unregister async I/O handlers and close the socket. */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
@@ -918,7 +894,7 @@ void freeClient(client *c) {
|
||||
/* We need to remember the time when we started to have zero
|
||||
* attached slaves, as after some time we'll free the replication
|
||||
* backlog. */
|
||||
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
|
||||
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
@@ -1030,8 +1006,8 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave or a monitor (otherwise, on high-speed traffic, the
|
||||
* replication/output buffer will grow indefinitely) */
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory) &&
|
||||
@@ -1216,7 +1192,7 @@ int processInlineBuffer(client *c) {
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Move querybuffer position to the next query in the buffer. */
|
||||
@@ -1230,8 +1206,12 @@ int processInlineBuffer(client *c) {
|
||||
|
||||
/* Create redis objects for all arguments. */
|
||||
for (c->argc = 0, j = 0; j < argc; j++) {
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
if (sdslen(argv[j])) {
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
} else {
|
||||
sdsfree(argv[j]);
|
||||
}
|
||||
}
|
||||
zfree(argv);
|
||||
return C_OK;
|
||||
@@ -2017,8 +1997,15 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the number of bytes that Redis is
|
||||
/* This function returns the number of bytes that Redis is virtually
|
||||
* using to store the reply still not read by the client.
|
||||
* It is "virtual" since the reply output list may contain objects that
|
||||
* are shared and are not really using additional memory.
|
||||
*
|
||||
* The function returns the total sum of the length of all the objects
|
||||
* stored in the output list, plus the memory used to allocate every
|
||||
* list node. The static reply buffer is not taken into account since it
|
||||
* is allocated anyway.
|
||||
*
|
||||
* Note: this function is very fast so can be called as many time as
|
||||
* the caller wishes. The main usage of this function currently is
|
||||
@@ -2033,14 +2020,12 @@ unsigned long getClientOutputBufferMemoryUsage(client *c) {
|
||||
*
|
||||
* The function will return one of the following:
|
||||
* CLIENT_TYPE_NORMAL -> Normal client
|
||||
* CLIENT_TYPE_SLAVE -> Slave
|
||||
* CLIENT_TYPE_SLAVE -> Slave or client executing MONITOR command
|
||||
* CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels
|
||||
* CLIENT_TYPE_MASTER -> The client representing our replication master.
|
||||
*/
|
||||
int getClientType(client *c) {
|
||||
if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER;
|
||||
/* Even though MONITOR clients are marked as replicas, we
|
||||
* want the expose them as normal clients. */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR))
|
||||
return CLIENT_TYPE_SLAVE;
|
||||
if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB;
|
||||
@@ -2141,27 +2126,17 @@ void flushSlavesOutputBuffers(void) {
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = listNodeValue(ln);
|
||||
int events = aeGetFileEvents(server.el,slave->fd);
|
||||
int can_receive_writes = (events & AE_WRITABLE) ||
|
||||
(slave->flags & CLIENT_PENDING_WRITE);
|
||||
int events;
|
||||
|
||||
/* We don't want to send the pending data to the replica in a few
|
||||
* cases:
|
||||
*
|
||||
* 1. For some reason there is neither the write handler installed
|
||||
* nor the client is flagged as to have pending writes: for some
|
||||
* reason this replica may not be set to receive data. This is
|
||||
* just for the sake of defensive programming.
|
||||
*
|
||||
* 2. The put_online_on_ack flag is true. To know why we don't want
|
||||
* to send data to the replica in this case, please grep for the
|
||||
* flag for this flag.
|
||||
*
|
||||
* 3. Obviously if the slave is not ONLINE.
|
||||
*/
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
can_receive_writes &&
|
||||
!slave->repl_put_online_on_ack &&
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
* in STATE_ONLINE but having put_online_on_ack set to true: in this
|
||||
* case the writable event is never installed, since the purpose
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(server.el,slave->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
clientHasPendingReplies(slave))
|
||||
{
|
||||
writeToClient(slave->fd,slave,0);
|
||||
|
||||
+17
-36
@@ -415,18 +415,6 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Optimize the SDS string inside the string object to require little space,
|
||||
* in case there is more than 10% of free space at the end of the SDS
|
||||
* string. This happens because SDS strings tend to overallocate to avoid
|
||||
* wasting too much time in allocations when appending to the string. */
|
||||
void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
@@ -467,15 +455,10 @@ robj *tryObjectEncoding(robj *o) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
if (o->encoding == OBJ_ENCODING_RAW) {
|
||||
sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
decrRefCount(o);
|
||||
return createStringObjectFromLongLongForValue(value);
|
||||
}
|
||||
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +484,11 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
trimStringObjectIfNeeded(o);
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
@@ -839,9 +826,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
d = ((zset*)o->ptr)->dict;
|
||||
zskiplist *zsl = ((zset*)o->ptr)->zsl;
|
||||
zskiplistNode *znode = zsl->header->level[0].forward;
|
||||
asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
|
||||
(sizeof(struct dictEntry*)*dictSlots(d))+
|
||||
zmalloc_size(zsl->header);
|
||||
asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
|
||||
while(znode != NULL && samples < sample_size) {
|
||||
elesize += sdsAllocSize(znode->ele);
|
||||
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
|
||||
@@ -1206,7 +1191,7 @@ sds getMemoryDoctorReport(void) {
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
@@ -1217,20 +1202,16 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Provided LRU idle time is in seconds. Scale
|
||||
/* Serialized LRU idle time is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||
/* If the LRU field underflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide a large enough LRU
|
||||
* that is half-way in the circlular LRU clock we use: this way
|
||||
* the computed idle time for this object will stay high for quite
|
||||
* some time. */
|
||||
if (lru_abs < 0)
|
||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||
val->lru = lru_abs;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maximum
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1406,7 +1406,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) {
|
||||
|
||||
@@ -751,7 +751,7 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
||||
|
||||
/* Save a Redis object.
|
||||
* Returns -1 on error, number of bytes written on success. */
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
ssize_t n = 0, nwritten = 0;
|
||||
|
||||
if (o->type == OBJ_STRING) {
|
||||
@@ -966,6 +966,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
@@ -974,13 +975,10 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
mt->rdb_save(&io,mv->value);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -998,7 +996,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
ssize_t len = rdbSaveObject(NULL,o);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@@ -1040,7 +1038,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveObjectType(rdb,val) == -1) return -1;
|
||||
if (rdbSaveStringObject(rdb,key) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val,key) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1093,45 +1091,6 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
||||
/* Save a module-specific aux value. */
|
||||
RedisModuleIO io;
|
||||
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
retval = rdbSaveLen(rdb,mt->id);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* write the 'when' so that we can provide it on loading. add a UINT opcode
|
||||
* for backwards compatibility, everything after the MT needs to be prefixed
|
||||
* by an opcode. */
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_UINT);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
retval = rdbSaveLen(rdb,when);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
mt->aux_save(&io,when);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
if (io.error)
|
||||
return -1;
|
||||
return io.bytes;
|
||||
}
|
||||
|
||||
/* Produces a dump of the database in RDB format sending it to the specified
|
||||
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
|
||||
* is returned and part of the output, or all the output, can be
|
||||
@@ -1153,7 +1112,6 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
@@ -1215,8 +1173,6 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
}
|
||||
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@@ -1424,7 +1380,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -1811,7 +1767,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@@ -1881,7 +1837,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime(0);
|
||||
updateCachedTime();
|
||||
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
@@ -2018,14 +1974,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
decrRefCount(auxval);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||
/* Load module data that is not related to the Redis key space.
|
||||
* Such data can be potentially be stored both before and after the
|
||||
* RDB keys-values section. */
|
||||
/* This is just for compatibility with the future: we have plans
|
||||
* to add the ability for modules to store anything in the RDB
|
||||
* file, like data that is not related to the Redis key space.
|
||||
* Such data will potentially be stored both before and after the
|
||||
* RDB keys-values section. For this reason since RDB version 9,
|
||||
* we have the ability to read a MODULE_AUX opcode followed by an
|
||||
* identifier of the module, and a serialized value in "MODULE V2"
|
||||
* format. */
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
int when_opcode = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT)
|
||||
rdbExitReportCorruptRDB("bad when_opcode");
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@@ -2035,44 +1992,21 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
|
||||
exit(1);
|
||||
} else if (!rdbCheckMode && mt != NULL) {
|
||||
if (!mt->aux_load) {
|
||||
/* Module doesn't support AUX. */
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
}
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
}
|
||||
continue;
|
||||
/* This version of Redis actually does not know what to do
|
||||
* with modules AUX data... */
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
|
||||
exit(1);
|
||||
} else {
|
||||
/* RDB check mode. */
|
||||
robj *aux = rdbLoadCheckModuleValue(rdb,name);
|
||||
decrRefCount(aux);
|
||||
continue; /* Read next opcode. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
|
||||
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
|
||||
|
||||
@@ -140,12 +140,11 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
sprintf(__buf, __VA_ARGS__); \
|
||||
sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1044];
|
||||
static char error[1024];
|
||||
static off_t epos;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
|
||||
@@ -285,7 +285,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+96
-165
@@ -68,7 +68,6 @@
|
||||
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
|
||||
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
|
||||
#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
|
||||
#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"
|
||||
|
||||
#define CLUSTER_MANAGER_SLOTS 16384
|
||||
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
|
||||
@@ -211,8 +210,6 @@ static struct config {
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int memkeys;
|
||||
unsigned memkeys_samples;
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
@@ -1298,12 +1295,6 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.pipe_timeout = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--bigkeys")) {
|
||||
config.bigkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--memkeys")) {
|
||||
config.memkeys = 1;
|
||||
config.memkeys_samples = 0; /* use redis default */
|
||||
} else if (!strcmp(argv[i],"--memkeys-samples")) {
|
||||
config.memkeys = 1;
|
||||
config.memkeys_samples = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--hotkeys")) {
|
||||
config.hotkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
|
||||
@@ -1439,11 +1430,6 @@ static void parseEnv() {
|
||||
if (auth != NULL && config.auth == NULL) {
|
||||
config.auth = auth;
|
||||
}
|
||||
|
||||
char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV);
|
||||
if (cluster_yes != NULL && !strcmp(cluster_yes, "1")) {
|
||||
config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_YES;
|
||||
}
|
||||
}
|
||||
|
||||
static sds readArgFromStdin(void) {
|
||||
@@ -1507,10 +1493,7 @@ static void usage(void) {
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
||||
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
|
||||
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
|
||||
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
|
||||
" And define number of key elements to sample\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys.\n"
|
||||
" --hotkeys Sample Redis keys looking for hot keys.\n"
|
||||
" only works when maxmemory-policy is *lfu.\n"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
@@ -1555,9 +1538,6 @@ static void usage(void) {
|
||||
}
|
||||
|
||||
static int confirmWithYes(char *msg) {
|
||||
if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES) {
|
||||
return 1;
|
||||
}
|
||||
printf("%s (type 'yes' to accept): ", msg);
|
||||
fflush(stdout);
|
||||
char buf[4];
|
||||
@@ -1913,6 +1893,7 @@ typedef struct clusterManagerNode {
|
||||
int flags;
|
||||
list *flags_str; /* Flags string representations */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
list replicas;
|
||||
int dirty; /* Node has changes that can be flushed */
|
||||
uint8_t slots[CLUSTER_MANAGER_SLOTS];
|
||||
int slots_count;
|
||||
@@ -2443,18 +2424,21 @@ static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
|
||||
clusterManagerNode *node = node_array->nodes[j];
|
||||
if (node == NULL) continue;
|
||||
if (!ip) ip = node->ip;
|
||||
sds types;
|
||||
/* We always use the Master ID as key. */
|
||||
sds types, otypes;
|
||||
// We always use the Master ID as key
|
||||
sds key = (!node->replicate ? node->name : node->replicate);
|
||||
assert(key != NULL);
|
||||
dictEntry *entry = dictFind(related, key);
|
||||
if (entry) types = sdsdup((sds) dictGetVal(entry));
|
||||
else types = sdsempty();
|
||||
/* Master type 'm' is always set as the first character of the
|
||||
* types string. */
|
||||
if (!node->replicate) types = sdscatprintf(types, "m%s", types);
|
||||
else types = sdscat(types, "s");
|
||||
dictReplace(related, key, types);
|
||||
if (entry) otypes = (sds) dictGetVal(entry);
|
||||
else {
|
||||
otypes = sdsempty();
|
||||
dictAdd(related, key, otypes);
|
||||
}
|
||||
// Master type 'm' is always set as the first character of the
|
||||
// types string.
|
||||
if (!node->replicate) types = sdscatprintf(otypes, "m%s", otypes);
|
||||
else types = sdscat(otypes, "s");
|
||||
if (types != otypes) dictReplace(related, key, types);
|
||||
}
|
||||
/* Now it's trivial to check, for each related group having the
|
||||
* same host, what is their local score. */
|
||||
@@ -3658,7 +3642,7 @@ static clusterManagerNode *clusterManagerNodeWithLeastReplicas() {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* This fucntion returns a random master node, return NULL if none */
|
||||
/* This function returns a random master node, return NULL if none */
|
||||
|
||||
static clusterManagerNode *clusterManagerNodeMasterRandom() {
|
||||
int master_count = 0;
|
||||
@@ -4695,12 +4679,6 @@ static int clusterManagerCommandCreate(int argc, char **argv) {
|
||||
cursor += slots_per_node;
|
||||
}
|
||||
|
||||
/* Rotating the list sometimes helps to get better initial
|
||||
* anti-affinity before the optimizer runs. */
|
||||
clusterManagerNode *first_node = interleaved[0];
|
||||
for (i = 0; i < (interleaved_len - 1); i++)
|
||||
interleaved[i] = interleaved[i + 1];
|
||||
interleaved[interleaved_len - 1] = first_node;
|
||||
int assign_unused = 0, available_count = interleaved_len;
|
||||
assign_replicas:
|
||||
for (i = 0; i < masters_count; i++) {
|
||||
@@ -6073,7 +6051,6 @@ static void pipeMode(void) {
|
||||
/* Handle the readable state: we can read replies from the server. */
|
||||
if (mask & AE_READABLE) {
|
||||
ssize_t nread;
|
||||
int read_error = 0;
|
||||
|
||||
/* Read from socket and feed the hiredis reader. */
|
||||
do {
|
||||
@@ -6081,8 +6058,7 @@ static void pipeMode(void) {
|
||||
if (nread == -1 && errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "Error reading from the server: %s\n",
|
||||
strerror(errno));
|
||||
read_error = 1;
|
||||
break;
|
||||
exit(1);
|
||||
}
|
||||
if (nread > 0) {
|
||||
redisReaderFeed(reader,ibuf,nread);
|
||||
@@ -6115,11 +6091,6 @@ static void pipeMode(void) {
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
} while(reply);
|
||||
|
||||
/* Abort on read errors. We abort here because it is important
|
||||
* to consume replies even after a read error: this way we can
|
||||
* show a potential problem to the user. */
|
||||
if (read_error) exit(1);
|
||||
}
|
||||
|
||||
/* Handle the writable state: we can send protocol to the server. */
|
||||
@@ -6207,6 +6178,15 @@ static void pipeMode(void) {
|
||||
* Find big keys
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define TYPE_STRING 0
|
||||
#define TYPE_LIST 1
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_STREAM 5
|
||||
#define TYPE_NONE 6
|
||||
#define TYPE_COUNT 7
|
||||
|
||||
static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
|
||||
@@ -6253,51 +6233,28 @@ static int getDbSize(void) {
|
||||
return size;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *sizecmd;
|
||||
char *sizeunit;
|
||||
unsigned long long biggest;
|
||||
unsigned long long count;
|
||||
unsigned long long totalsize;
|
||||
sds biggest_key;
|
||||
} typeinfo;
|
||||
|
||||
typeinfo type_string = { "string", "STRLEN", "bytes" };
|
||||
typeinfo type_list = { "list", "LLEN", "items" };
|
||||
typeinfo type_set = { "set", "SCARD", "members" };
|
||||
typeinfo type_hash = { "hash", "HLEN", "fields" };
|
||||
typeinfo type_zset = { "zset", "ZCARD", "members" };
|
||||
typeinfo type_stream = { "stream", "XLEN", "entries" };
|
||||
typeinfo type_other = { "other", NULL, "?" };
|
||||
|
||||
static typeinfo* typeinfo_add(dict *types, char* name, typeinfo* type_template) {
|
||||
typeinfo *info = zmalloc(sizeof(typeinfo));
|
||||
*info = *type_template;
|
||||
info->name = sdsnew(name);
|
||||
dictAdd(types, info->name, info);
|
||||
return info;
|
||||
static int toIntType(char *key, char *type) {
|
||||
if(!strcmp(type, "string")) {
|
||||
return TYPE_STRING;
|
||||
} else if(!strcmp(type, "list")) {
|
||||
return TYPE_LIST;
|
||||
} else if(!strcmp(type, "set")) {
|
||||
return TYPE_SET;
|
||||
} else if(!strcmp(type, "hash")) {
|
||||
return TYPE_HASH;
|
||||
} else if(!strcmp(type, "zset")) {
|
||||
return TYPE_ZSET;
|
||||
} else if(!strcmp(type, "stream")) {
|
||||
return TYPE_STREAM;
|
||||
} else if(!strcmp(type, "none")) {
|
||||
return TYPE_NONE;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void type_free(void* priv_data, void* val) {
|
||||
typeinfo *info = val;
|
||||
UNUSED(priv_data);
|
||||
if (info->biggest_key)
|
||||
sdsfree(info->biggest_key);
|
||||
sdsfree(info->name);
|
||||
zfree(info);
|
||||
}
|
||||
|
||||
static dictType typeinfoDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
NULL, /* key destructor (owned by the value)*/
|
||||
type_free /* val destructor */
|
||||
};
|
||||
|
||||
static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
unsigned int i;
|
||||
|
||||
@@ -6323,47 +6280,32 @@ static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sds typereply = sdsnew(reply->str);
|
||||
dictEntry *de = dictFind(types_dict, typereply);
|
||||
sdsfree(typereply);
|
||||
typeinfo *type = NULL;
|
||||
if (de)
|
||||
type = dictGetVal(de);
|
||||
else if (strcmp(reply->str, "none")) /* create new types for modules, (but not for deleted keys) */
|
||||
type = typeinfo_add(types_dict, reply->str, &type_other);
|
||||
types[i] = type;
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeySizes(redisReply *keys, typeinfo **types,
|
||||
unsigned long long *sizes, int memkeys,
|
||||
unsigned memkeys_samples)
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
|
||||
if(!types[i] || (!types[i]->sizecmd && !memkeys))
|
||||
/* Skip keys that were deleted */
|
||||
if(types[i]==TYPE_NONE)
|
||||
continue;
|
||||
|
||||
if (!memkeys)
|
||||
redisAppendCommand(context, "%s %s",
|
||||
types[i]->sizecmd, keys->element[i]->str);
|
||||
else if (memkeys_samples==0)
|
||||
redisAppendCommand(context, "%s %s %s",
|
||||
"MEMORY", "USAGE", keys->element[i]->str);
|
||||
else
|
||||
redisAppendCommand(context, "%s %s %s SAMPLES %u",
|
||||
"MEMORY", "USAGE", keys->element[i]->str, memkeys_samples);
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve sizes */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
|
||||
if(!types[i] || (!types[i]->sizecmd && !memkeys)) {
|
||||
/* Skip keys that disappeared between SCAN and TYPE */
|
||||
if(types[i] == TYPE_NONE) {
|
||||
sizes[i] = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -6378,8 +6320,7 @@ static void getKeySizes(redisReply *keys, typeinfo **types,
|
||||
* added as a different type between TYPE and SIZE */
|
||||
fprintf(stderr,
|
||||
"Warning: %s on '%s' failed (may have changed type)\n",
|
||||
!memkeys? types[i]->sizecmd: "MEMORY USAGE",
|
||||
keys->element[i]->str);
|
||||
sizecmds[types[i]], keys->element[i]->str);
|
||||
sizes[i] = 0;
|
||||
} else {
|
||||
sizes[i] = reply->integer;
|
||||
@@ -6389,23 +6330,17 @@ static void getKeySizes(redisReply *keys, typeinfo **types,
|
||||
}
|
||||
}
|
||||
|
||||
static void findBigKeys(int memkeys, unsigned memkeys_samples) {
|
||||
static void findBigKeys(void) {
|
||||
unsigned long long biggest[TYPE_COUNT] = {0}, counts[TYPE_COUNT] = {0}, totalsize[TYPE_COUNT] = {0};
|
||||
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
|
||||
sds maxkeys[TYPE_COUNT] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset","stream","none"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members","entries",""};
|
||||
redisReply *reply, *keys;
|
||||
unsigned int arrsize=0, i;
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
typeinfo **types = NULL;
|
||||
int type, *types=NULL;
|
||||
double pct;
|
||||
|
||||
dict *types_dict = dictCreate(&typeinfoDictType, NULL);
|
||||
typeinfo_add(types_dict, "string", &type_string);
|
||||
typeinfo_add(types_dict, "list", &type_list);
|
||||
typeinfo_add(types_dict, "set", &type_set);
|
||||
typeinfo_add(types_dict, "hash", &type_hash);
|
||||
typeinfo_add(types_dict, "zset", &type_zset);
|
||||
typeinfo_add(types_dict, "stream", &type_stream);
|
||||
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
@@ -6414,6 +6349,15 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* New up sds strings to keep track of overall biggest per type */
|
||||
for(i=0;i<TYPE_NONE; i++) {
|
||||
maxkeys[i] = sdsempty();
|
||||
if(!maxkeys[i]) {
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
@@ -6425,7 +6369,7 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
|
||||
|
||||
/* Reallocate our type and size array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
types = zrealloc(types, sizeof(typeinfo*)*keys->elements);
|
||||
types = zrealloc(types, sizeof(int)*keys->elements);
|
||||
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
|
||||
|
||||
if(!types || !sizes) {
|
||||
@@ -6437,38 +6381,34 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
|
||||
}
|
||||
|
||||
/* Retrieve types and then sizes */
|
||||
getKeyTypes(types_dict, keys, types);
|
||||
getKeySizes(keys, types, sizes, memkeys, memkeys_samples);
|
||||
getKeyTypes(keys, types);
|
||||
getKeySizes(keys, types, sizes);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
typeinfo *type = types[i];
|
||||
/* Skip keys that disappeared between SCAN and TYPE */
|
||||
if(!type)
|
||||
if((type = types[i]) == TYPE_NONE)
|
||||
continue;
|
||||
|
||||
type->totalsize += sizes[i];
|
||||
type->count++;
|
||||
totalsize[type] += sizes[i];
|
||||
counts[type]++;
|
||||
totlen += keys->element[i]->len;
|
||||
sampled++;
|
||||
|
||||
if(type->biggest<sizes[i]) {
|
||||
if(biggest[type]<sizes[i]) {
|
||||
printf(
|
||||
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
|
||||
pct, type->name, keys->element[i]->str, sizes[i],
|
||||
!memkeys? type->sizeunit: "bytes");
|
||||
pct, typename[type], keys->element[i]->str, sizes[i],
|
||||
typeunit[type]);
|
||||
|
||||
/* Keep track of biggest key name for this type */
|
||||
if (type->biggest_key)
|
||||
sdsfree(type->biggest_key);
|
||||
type->biggest_key = sdsnew(keys->element[i]->str);
|
||||
if(!type->biggest_key) {
|
||||
maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
|
||||
if(!maxkeys[type]) {
|
||||
fprintf(stderr, "Failed to allocate memory for key!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Keep track of the biggest size for this type */
|
||||
type->biggest = sizes[i];
|
||||
biggest[type] = sizes[i];
|
||||
}
|
||||
|
||||
/* Update overall progress */
|
||||
@@ -6496,29 +6436,26 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
|
||||
totlen, totlen ? (double)totlen/sampled : 0);
|
||||
|
||||
/* Output the biggest keys we found, for types we did find */
|
||||
di = dictGetIterator(types_dict);
|
||||
while ((de = dictNext(di))) {
|
||||
typeinfo *type = dictGetVal(de);
|
||||
if(type->biggest_key) {
|
||||
printf("Biggest %6s found '%s' has %llu %s\n", type->name, type->biggest_key,
|
||||
type->biggest, !memkeys? type->sizeunit: "bytes");
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
if(sdslen(maxkeys[i])>0) {
|
||||
printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
|
||||
biggest[i], typeunit[i]);
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
printf("\n");
|
||||
|
||||
di = dictGetIterator(types_dict);
|
||||
while ((de = dictNext(di))) {
|
||||
typeinfo *type = dictGetVal(de);
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
|
||||
type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",
|
||||
sampled ? 100 * (double)type->count/sampled : 0,
|
||||
type->count ? (double)type->totalsize/type->count : 0);
|
||||
counts[i], typename[i], totalsize[i], typeunit[i],
|
||||
sampled ? 100 * (double)counts[i]/sampled : 0,
|
||||
counts[i] ? (double)totalsize[i]/counts[i] : 0);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
dictRelease(types_dict);
|
||||
/* Free sds strings containing max keys */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
sdsfree(maxkeys[i]);
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
exit(0);
|
||||
@@ -7110,13 +7047,7 @@ int main(int argc, char **argv) {
|
||||
/* Find big keys */
|
||||
if (config.bigkeys) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
findBigKeys(0, 0);
|
||||
}
|
||||
|
||||
/* Find large keys */
|
||||
if (config.memkeys) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
findBigKeys(1, config.memkeys_samples);
|
||||
findBigKeys();
|
||||
}
|
||||
|
||||
/* Find hot keys */
|
||||
|
||||
+1
-56
@@ -85,26 +85,6 @@
|
||||
#define REDISMODULE_CTX_FLAGS_OOM (1<<10)
|
||||
/* Less than 25% of memory available according to maxmemory. */
|
||||
#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
|
||||
/* The command was sent over the replication link. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
|
||||
/* Redis is currently loading either from AOF or RDB. */
|
||||
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
|
||||
/* The replica has no link with its master, note that
|
||||
* there is the inverse flag as well:
|
||||
*
|
||||
* REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE
|
||||
*
|
||||
* The two flags are exclusive, one or the other can be set. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14)
|
||||
/* The replica is trying to connect with the master.
|
||||
* (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15)
|
||||
/* THe replica is receiving an RDB file from its master. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16)
|
||||
/* The replica is online, receiving updates from its master. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
|
||||
/* There is currently some background process active. */
|
||||
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
@@ -143,21 +123,12 @@
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
/* This type represents a timer handle, and is returned when a timer is
|
||||
* registered and used in order to invalidate a timer. It's just a 64 bit
|
||||
* number, because this is how each timer is represented inside the radix tree
|
||||
* of timers that are going to expire, sorted by expire time. */
|
||||
typedef uint64_t RedisModuleTimerID;
|
||||
|
||||
/* CommandFilter Flags */
|
||||
|
||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@@ -176,25 +147,20 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
|
||||
typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
uint64_t version;
|
||||
RedisModuleTypeLoadFunc rdb_load;
|
||||
@@ -203,9 +169,6 @@ typedef struct RedisModuleTypeMethods {
|
||||
RedisModuleTypeMemUsageFunc mem_usage;
|
||||
RedisModuleTypeDigestFunc digest;
|
||||
RedisModuleTypeFreeFunc free;
|
||||
RedisModuleTypeAuxLoadFunc aux_load;
|
||||
RedisModuleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
} RedisModuleTypeMethods;
|
||||
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
@@ -251,7 +214,6 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
@@ -313,7 +275,6 @@ int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Re
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||
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);
|
||||
@@ -373,13 +334,6 @@ void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedC
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
|
||||
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@@ -402,7 +356,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ReplyWithArray);
|
||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||
REDISMODULE_GET_API(ReplyWithCString);
|
||||
REDISMODULE_GET_API(ReplyWithString);
|
||||
REDISMODULE_GET_API(ReplyWithNull);
|
||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||
@@ -486,7 +439,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
@@ -544,13 +496,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(SetClusterFlags);
|
||||
REDISMODULE_GET_API(ExportSharedAPI);
|
||||
REDISMODULE_GET_API(GetSharedAPI);
|
||||
REDISMODULE_GET_API(RegisterCommandFilter);
|
||||
REDISMODULE_GET_API(UnregisterCommandFilter);
|
||||
REDISMODULE_GET_API(CommandFilterArgsCount);
|
||||
REDISMODULE_GET_API(CommandFilterArgGet);
|
||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
||||
+22
-80
@@ -30,7 +30,6 @@
|
||||
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
@@ -594,7 +593,6 @@ int startBgsaveForReplication(int mincapa) {
|
||||
client *slave = ln->value;
|
||||
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
slave->replstate = REPL_STATE_NONE;
|
||||
slave->flags &= ~CLIENT_SLAVE;
|
||||
listDelNode(server.slaves,ln);
|
||||
addReplyError(slave,
|
||||
@@ -823,9 +821,7 @@ void replconfCommand(client *c) {
|
||||
c->repl_ack_time = server.unixtime;
|
||||
/* If this was a diskless replication, we need to really put
|
||||
* the slave online when the first ACK is received (which
|
||||
* confirms slave is online and ready to get more data). This
|
||||
* allows for simpler and less CPU intensive EOF detection
|
||||
* when streaming RDB files. */
|
||||
* confirms slave is online and ready to get more data). */
|
||||
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
|
||||
putSlaveOnline(c);
|
||||
/* Note: this command does not reply anything! */
|
||||
@@ -844,20 +840,18 @@ void replconfCommand(client *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* This function puts a replica in the online state, and should be called just
|
||||
* after a replica received the RDB file for the initial synchronization, and
|
||||
/* This function puts a slave in the online state, and should be called just
|
||||
* after a slave received the RDB file for the initial synchronization, and
|
||||
* we are finally ready to send the incremental stream of commands.
|
||||
*
|
||||
* It does a few things:
|
||||
*
|
||||
* 1) Put the slave in ONLINE state. Note that the function may also be called
|
||||
* for a replicas that are already in ONLINE state, but having the flag
|
||||
* repl_put_online_on_ack set to true: we still have to install the write
|
||||
* handler in that case. This function will take care of that.
|
||||
* 1) Put the slave in ONLINE state (useless when the function is called
|
||||
* because state is already ONLINE but repl_put_online_on_ack is true).
|
||||
* 2) Make sure the writable event is re-installed, since calling the SYNC
|
||||
* command disables it, so that we can accumulate output buffer without
|
||||
* sending it to the replica.
|
||||
* 3) Update the count of "good replicas". */
|
||||
* sending it to the slave.
|
||||
* 3) Update the count of good slaves. */
|
||||
void putSlaveOnline(client *slave) {
|
||||
slave->replstate = SLAVE_STATE_ONLINE;
|
||||
slave->repl_put_online_on_ack = 0;
|
||||
@@ -969,31 +963,11 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
serverLog(LL_NOTICE,
|
||||
"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 the replica in
|
||||
/* Note: we wait for a REPLCONF ACK message from slave in
|
||||
* order to really put it online (install the write handler
|
||||
* so that the accumulated data can be transferred). However
|
||||
* we change the replication state ASAP, since our slave
|
||||
* is technically online now.
|
||||
*
|
||||
* So things work like that:
|
||||
*
|
||||
* 1. We end trasnferring the RDB file via socket.
|
||||
* 2. The replica is put ONLINE but the write handler
|
||||
* is not installed.
|
||||
* 3. The replica however goes really online, and pings us
|
||||
* back via REPLCONF ACK commands.
|
||||
* 4. Now we finally install the write handler, and send
|
||||
* the buffers accumulated so far to the replica.
|
||||
*
|
||||
* But why we do that? Because the replica, when we stream
|
||||
* the RDB directly via the socket, must detect the RDB
|
||||
* EOF (end of file), that is a special random string at the
|
||||
* end of the RDB (for streamed RDBs we don't know the length
|
||||
* in advance). Detecting such final EOF string is much
|
||||
* simpler and less CPU intensive if no more data is sent
|
||||
* after such final EOF. So we don't want to glue the end of
|
||||
* the RDB trasfer with the start of the other replication
|
||||
* data. */
|
||||
* is technically online now. */
|
||||
slave->replstate = SLAVE_STATE_ONLINE;
|
||||
slave->repl_put_online_on_ack = 1;
|
||||
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
|
||||
@@ -1115,23 +1089,14 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
if (dbid != -1) selectDb(server.master,dbid);
|
||||
}
|
||||
|
||||
/* This function will try to re-enable the AOF file after the
|
||||
* master-replica synchronization: if it fails after multiple attempts
|
||||
* the replica cannot be considered reliable and exists with an
|
||||
* error. */
|
||||
void restartAOFAfterSYNC() {
|
||||
unsigned int tries, max_tries = 10;
|
||||
for (tries = 0; tries < max_tries; ++tries) {
|
||||
if (startAppendOnly() == C_OK) break;
|
||||
serverLog(LL_WARNING,
|
||||
"Failed enabling the AOF after successful master synchronization! "
|
||||
"Trying it again in one second.");
|
||||
void restartAOF() {
|
||||
int retry = 10;
|
||||
while (retry-- && startAppendOnly() == C_ERR) {
|
||||
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (tries == max_tries) {
|
||||
serverLog(LL_WARNING,
|
||||
"FATAL: this replica instance finished the synchronization with "
|
||||
"its master, but the AOF can't be turned on. Exiting now.");
|
||||
if (!retry) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1318,7 +1283,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
cancelReplicationHandshake();
|
||||
/* Re-enable the AOF if we disabled it earlier, in order to restore
|
||||
* the original configuration. */
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
if (aof_is_enabled) restartAOF();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
@@ -1343,7 +1308,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
/* 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. */
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
if (aof_is_enabled) restartAOF();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -1996,10 +1961,7 @@ void replicationSetMaster(char *ip, int port) {
|
||||
cancelReplicationHandshake();
|
||||
/* Before destroying our master state, create a cached master using
|
||||
* our own parameters, to later PSYNC with the new master. */
|
||||
if (was_master) {
|
||||
replicationDiscardCachedMaster();
|
||||
replicationCacheMasterUsingMyself();
|
||||
}
|
||||
if (was_master) replicationCacheMasterUsingMyself();
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
}
|
||||
|
||||
@@ -2069,15 +2031,6 @@ void replicaofCommand(client *c) {
|
||||
} else {
|
||||
long port;
|
||||
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
{
|
||||
/* If a client is already a replica they cannot run this command,
|
||||
* because it involves flushing all replicas (including this
|
||||
* client) */
|
||||
addReplyError(c, "Command is not valid when client is a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -2621,21 +2574,10 @@ void replicationCron(void) {
|
||||
if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&
|
||||
listLength(server.slaves))
|
||||
{
|
||||
/* Note that we don't send the PING if the clients are paused during
|
||||
* a Redis Cluster manual failover: the PING we send will otherwise
|
||||
* alter the replication offsets of master and slave, and will no longer
|
||||
* match the one stored into 'mf_master_offset' state. */
|
||||
int manual_failover_in_progress =
|
||||
server.cluster_enabled &&
|
||||
server.cluster->mf_end &&
|
||||
clientsArePaused();
|
||||
|
||||
if (!manual_failover_in_progress) {
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
}
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
}
|
||||
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
|
||||
@@ -443,11 +443,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
|
||||
/* Process module hooks */
|
||||
moduleCallCommandFilters(c);
|
||||
argv = c->argv;
|
||||
argc = c->argc;
|
||||
|
||||
/* Log the command if debugging is active. */
|
||||
if (ldb.active && ldb.step) {
|
||||
sds cmdlog = sdsnew("<redis>");
|
||||
@@ -2213,7 +2208,6 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(code);
|
||||
sdsfree(expr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,12 +257,8 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
extern const char *SDS_NOINIT;
|
||||
const char *SDS_NOINIT;
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
+1
-1
@@ -4270,7 +4270,7 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
int retval;
|
||||
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->flags & (SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->link->disconnected) continue;
|
||||
|
||||
retval = sentinelSendSlaveOf(slave,
|
||||
|
||||
+30
-72
@@ -200,8 +200,8 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-3,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-3,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
@@ -779,11 +779,6 @@ void updateDictResizePolicy(void) {
|
||||
dictDisableResize();
|
||||
}
|
||||
|
||||
int hasActiveChildProcess() {
|
||||
return server.rdb_child_pid != -1 ||
|
||||
server.aof_child_pid != -1;
|
||||
}
|
||||
|
||||
/* ======================= Cron: called every 100 ms ======================== */
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
@@ -821,7 +816,7 @@ int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves and monitors */
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves */
|
||||
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
|
||||
@@ -987,7 +982,7 @@ void clientsCron(void) {
|
||||
/* Rotate the list, take the current head, process.
|
||||
* This way if the client must be removed from the list it's the
|
||||
* first element and we don't incur into O(N) computation. */
|
||||
listRotateTailToHead(server.clients);
|
||||
listRotate(server.clients);
|
||||
head = listFirst(server.clients);
|
||||
c = listNodeValue(head);
|
||||
/* The following functions do different service checks on the client.
|
||||
@@ -1005,12 +1000,10 @@ void clientsCron(void) {
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.active_expire_enabled) {
|
||||
if (server.masterhost == NULL) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else {
|
||||
expireSlaveKeys();
|
||||
}
|
||||
if (server.active_expire_enabled && server.masterhost == NULL) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else if (server.masterhost != NULL) {
|
||||
expireSlaveKeys();
|
||||
}
|
||||
|
||||
/* Defrag keys gradually. */
|
||||
@@ -1059,30 +1052,19 @@ void databasesCron(void) {
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
server.ustime = ustime();
|
||||
server.mstime = server.ustime / 1000;
|
||||
time_t unixtime = server.mstime / 1000;
|
||||
* a lot faster than calling time(NULL) */
|
||||
void updateCachedTime(void) {
|
||||
time_t unixtime = time(NULL);
|
||||
atomicSet(server.unixtime,unixtime);
|
||||
server.mstime = mstime();
|
||||
|
||||
/* To get information about daylight saving time, we need to call
|
||||
* localtime_r and cache the result. However calling localtime_r in this
|
||||
* context is safe since we will never fork() while here, in the main
|
||||
* thread. The logging function will call a thread safe version of
|
||||
* localtime that has no locks. */
|
||||
if (update_daylight_info) {
|
||||
struct tm tm;
|
||||
time_t ut = server.unixtime;
|
||||
localtime_r(&ut,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
}
|
||||
/* To get information about daylight saving time, we need to call localtime_r
|
||||
* and cache the result. However calling localtime_r in this context is safe
|
||||
* since we will never fork() while here, in the main thread. The logging
|
||||
* function will call a thread safe version of localtime that has no locks. */
|
||||
struct tm tm;
|
||||
localtime_r(&server.unixtime,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
@@ -1115,7 +1097,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||
|
||||
/* Update the time cache. */
|
||||
updateCachedTime(1);
|
||||
updateCachedTime();
|
||||
|
||||
server.hz = server.config_hz;
|
||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||
@@ -1539,7 +1521,7 @@ void initServerConfig(void) {
|
||||
pthread_mutex_init(&server.lruclock_mutex,NULL);
|
||||
pthread_mutex_init(&server.unixtime_mutex,NULL);
|
||||
|
||||
updateCachedTime(1);
|
||||
updateCachedTime();
|
||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||
changeReplicationId();
|
||||
@@ -2043,7 +2025,6 @@ void initServer(void) {
|
||||
server.hz = server.config_hz;
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.fixed_time_expire = 0;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -2196,14 +2177,6 @@ void initServer(void) {
|
||||
scriptingInit(1);
|
||||
slowlogInit();
|
||||
latencyMonitorInit();
|
||||
}
|
||||
|
||||
/* Some steps in server initialization need to be done last (after modules
|
||||
* are loaded).
|
||||
* Specifically, creation of threads due to a race bug in ld.so, in which
|
||||
* Thread Local Storage initialization collides with dlopen call.
|
||||
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
|
||||
void InitServerLast() {
|
||||
bioInit();
|
||||
server.initial_memory_usage = zmalloc_used_memory();
|
||||
}
|
||||
@@ -2336,13 +2309,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
|
||||
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
|
||||
* + PROPAGATE_REPL (propagate into the replication link)
|
||||
*
|
||||
* This should not be used inside commands implementation since it will not
|
||||
* wrap the resulting commands in MULTI/EXEC. Use instead alsoPropagate(),
|
||||
* preventCommandPropagation(), forceCommandPropagation().
|
||||
*
|
||||
* However for functions that need to (also) propagate out of the context of a
|
||||
* command execution, for example when serving a blocked client, you
|
||||
* want to use propagate().
|
||||
* This should not be used inside commands implementation. Use instead
|
||||
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
|
||||
*/
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
||||
int flags)
|
||||
@@ -2444,13 +2412,10 @@ void preventCommandReplication(client *c) {
|
||||
*
|
||||
*/
|
||||
void call(client *c, int flags) {
|
||||
long long dirty;
|
||||
ustime_t start, duration;
|
||||
long long dirty, start, duration;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
|
||||
server.fixed_time_expire++;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
* not generated from reading an AOF. */
|
||||
if (listLength(server.monitors) &&
|
||||
@@ -2468,8 +2433,7 @@ void call(client *c, int flags) {
|
||||
|
||||
/* Call the command. */
|
||||
dirty = server.dirty;
|
||||
updateCachedTime(0);
|
||||
start = server.ustime;
|
||||
start = ustime();
|
||||
c->cmd->proc(c);
|
||||
duration = ustime()-start;
|
||||
dirty = server.dirty-dirty;
|
||||
@@ -2565,7 +2529,6 @@ void call(client *c, int flags) {
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
}
|
||||
server.also_propagate = prev_also_propagate;
|
||||
server.fixed_time_expire--;
|
||||
server.stat_numcommands++;
|
||||
}
|
||||
|
||||
@@ -2578,8 +2541,6 @@ void call(client *c, int flags) {
|
||||
* other operations can be performed by the caller. Otherwise
|
||||
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
|
||||
int processCommand(client *c) {
|
||||
moduleCallCommandFilters(c);
|
||||
|
||||
/* The QUIT command is handled separately. Normal command procs will
|
||||
* go through checking for replication and QUIT will cause trouble
|
||||
* when FORCE_REPLICATION is enabled and would be implemented in
|
||||
@@ -3842,7 +3803,6 @@ static void sigShutdownHandler(int sig) {
|
||||
rdbRemoveTempFile(getpid());
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (server.loading) {
|
||||
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -3899,14 +3859,12 @@ void loadDataFromDisk(void) {
|
||||
(float)(ustime()-start)/1000000);
|
||||
|
||||
/* Restore the replication ID / offset from the RDB file. */
|
||||
if ((server.masterhost ||
|
||||
(server.cluster_enabled &&
|
||||
nodeIsSlave(server.cluster->myself))) &&
|
||||
if (server.masterhost &&
|
||||
rsi.repl_id_is_set &&
|
||||
rsi.repl_offset != -1 &&
|
||||
/* Note that older implementations may save a repl_stream_db
|
||||
* of -1 inside the RDB file in a wrong way, see more
|
||||
* information in function rdbPopulateSaveInfo. */
|
||||
* of -1 inside the RDB file in a wrong way, see more information
|
||||
* in function rdbPopulateSaveInfo. */
|
||||
rsi.repl_stream_db != -1)
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
@@ -4060,6 +4018,8 @@ int main(int argc, char **argv) {
|
||||
return sha1Test(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "util")) {
|
||||
return utilTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "sds")) {
|
||||
return sdsTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "endianconv")) {
|
||||
return endianconvTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "crc64")) {
|
||||
@@ -4210,7 +4170,6 @@ int main(int argc, char **argv) {
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
InitServerLast();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@@ -4225,7 +4184,6 @@ int main(int argc, char **argv) {
|
||||
if (server.sofd > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||
} else {
|
||||
InitServerLast();
|
||||
sentinelIsRunning();
|
||||
}
|
||||
|
||||
|
||||
+6
-27
@@ -50,7 +50,6 @@
|
||||
#include <signal.h>
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
typedef long long ustime_t; /* microsecond time type. */
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
@@ -490,10 +489,6 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
|
||||
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
|
||||
|
||||
/* Bit flags for moduleTypeAuxSaveFunc */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
struct RedisModule;
|
||||
struct RedisModuleIO;
|
||||
struct RedisModuleDigest;
|
||||
@@ -506,8 +501,6 @@ struct redisObject;
|
||||
* is deleted. */
|
||||
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
||||
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
||||
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
|
||||
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
||||
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
@@ -524,9 +517,6 @@ typedef struct RedisModuleType {
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
moduleTypeAuxLoadFunc aux_load;
|
||||
moduleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
||||
} moduleType;
|
||||
|
||||
@@ -561,18 +551,16 @@ typedef struct RedisModuleIO {
|
||||
int ver; /* Module serialization version: 1 (old),
|
||||
* 2 (current version with opcodes annotation). */
|
||||
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
||||
struct redisObject *key; /* Optional name of key processed */
|
||||
} RedisModuleIO;
|
||||
|
||||
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
||||
* inside rdb.c according to the version of the value to load. */
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr) do { \
|
||||
iovar.rio = rioptr; \
|
||||
iovar.type = mtype; \
|
||||
iovar.bytes = 0; \
|
||||
iovar.error = 0; \
|
||||
iovar.ver = 0; \
|
||||
iovar.key = keyptr; \
|
||||
iovar.ctx = NULL; \
|
||||
} while(0);
|
||||
|
||||
@@ -749,7 +737,7 @@ typedef struct client {
|
||||
int flags; /* Client flags: CLIENT_* macros. */
|
||||
int authenticated; /* When requirepass is non-NULL. */
|
||||
int replstate; /* Replication state if this is a slave. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on first ACK. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
@@ -989,8 +977,7 @@ struct redisServer {
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client executing the command. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
client *current_client; /* Current client, only used on crash report */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
@@ -1076,7 +1063,6 @@ struct redisServer {
|
||||
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */
|
||||
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
|
||||
off_t aof_current_size; /* AOF current size. */
|
||||
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
||||
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||
pid_t aof_child_pid; /* PID if rewriting process */
|
||||
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
||||
@@ -1233,8 +1219,7 @@ struct redisServer {
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||
int daylight_active; /* Currently in daylight saving time. */
|
||||
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
||||
ustime_t ustime; /* 'unixtime' in microseconds. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
@@ -1422,8 +1407,7 @@ size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
void moduleCallCommandFilters(client *c);
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@@ -1451,7 +1435,6 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyString(client *c, const char *s, size_t len);
|
||||
void AddReplyFromClient(client *c, client *src);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@@ -1583,7 +1566,6 @@ int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -1653,7 +1635,6 @@ void openChildInfoPipe(void);
|
||||
void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
int hasActiveChildProcess();
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
@@ -1732,8 +1713,6 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
void call(client *c, int flags);
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||
void redisOpArrayInit(redisOpArray *oa);
|
||||
void redisOpArrayFree(redisOpArray *oa);
|
||||
void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandAOF(client *c);
|
||||
@@ -1754,7 +1733,7 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(int update_daylight_info);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
void activeDefragCycle(void);
|
||||
unsigned int getLRUClock(void);
|
||||
|
||||
@@ -109,6 +109,5 @@ 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);
|
||||
void streamIncrID(streamID *id);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -615,10 +615,6 @@ void hincrbyfloatCommand(client *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
if (isnan(value) || isinf(value)) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
|
||||
+2
-2
@@ -520,7 +520,7 @@ void lremCommand(client *c) {
|
||||
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) {
|
||||
@@ -617,7 +617,7 @@ void rpoplpushCommand(client *c) {
|
||||
* the AOF and replication channel.
|
||||
*
|
||||
* The argument 'where' is LIST_TAIL or LIST_HEAD, and indicates if the
|
||||
* 'value' element was popped from the head (BLPOP) or tail (BRPOP) so that
|
||||
* 'value' element was popped fron the head (BLPOP) or tail (BRPOP) so that
|
||||
* we can propagate the command properly.
|
||||
*
|
||||
* The function returns C_OK if we are able to serve the client, otherwise
|
||||
|
||||
+48
-99
@@ -37,7 +37,7 @@
|
||||
* mark the entry as deleted, or having the same field as the "master"
|
||||
* entry at the start of the listpack> */
|
||||
#define STREAM_ITEM_FLAG_NONE 0 /* No special flags. */
|
||||
#define STREAM_ITEM_FLAG_DELETED (1<<0) /* Entry is delted. Skip it. */
|
||||
#define STREAM_ITEM_FLAG_DELETED (1<<0) /* Entry is deleted. Skip it. */
|
||||
#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1) /* Same fields as master entry. */
|
||||
|
||||
void streamFreeCG(streamCG *cg);
|
||||
@@ -67,21 +67,6 @@ void freeStream(stream *s) {
|
||||
zfree(s);
|
||||
}
|
||||
|
||||
/* Set 'id' to be its successor streamID */
|
||||
void streamIncrID(streamID *id) {
|
||||
if (id->seq == UINT64_MAX) {
|
||||
if (id->ms == UINT64_MAX) {
|
||||
/* Special case where 'id' is the last possible streamID... */
|
||||
id->ms = id->seq = 0;
|
||||
} else {
|
||||
id->ms++;
|
||||
id->seq = 0;
|
||||
}
|
||||
} else {
|
||||
id->seq++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate the next stream item ID given the previous one. If the current
|
||||
* milliseconds Unix time is greater than the previous one, just use this
|
||||
* as time part and start with sequence part of zero. Otherwise we use the
|
||||
@@ -92,8 +77,8 @@ void streamNextID(streamID *last_id, streamID *new_id) {
|
||||
new_id->ms = ms;
|
||||
new_id->seq = 0;
|
||||
} else {
|
||||
*new_id = *last_id;
|
||||
streamIncrID(new_id);
|
||||
new_id->ms = last_id->ms;
|
||||
new_id->seq = last_id->seq+1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +165,7 @@ int streamCompareID(streamID *a, streamID *b) {
|
||||
* Returns the new entry ID populating the 'added_id' structure.
|
||||
*
|
||||
* If 'use_id' is not NULL, the ID is not auto-generated by the function,
|
||||
* but instead the passed ID is uesd to add the new entry. In this case
|
||||
* but instead the passed ID is used to add the new entry. In this case
|
||||
* adding the entry may fail as specified later in this comment.
|
||||
*
|
||||
* The function returns C_OK if the item was added, this is always true
|
||||
@@ -188,19 +173,9 @@ int streamCompareID(streamID *a, streamID *b) {
|
||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. */
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
streamID id;
|
||||
if (use_id)
|
||||
id = *use_id;
|
||||
else
|
||||
streamNextID(&s->last_id,&id);
|
||||
|
||||
/* Check that the new ID is greater than the last entry ID
|
||||
* or return an error. Automatically generated IDs might
|
||||
* overflow (and wrap-around) when incrementing the sequence
|
||||
part. */
|
||||
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
|
||||
/* If an ID was given, check that it's greater than the last entry ID
|
||||
* or return an error. */
|
||||
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
|
||||
|
||||
/* Add the new entry. */
|
||||
raxIterator ri;
|
||||
@@ -217,6 +192,13 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
streamID id;
|
||||
if (use_id)
|
||||
id = *use_id;
|
||||
else
|
||||
streamNextID(&s->last_id,&id);
|
||||
|
||||
/* We have to add the key into the radix tree in lexicographic order,
|
||||
* to do so we consider the ID as a single 128 bit number written in
|
||||
* big endian, so that the most significant bytes are the first ones. */
|
||||
@@ -241,13 +223,13 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
*
|
||||
* count and deleted just represent respectively the total number of
|
||||
* entries inside the listpack that are valid, and marked as deleted
|
||||
* (delted flag in the entry flags set). So the total number of items
|
||||
* (deleted flag in the entry flags set). So the total number of items
|
||||
* actually inside the listpack (both deleted and not) is count+deleted.
|
||||
*
|
||||
* The real entries will be encoded with an ID that is just the
|
||||
* millisecond and sequence difference compared to the key stored at
|
||||
* the radix tree node containing the listpack (delta encoding), and
|
||||
* if the fields of the entry are the same as the master enty fields, the
|
||||
* if the fields of the entry are the same as the master entry fields, the
|
||||
* entry flags will specify this fact and the entry fields and number
|
||||
* of fields will be omitted (see later in the code of this function).
|
||||
*
|
||||
@@ -260,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes >= server.stream_node_max_bytes)
|
||||
lp_bytes > server.stream_node_max_bytes)
|
||||
{
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
int64_t count = lpGetInteger(lpFirst(lp));
|
||||
if (count >= server.stream_node_max_entries) lp = NULL;
|
||||
if (count > server.stream_node_max_entries) lp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int flags = STREAM_ITEM_FLAG_NONE;
|
||||
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
|
||||
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
|
||||
master_id = id;
|
||||
streamEncodeID(rax_key,&id);
|
||||
/* Create the listpack having the master entry ID and fields. */
|
||||
@@ -504,20 +486,20 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
|
||||
* }
|
||||
* streamIteratorStop(&myiterator); */
|
||||
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev) {
|
||||
/* Intialize the iterator and translates the iteration start/stop
|
||||
/* Initialize the iterator and translates the iteration start/stop
|
||||
* elements into a 128 big big-endian number. */
|
||||
if (start) {
|
||||
streamEncodeID(si->start_key,start);
|
||||
} else {
|
||||
si->start_key[0] = 0;
|
||||
si->start_key[1] = 0;
|
||||
si->start_key[0] = 0;
|
||||
}
|
||||
|
||||
if (end) {
|
||||
streamEncodeID(si->end_key,end);
|
||||
} else {
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
si->end_key[1] = UINT64_MAX;
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
}
|
||||
|
||||
/* Seek the correct node in the radix tree. */
|
||||
@@ -582,7 +564,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
si->lp_ele = lpLast(si->lp);
|
||||
}
|
||||
} else if (si->rev) {
|
||||
/* If we are itereating in the reverse order, and this is not
|
||||
/* If we are iterating in the reverse order, and this is not
|
||||
* the first entry emitted for this listpack, then we already
|
||||
* emitted the current entry, and have to go back to the previous
|
||||
* one. */
|
||||
@@ -769,7 +751,7 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
|
||||
}
|
||||
|
||||
/* Stop the stream iterator. The only cleanup we need is to free the rax
|
||||
* itereator, since the stream iterator itself is supposed to be stack
|
||||
* iterator, since the stream iterator itself is supposed to be stack
|
||||
* allocated. */
|
||||
void streamIteratorStop(streamIterator *si) {
|
||||
raxStop(&si->ri);
|
||||
@@ -791,16 +773,6 @@ int streamDeleteItem(stream *s, streamID *id) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* Get the last valid (non-tombstone) streamID of 's'. */
|
||||
void streamLastValidID(stream *s, streamID *maxid)
|
||||
{
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
}
|
||||
|
||||
/* Emit a reply in the client output buffer by formatting a Stream ID
|
||||
* in the standard <ms>-<seq> format, using the simple string protocol
|
||||
* of REPL. */
|
||||
@@ -869,7 +841,7 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
alsoPropagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -895,15 +867,15 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
*
|
||||
* The behavior may be modified passing non-zero flags:
|
||||
*
|
||||
* STREAM_RWR_NOACK: Do not craete PEL entries, that is, the point "3" above
|
||||
* STREAM_RWR_NOACK: Do not create PEL entries, that is, the point "3" above
|
||||
* is not performed.
|
||||
* STREAM_RWR_RAWENTRIES: Do not emit array boundaries, but just the entries,
|
||||
* and return the number of entries emitted as usually.
|
||||
* This is used when the function is just used in order
|
||||
* to emit data and there is some higher level logic.
|
||||
*
|
||||
* The final argument 'spi' (stream propagatino info pointer) is a structure
|
||||
* filled with information needed to propagte the command execution to AOF
|
||||
* The final argument 'spi' (stream propagation info pointer) is a structure
|
||||
* filled with information needed to propagate the command execution to AOF
|
||||
* and slaves, in the case a consumer group was passed: we need to generate
|
||||
* XCLAIM commands to create the pending list into AOF/slaves in that case.
|
||||
*
|
||||
@@ -1166,7 +1138,7 @@ invalid:
|
||||
}
|
||||
|
||||
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
|
||||
* 0, to be used when - and + are accetable IDs. */
|
||||
* 0, to be used when - and + are acceptable IDs. */
|
||||
int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
|
||||
return streamGenericParseIDOrReply(c,o,id,missing_seq,0);
|
||||
}
|
||||
@@ -1244,27 +1216,12 @@ void xaddCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
|
||||
* a new stream and have streamAppendItem fail, leaving an empty key in the
|
||||
* database. */
|
||||
if (id_given && id.ms == 0 && id.seq == 0) {
|
||||
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup the stream at key. */
|
||||
robj *o;
|
||||
stream *s;
|
||||
if ((o = streamTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
s = o->ptr;
|
||||
|
||||
/* Return ASAP if the stream has reached the last possible ID */
|
||||
if (s->last_id.ms == UINT64_MAX && s->last_id.seq == UINT64_MAX) {
|
||||
addReplyError(c,"The stream has exhausted the last possible ID, "
|
||||
"unable to add more items");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Append using the low level function and return the ID. */
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
&id, id_given ? &id : NULL)
|
||||
@@ -1528,23 +1485,20 @@ void xreadCommand(client *c) {
|
||||
{
|
||||
serve_synchronously = 1;
|
||||
serve_history = 1;
|
||||
} else if (s->length) {
|
||||
} 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 maxid, *last = &groups[i]->last_id;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, last) > 0) {
|
||||
streamID *last = &groups[i]->last_id;
|
||||
if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
|
||||
serve_synchronously = 1;
|
||||
*gt = *last;
|
||||
}
|
||||
}
|
||||
} else if (s->length) {
|
||||
} else {
|
||||
/* For consumers without a group, we serve synchronously if we can
|
||||
* actually provide at least one item from the stream. */
|
||||
streamID maxid;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, gt) > 0) {
|
||||
if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
|
||||
serve_synchronously = 1;
|
||||
}
|
||||
}
|
||||
@@ -1556,7 +1510,7 @@ void xreadCommand(client *c) {
|
||||
* so start from the next ID, since we want only messages with
|
||||
* IDs greater than start. */
|
||||
streamID start = *gt;
|
||||
streamIncrID(&start);
|
||||
start.seq++; /* uint64_t can't overflow in this context. */
|
||||
|
||||
/* Emit the two elements sub-array consisting of the name
|
||||
* of the stream and the data we extracted from it. */
|
||||
@@ -1778,17 +1732,14 @@ NULL
|
||||
/* Everything but the "HELP" option requires a key and group name. */
|
||||
if (c->argc >= 4) {
|
||||
o = lookupKeyWrite(c->db,c->argv[2]);
|
||||
if (o) {
|
||||
if (checkType(c,o,OBJ_STREAM)) return;
|
||||
s = o->ptr;
|
||||
}
|
||||
if (o) s = o->ptr;
|
||||
grpname = c->argv[3]->ptr;
|
||||
}
|
||||
|
||||
/* Check for missing key/group. */
|
||||
if (c->argc >= 4 && !mkstream) {
|
||||
/* At this point key must exist, or there is an error. */
|
||||
if (s == NULL) {
|
||||
if (o == NULL) {
|
||||
addReplyError(c,
|
||||
"The XGROUP subcommand requires the key to exist. "
|
||||
"Note that for CREATE you may want to use the MKSTREAM "
|
||||
@@ -1796,6 +1747,8 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkType(c,o,OBJ_STREAM)) return;
|
||||
|
||||
/* Certain subcommands require the group to exist. */
|
||||
if ((cg = streamLookupCG(s,grpname)) == NULL &&
|
||||
(!strcasecmp(opt,"SETID") ||
|
||||
@@ -1823,8 +1776,7 @@ NULL
|
||||
}
|
||||
|
||||
/* Handle the MKSTREAM option now that the command can no longer fail. */
|
||||
if (s == NULL) {
|
||||
serverAssert(mkstream);
|
||||
if (s == NULL && mkstream) {
|
||||
o = createStreamObject();
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
s = o->ptr;
|
||||
@@ -1893,7 +1845,11 @@ void xsetidCommand(client *c) {
|
||||
* item, otherwise the fundamental ID monotonicity assumption is violated. */
|
||||
if (s->length > 0) {
|
||||
streamID maxid;
|
||||
streamLastValidID(s,&maxid);
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,&maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
|
||||
if (streamCompareID(&id,&maxid) < 0) {
|
||||
addReplyError(c,"The ID specified in XSETID is smaller than the "
|
||||
@@ -2264,7 +2220,7 @@ void xclaimCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Do the actual claiming. */
|
||||
streamConsumer *consumer = NULL;
|
||||
streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
void *arraylenptr = addDeferredMultiBulkLength(c);
|
||||
size_t arraylen = 0;
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
@@ -2316,17 +2272,10 @@ void xclaimCommand(client *c) {
|
||||
if (nack->consumer)
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
/* Update the consumer and idle time. */
|
||||
if (consumer == NULL)
|
||||
consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = deliverytime;
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
* autoincrement unless JUSTID option provided */
|
||||
if (retrycount >= 0) {
|
||||
nack->delivery_count = retrycount;
|
||||
} else if (!justid) {
|
||||
nack->delivery_count++;
|
||||
}
|
||||
/* Set the delivery attempts counter if given. */
|
||||
if (retrycount >= 0) nack->delivery_count = retrycount;
|
||||
/* Add the entry in the new consumer local PEL. */
|
||||
raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
/* Send the reply for this entry. */
|
||||
|
||||
+5
-10
@@ -1357,8 +1357,9 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
/* Optimize: check if the element is too large or the list
|
||||
* becomes too long *before* executing zzlInsert. */
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value)
|
||||
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (sdslen(ele) > server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (newscore) *newscore = score;
|
||||
*flags |= ZADD_ADDED;
|
||||
@@ -2906,10 +2907,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
while (remaining) {
|
||||
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) return;
|
||||
pos += 3; remaining -= 3;
|
||||
} else {
|
||||
zslFreeLexRange(&range);
|
||||
@@ -3143,10 +3141,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
if (countarg) {
|
||||
if (getLongFromObjectOrReply(c,countarg,&count,NULL) != C_OK)
|
||||
return;
|
||||
if (count <= 0) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
if (count < 0) count = 1;
|
||||
}
|
||||
|
||||
/* Check type and break on the first error, otherwise identify candidate. */
|
||||
|
||||
+1
-1
@@ -447,7 +447,7 @@ int string2l(const char *s, size_t slen, long *lval) {
|
||||
* a double: no spaces or other characters before or after the string
|
||||
* representing the number are accepted. */
|
||||
int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
char buf[256];
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "5.0.9"
|
||||
#define REDIS_VERSION "999.999.999"
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ void zipEntry(unsigned char *p, zlentry *e) {
|
||||
|
||||
/* Create a new empty ziplist. */
|
||||
unsigned char *ziplistNew(void) {
|
||||
unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
|
||||
unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
|
||||
unsigned char *zl = zmalloc(bytes);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
|
||||
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
|
||||
|
||||
@@ -323,13 +323,6 @@ int zmalloc_get_allocator_info(size_t *allocated,
|
||||
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void set_jemalloc_bg_thread(int enable) {
|
||||
/* let jemalloc do purging asynchronously, required when there's no traffic
|
||||
* after flushdb */
|
||||
char val = !!enable;
|
||||
je_mallctl("background_thread", NULL, 0, &val, 1);
|
||||
}
|
||||
#else
|
||||
int zmalloc_get_allocator_info(size_t *allocated,
|
||||
size_t *active,
|
||||
|
||||
@@ -166,15 +166,12 @@ start_server {} {
|
||||
# Pick a random slave
|
||||
set slave_id [expr {($master_id+1)%5}]
|
||||
set sync_count [status $R($master_id) sync_full]
|
||||
set sync_partial [status $R($master_id) sync_partial_ok]
|
||||
catch {
|
||||
$R($slave_id) config rewrite
|
||||
$R($slave_id) debug restart
|
||||
}
|
||||
# note: just waiting for connected_slaves==4 has a race condition since
|
||||
# we might do the check before the master realized that the slave disconnected
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) sync_partial_ok] == $sync_partial + 1
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
} else {
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
|
||||
@@ -79,32 +79,6 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
stop_bg_complex_data $load_handle0
|
||||
stop_bg_complex_data $load_handle1
|
||||
stop_bg_complex_data $load_handle2
|
||||
|
||||
# Wait for the slave to reach the "online"
|
||||
# state from the POV of the master.
|
||||
set retry 5000
|
||||
while {$retry} {
|
||||
set info [$master info]
|
||||
if {[string match {*slave0:*state=online*} $info]} {
|
||||
break
|
||||
} else {
|
||||
incr retry -1
|
||||
after 100
|
||||
}
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "assertion:Slave not correctly synchronized"
|
||||
}
|
||||
|
||||
# Wait that slave acknowledge it is online so
|
||||
# we are sure that DBSIZE and DEBUG DIGEST will not
|
||||
# fail because of timing issues. (-LOADING error)
|
||||
wait_for_condition 5000 100 {
|
||||
[lindex [$slave role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Slave still not connected after some time"
|
||||
}
|
||||
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
# find the OS
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
# Compile flags for linux / osx
|
||||
ifeq ($(uname_S),Linux)
|
||||
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -shared
|
||||
else
|
||||
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
|
||||
endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: commandfilter.so testrdb.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
commandfilter.xo: ../../src/redismodule.h
|
||||
testrdb.xo: ../../src/redismodule.h
|
||||
|
||||
commandfilter.so: commandfilter.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testrdb.so: testrdb.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
@@ -1,149 +0,0 @@
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleString *log_key_name;
|
||||
|
||||
static const char log_command_name[] = "commandfilter.log";
|
||||
static const char ping_command_name[] = "commandfilter.ping";
|
||||
static const char unregister_command_name[] = "commandfilter.unregister";
|
||||
static int in_log_command = 0;
|
||||
|
||||
static RedisModuleCommandFilter *filter = NULL;
|
||||
|
||||
int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,
|
||||
RedisModule_UnregisterCommandFilter(ctx, filter));
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
|
||||
|
||||
int i;
|
||||
for (i = 1; i < argc; i++) {
|
||||
size_t arglen;
|
||||
const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
|
||||
|
||||
if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
|
||||
RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
|
||||
}
|
||||
|
||||
RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
|
||||
RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
|
||||
RedisModule_CloseKey(log);
|
||||
RedisModule_FreeString(ctx, s);
|
||||
|
||||
in_log_command = 1;
|
||||
|
||||
size_t cmdlen;
|
||||
const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], argc - 2);
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
in_log_command = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
|
||||
{
|
||||
if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
|
||||
|
||||
/* Fun manipulations:
|
||||
* - Remove @delme
|
||||
* - Replace @replaceme
|
||||
* - Append @insertbefore or @insertafter
|
||||
* - Prefix with Log command if @log encounterd
|
||||
*/
|
||||
int log = 0;
|
||||
int pos = 0;
|
||||
while (pos < RedisModule_CommandFilterArgsCount(filter)) {
|
||||
const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
|
||||
size_t arg_len;
|
||||
const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
|
||||
|
||||
if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
|
||||
RedisModule_CommandFilterArgDelete(filter, pos);
|
||||
continue;
|
||||
}
|
||||
if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
|
||||
RedisModule_CommandFilterArgReplace(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--replaced--", 12));
|
||||
} else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--inserted-before--", 19));
|
||||
pos++;
|
||||
} else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos + 1,
|
||||
RedisModule_CreateString(NULL, "--inserted-after--", 18));
|
||||
pos++;
|
||||
} else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
|
||||
log = 1;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (log) RedisModule_CommandFilterArgInsert(filter, 0,
|
||||
RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (argc != 2) {
|
||||
RedisModule_Log(ctx, "warning", "Log key name not specified");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
long long noself = 0;
|
||||
log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
|
||||
RedisModule_StringToLongLong(argv[1], &noself);
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,log_command_name,
|
||||
CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,ping_command_name,
|
||||
CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,unregister_command_name,
|
||||
CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
|
||||
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
|
||||
== NULL) return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* Module configuration, save aux or not? */
|
||||
long long conf_aux_count = 0;
|
||||
|
||||
/* Registered type */
|
||||
RedisModuleType *testrdb_type = NULL;
|
||||
|
||||
/* Global values to store and persist to aux */
|
||||
RedisModuleString *before_str = NULL;
|
||||
RedisModuleString *after_str = NULL;
|
||||
|
||||
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
assert(count==1);
|
||||
assert(encver==1);
|
||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||
return str;
|
||||
}
|
||||
|
||||
void testrdb_type_save(RedisModuleIO *rdb, void *value) {
|
||||
RedisModuleString *str = (RedisModuleString*)value;
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, str);
|
||||
}
|
||||
|
||||
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
|
||||
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||
if (conf_aux_count==0) assert(0);
|
||||
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||
if (before_str) {
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, before_str);
|
||||
} else {
|
||||
RedisModule_SaveSigned(rdb, 0);
|
||||
}
|
||||
} else {
|
||||
if (after_str) {
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, after_str);
|
||||
} else {
|
||||
RedisModule_SaveSigned(rdb, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
||||
assert(encver == 1);
|
||||
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||
if (conf_aux_count==0) assert(0);
|
||||
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
|
||||
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||
if (before_str)
|
||||
RedisModule_FreeString(ctx, before_str);
|
||||
before_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (count)
|
||||
before_str = RedisModule_LoadString(rdb);
|
||||
} else {
|
||||
if (after_str)
|
||||
RedisModule_FreeString(ctx, after_str);
|
||||
after_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (count)
|
||||
after_str = RedisModule_LoadString(rdb);
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void testrdb_type_free(void *value) {
|
||||
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||
}
|
||||
|
||||
int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
if (before_str)
|
||||
RedisModule_FreeString(ctx, before_str);
|
||||
before_str = argv[1];
|
||||
RedisModule_RetainString(ctx, argv[1]);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
if (argc != 1){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
if (before_str)
|
||||
RedisModule_ReplyWithString(ctx, before_str);
|
||||
else
|
||||
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
if (after_str)
|
||||
RedisModule_FreeString(ctx, after_str);
|
||||
after_str = argv[1];
|
||||
RedisModule_RetainString(ctx, argv[1]);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
if (argc != 1){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
if (after_str)
|
||||
RedisModule_ReplyWithString(ctx, after_str);
|
||||
else
|
||||
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 3){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||
if (str)
|
||||
RedisModule_FreeString(ctx, str);
|
||||
RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
|
||||
RedisModule_RetainString(ctx, argv[2]);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithString(ctx, str);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (argc > 0)
|
||||
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
|
||||
|
||||
if (conf_aux_count==0) {
|
||||
RedisModuleTypeMethods datatype_methods = {
|
||||
.version = 1,
|
||||
.rdb_load = testrdb_type_load,
|
||||
.rdb_save = testrdb_type_save,
|
||||
.aof_rewrite = NULL,
|
||||
.digest = NULL,
|
||||
.free = testrdb_type_free,
|
||||
};
|
||||
|
||||
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||
if (testrdb_type == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
} else {
|
||||
RedisModuleTypeMethods datatype_methods = {
|
||||
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||
.rdb_load = testrdb_type_load,
|
||||
.rdb_save = testrdb_type_save,
|
||||
.aof_rewrite = NULL,
|
||||
.digest = NULL,
|
||||
.free = testrdb_type_free,
|
||||
.aux_load = testrdb_aux_load,
|
||||
.aux_save = testrdb_aux_save,
|
||||
.aux_save_triggers = (conf_aux_count == 1 ?
|
||||
REDISMODULE_AUX_AFTER_RDB :
|
||||
REDISMODULE_AUX_BEFORE_RDB | REDISMODULE_AUX_AFTER_RDB)
|
||||
};
|
||||
|
||||
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||
if (testrdb_type == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -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:"
|
||||
|
||||
@@ -502,7 +502,7 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--only}} {
|
||||
lappend ::only_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--skip-till}} {
|
||||
} elseif {$opt eq {--skiptill}} {
|
||||
set ::skip_till $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--list-tests}} {
|
||||
|
||||
@@ -115,34 +115,6 @@ start_server {tags {"hll"}} {
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Fuzzing dense/sparse encoding: Redis should always detect errors} {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
r del hll
|
||||
set items {}
|
||||
set numitems [randomInt 3000]
|
||||
for {set i 0} {$i < $numitems} {incr i} {
|
||||
lappend items [expr {rand()}]
|
||||
}
|
||||
r pfadd hll {*}$items
|
||||
|
||||
# Corrupt it in some random way.
|
||||
for {set i 0} {$i < 5} {incr i} {
|
||||
set len [r strlen hll]
|
||||
set pos [randomInt $len]
|
||||
set byte [randstring 1 1 binary]
|
||||
r setrange hll $pos $byte
|
||||
# Don't modify more bytes 50% of times
|
||||
if {rand() < 0.5} break
|
||||
}
|
||||
|
||||
# Use the hyperloglog to check if it crashes
|
||||
# Redis in some way.
|
||||
catch {
|
||||
r pfcount hll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
|
||||
@@ -161,7 +161,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
}
|
||||
|
||||
# make sure master doesn't disconnect slave because of timeout
|
||||
$master config set repl-timeout 1200 ;# 20 minutes (for valgrind and slow machines)
|
||||
$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}]
|
||||
@@ -212,8 +212,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
|
||||
assert {[$master dbsize] == 100}
|
||||
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
|
||||
set delta_max [expr {$cmd_count / 2}] ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
assert {$delta < $delta_max && $delta > -$delta_max}
|
||||
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]
|
||||
@@ -222,7 +221,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
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 > -$delta_max && $delta_no_repl < $delta_max}
|
||||
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
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
set testmodule [file normalize tests/modules/commandfilter.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter handles redirected commands} {
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} "{set mykey @log}"
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgDelete} {
|
||||
r rpush mylist elem1 @delme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 elem2}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgInsert} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgReplace} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @replaceme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --replaced-- elem2}
|
||||
|
||||
test {Command Filter applies on RM_Call() commands} {
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call()} {
|
||||
r del log-key
|
||||
r eval "redis.call('ping', '@log')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call() that calls a module} {
|
||||
r del log-key
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter is unregistered implicitly on module unload} {
|
||||
r del log-key
|
||||
r module unload commandfilter
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter unregister works as expected} {
|
||||
# Validate reloading succeeded
|
||||
r del log-key
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
# Unregister
|
||||
r commandfilter.unregister
|
||||
r del log-key
|
||||
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module unload commandfilter
|
||||
r module load $testmodule log-key 1
|
||||
|
||||
test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
set testmodule [file normalize tests/modules/testrdb.so]
|
||||
|
||||
proc restart_and_wait {} {
|
||||
catch {
|
||||
r debug restart
|
||||
}
|
||||
|
||||
# wait for the server to come back up
|
||||
set retry 50
|
||||
while {$retry} {
|
||||
if {[catch { r ping }]} {
|
||||
after 100
|
||||
} else {
|
||||
break
|
||||
}
|
||||
incr retry -1
|
||||
}
|
||||
}
|
||||
|
||||
tags "modules" {
|
||||
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||
test {modules are able to persist types} {
|
||||
r testrdb.set.key key1 value1
|
||||
assert_equal "value1" [r testrdb.get.key key1]
|
||||
r debug reload
|
||||
assert_equal "value1" [r testrdb.get.key key1]
|
||||
}
|
||||
|
||||
test {modules global are lost without aux} {
|
||||
r testrdb.set.before global1
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
restart_and_wait
|
||||
assert_equal "" [r testrdb.get.before]
|
||||
}
|
||||
}
|
||||
|
||||
start_server [list overrides [list loadmodule "$testmodule 2"]] {
|
||||
test {modules are able to persist globals before and after} {
|
||||
r testrdb.set.before global1
|
||||
r testrdb.set.after global2
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
restart_and_wait
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
start_server [list overrides [list loadmodule "$testmodule 1"]] {
|
||||
test {modules are able to persist globals just after} {
|
||||
r testrdb.set.after global2
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
restart_and_wait
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# TODO: test short read handling
|
||||
|
||||
}
|
||||
@@ -80,11 +80,9 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
|
||||
}
|
||||
|
||||
test {SLOWLOG - can be disabled} {
|
||||
r config set slowlog-max-len 1
|
||||
r config set slowlog-log-slower-than 1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 1
|
||||
assert_equal [r slowlog len] 1
|
||||
r config set slowlog-log-slower-than -1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
|
||||
@@ -147,20 +147,6 @@ start_server {
|
||||
assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
|
||||
}
|
||||
|
||||
test {Blocking XREADGROUP will not reply with an empty array} {
|
||||
r del mystream
|
||||
r XGROUP CREATE mystream mygroup $ MKSTREAM
|
||||
r XADD mystream 666 f v
|
||||
set res [r XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"]
|
||||
assert {[lindex $res 0 1 0] == {666-0 {f v}}}
|
||||
r XADD mystream 667 f2 v2
|
||||
r XDEL mystream 667
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"
|
||||
after 20
|
||||
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}}
|
||||
}
|
||||
|
||||
test {XCLAIM can claim PEL items from another consumer} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
@@ -209,49 +195,6 @@ start_server {
|
||||
assert_equal "" [lindex $reply 0]
|
||||
}
|
||||
|
||||
test {XCLAIM without JUSTID increments delivery count} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
set id1 [r XADD mystream * a 1]
|
||||
set id2 [r XADD mystream * b 2]
|
||||
set id3 [r XADD mystream * c 3]
|
||||
r XGROUP CREATE mystream mygroup 0
|
||||
|
||||
# Client 1 reads item 1 from the stream without acknowledgements.
|
||||
# Client 2 then claims pending item 1 from the PEL of client 1
|
||||
set reply [
|
||||
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||
r debug sleep 0.2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client2 10 $id1
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1] eq {a 1}}
|
||||
|
||||
set reply [
|
||||
r XPENDING mystream mygroup - + 10
|
||||
]
|
||||
assert {[llength [lindex $reply 0]] == 4}
|
||||
assert {[lindex $reply 0 3] == 2}
|
||||
|
||||
# Client 3 then claims pending item 1 from the PEL of client 2 using JUSTID
|
||||
r debug sleep 0.2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client3 10 $id1 JUSTID
|
||||
]
|
||||
assert {[llength $reply] == 1}
|
||||
assert {[lindex $reply 0] eq $id1}
|
||||
|
||||
set reply [
|
||||
r XPENDING mystream mygroup - + 10
|
||||
]
|
||||
assert {[llength [lindex $reply 0]] == 4}
|
||||
assert {[lindex $reply 0 3] == 2}
|
||||
}
|
||||
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
|
||||
@@ -79,12 +79,6 @@ start_server {
|
||||
assert {[streamCompareID $id2 $id3] == -1}
|
||||
}
|
||||
|
||||
test {XADD IDs correctly report an error when overflowing} {
|
||||
r DEL mystream
|
||||
r xadd mystream 18446744073709551615-18446744073709551615 a b
|
||||
assert_error ERR* {r xadd mystream * c d}
|
||||
}
|
||||
|
||||
test {XADD with MAXLEN option} {
|
||||
r DEL mystream
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
@@ -123,12 +117,6 @@ start_server {
|
||||
assert {[r xlen mystream] == $j}
|
||||
}
|
||||
|
||||
test {XADD with ID 0-0} {
|
||||
r DEL otherstream
|
||||
catch {r XADD otherstream 0-0 k v} err
|
||||
assert {[r EXISTS otherstream] == 0}
|
||||
}
|
||||
|
||||
test {XRANGE COUNT works as expected} {
|
||||
assert {[llength [r xrange mystream - + COUNT 10]] == 10}
|
||||
}
|
||||
@@ -191,17 +179,6 @@ start_server {
|
||||
assert {[lindex $res 0 1 0 1] eq {old abcd1234}}
|
||||
}
|
||||
|
||||
test {Blocking XREAD will not reply with an empty array} {
|
||||
r del s1
|
||||
r XADD s1 666 f v
|
||||
r XADD s1 667 f2 v2
|
||||
r XDEL s1 667
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREAD BLOCK 10 STREAMS s1 666
|
||||
after 20
|
||||
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {s1 {}}
|
||||
}
|
||||
|
||||
test "XREAD: XADD + DEL should not awake client" {
|
||||
set rd [redis_deferring_client]
|
||||
r del s1
|
||||
@@ -339,33 +316,6 @@ start_server {
|
||||
|
||||
assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
|
||||
}
|
||||
|
||||
test {XREAD streamID edge (no-blocking)} {
|
||||
r del x
|
||||
r XADD x 1-1 f v
|
||||
r XADD x 1-18446744073709551615 f v
|
||||
r XADD x 2-1 f v
|
||||
set res [r XREAD BLOCK 0 STREAMS x 1-18446744073709551615]
|
||||
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
|
||||
}
|
||||
|
||||
test {XREAD streamID edge (blocking)} {
|
||||
r del x
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREAD BLOCK 0 STREAMS x 1-18446744073709551615
|
||||
r XADD x 1-1 f v
|
||||
r XADD x 1-18446744073709551615 f v
|
||||
r XADD x 2-1 f v
|
||||
set res [$rd read]
|
||||
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
|
||||
}
|
||||
|
||||
test {XADD streamID edge} {
|
||||
r del x
|
||||
r XADD x 2577343934890-18446744073709551615 f v ;# we need the timestamp to be in the future
|
||||
r XADD x * f2 v2
|
||||
assert_equal [r XRANGE x - +] {{2577343934890-18446744073709551615 {f v}} {2577343934891-0 {f2 v2}}}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||
@@ -405,12 +355,12 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
|
||||
r XADD mystream * xitem v
|
||||
}
|
||||
r XTRIM mystream MAXLEN ~ 85
|
||||
assert {[r xlen mystream] == 90}
|
||||
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] == 91}
|
||||
assert {[r xlen mystream] == 90}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -21,9 +21,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
srand(time(NULL));
|
||||
char *filename = argv[1];
|
||||
cycles = atoi(argv[2]);
|
||||
fd = open(filename,O_RDWR);
|
||||
fd = open("dump.rdb",O_RDWR);
|
||||
if (fd == -1) {
|
||||
perror("open");
|
||||
exit(1);
|
||||
|
||||
Reference in New Issue
Block a user