Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07cb1f4581 | ||
|
|
f2ca2aa1f7 | ||
|
|
b20bf10fda | ||
|
|
fa6870a268 | ||
|
|
c3d3c82f3a | ||
|
|
f534b5b3a2 | ||
|
|
27da034819 | ||
|
|
6ec1641294 | ||
|
|
7b79ec360b | ||
|
|
56fc6b7deb | ||
|
|
528a43bd6c | ||
|
|
b357803362 | ||
|
|
448808529a | ||
|
|
a0bc208412 | ||
|
|
6b0172b9bf | ||
|
|
bc1eccb66d | ||
|
|
a9249d4a82 | ||
|
|
3c373494d6 | ||
|
|
5aef606d3b | ||
|
|
2ec0b2cd20 | ||
|
|
cd76a71de3 | ||
|
|
e523afa741 | ||
|
|
c2a98a60f9 | ||
|
|
13cf8bbeb7 | ||
|
|
60c0d4104d | ||
|
|
693ba4ed4a | ||
|
|
5397f0b033 | ||
|
|
6c78ec7c41 | ||
|
|
c0882864f2 | ||
|
|
faa87ed839 | ||
|
|
83f585e30a | ||
|
|
7a244bb847 | ||
|
|
3eb847e28f | ||
|
|
3ce6441a0c | ||
|
|
6d00a1a5cf | ||
|
|
aaca32a5cd | ||
|
|
54c96d1995 | ||
|
|
e5a2d59446 | ||
|
|
d95bead383 | ||
|
|
6ad6c1d780 | ||
|
|
98aefac09b | ||
|
|
1ef187533c | ||
|
|
b9db899f6c | ||
|
|
06ad1c15d7 | ||
|
|
63ae679fe1 | ||
|
|
b92bbf4de8 | ||
|
|
4781eda722 | ||
|
|
2fd93c5789 | ||
|
|
52434a583a | ||
|
|
16a019dba0 | ||
|
|
1a4c3224c9 | ||
|
|
c3317686f8 | ||
|
|
4622472123 | ||
|
|
f1748f8c76 | ||
|
|
0c101dccc8 | ||
|
|
dc78bf1ccb | ||
|
|
a7cc3aac28 | ||
|
|
fb2bdf7d05 | ||
|
|
050d58007f | ||
|
|
49d86746ed | ||
|
|
1069d0b4bf | ||
|
|
c983d57f73 | ||
|
|
3204a39ada | ||
|
|
f4319c1280 | ||
|
|
762396d997 | ||
|
|
c575e7df94 | ||
|
|
aa6409f2e8 | ||
|
|
e603e57588 | ||
|
|
049b48ef79 | ||
|
|
0ba0a6ab0a | ||
|
|
7475ea1b0b | ||
|
|
d0654af74f | ||
|
|
d5f8df5997 | ||
|
|
39acb312ef | ||
|
|
b643c38204 | ||
|
|
dd70f5efd7 | ||
|
|
bb38628f0e | ||
|
|
0cf3af6857 | ||
|
|
edb993d52b | ||
|
|
aea333bb78 | ||
|
|
45817db8c3 | ||
|
|
dbef17c2e1 | ||
|
|
42a8f22c21 | ||
|
|
6e06e0dfc7 | ||
|
|
bf500ab44b | ||
|
|
4d4eca5433 | ||
|
|
4b01ad1e30 | ||
|
|
3b60929bdc | ||
|
|
a45d141597 | ||
|
|
8cbe254491 | ||
|
|
ab365ce3da | ||
|
|
aa3e70f9a0 | ||
|
|
cda8d42a76 | ||
|
|
67f32bf1b1 | ||
|
|
7a3f363f34 | ||
|
|
91eac2cdb3 | ||
|
|
98a928147f | ||
|
|
8a9045c028 | ||
|
|
6c65a11b8d | ||
|
|
7dd53182e0 | ||
|
|
53b95d830a | ||
|
|
3028a890ef | ||
|
|
39c6fbddb1 | ||
|
|
704cb4e929 | ||
|
|
8e4f323439 | ||
|
|
937702ea1d | ||
|
|
3ea56ffb38 | ||
|
|
becd3fc5a3 | ||
|
|
4c8603815c | ||
|
|
3ae75c2d2b | ||
|
|
9371c005aa | ||
|
|
16bd6b3f5d | ||
|
|
3dd78a6101 | ||
|
|
41b678814b | ||
|
|
d0ccb074d5 | ||
|
|
cb0fc7cca2 | ||
|
|
5e40ea6ee7 | ||
|
|
427c5999f1 | ||
|
|
a91f58b8fd | ||
|
|
3013799664 | ||
|
|
f8220a658e | ||
|
|
f1d2b29450 | ||
|
|
cb93752aff | ||
|
|
549cad378d | ||
|
|
631f863dd8 | ||
|
|
4396682c07 | ||
|
|
ca4ee4e3e4 | ||
|
|
2da09e0732 | ||
|
|
4986dc6da9 | ||
|
|
1a70af2ae1 | ||
|
|
5a8cb77d0d | ||
|
|
600fc241cf | ||
|
|
fa797408d9 | ||
|
|
3fac516950 | ||
|
|
2f65aa2337 | ||
|
|
f5210a8fb0 | ||
|
|
8f1295c00f | ||
|
|
18a3768e07 | ||
|
|
95e3dbc104 | ||
|
|
40355eac3e | ||
|
|
3a3b59a054 | ||
|
|
12e17cfd6e | ||
|
|
203e341bf4 | ||
|
|
6e6cd84ad7 | ||
|
|
252725d50f | ||
|
|
c15633ea9e | ||
|
|
27c9ae667e | ||
|
|
16f96efbe7 | ||
|
|
5787e7adc7 | ||
|
|
4631119179 | ||
|
|
0a0c2146c8 | ||
|
|
cedea7e400 | ||
|
|
66cef6646c | ||
|
|
64aa4b80e6 | ||
|
|
551a3cb1aa | ||
|
|
0942188b0a | ||
|
|
070c481871 | ||
|
|
6df4d29422 | ||
|
|
6e1e5bd08b | ||
|
|
ba365298ed | ||
|
|
e1c9620951 | ||
|
|
e776474f68 | ||
|
|
4589e861ab | ||
|
|
55324aa56a | ||
|
|
18d42a5c35 | ||
|
|
71d5a10621 | ||
|
|
ac595ebe37 | ||
|
|
50a6a3ca38 | ||
|
|
f83a89f82a | ||
|
|
bf905a78f0 | ||
|
|
fad9483fc9 | ||
|
|
46a78c69e7 | ||
|
|
33e0b3e1c0 | ||
|
|
4e9bfcf84b | ||
|
|
75c6b4c64a | ||
|
|
8536854266 | ||
|
|
6016dd0f8b | ||
|
|
47ef9e5cbd | ||
|
|
4858fd893c | ||
|
|
6b6e6f6c1e | ||
|
|
cbe5c04a0f | ||
|
|
b0a0f03b96 | ||
|
|
08b6a04055 | ||
|
|
f1982ca63d | ||
|
|
afe66288fe | ||
|
|
2794cfced4 | ||
|
|
d6b45a56e7 | ||
|
|
a55d980436 | ||
|
|
6037d1f326 | ||
|
|
0b43b51a2e | ||
|
|
184abac694 | ||
|
|
0ba5b2c3d6 | ||
|
|
0b3d74ea67 | ||
|
|
ea3bd614b8 | ||
|
|
d45edc493d | ||
|
|
271df3dad4 | ||
|
|
5a193872f8 | ||
|
|
b5512d77a1 | ||
|
|
42bda8eaba | ||
|
|
7e5d3f4f16 | ||
|
|
5f3920e491 | ||
|
|
3880d2616c | ||
|
|
156e596f9c | ||
|
|
fc58516cca | ||
|
|
a4efcd35af | ||
|
|
27fe1658a2 | ||
|
|
47feb2719c | ||
|
|
c21af6b351 | ||
|
|
821c12c482 | ||
|
|
4602f8c391 | ||
|
|
27a927fe00 | ||
|
|
3cdc6e8d84 | ||
|
|
89b52da387 | ||
|
|
50ad880ad5 | ||
|
|
15bed4bbcc | ||
|
|
10986ae3a8 | ||
|
|
2baa76b2ee | ||
|
|
ee460301fd | ||
|
|
8ebe2fce3d | ||
|
|
8198a697fd | ||
|
|
35acae360a | ||
|
|
8aaa8b0b11 | ||
|
|
25e18a7de9 | ||
|
|
bb090b6d20 | ||
|
|
a9c4e3970f | ||
|
|
d5ecc2347a | ||
|
|
58971a430d | ||
|
|
1512274f4c | ||
|
|
e310d33c12 | ||
|
|
ad32ba0343 | ||
|
|
4e30a5b4e5 | ||
|
|
c8516f3dc4 | ||
|
|
aae0fdc114 | ||
|
|
a19d0f67b9 | ||
|
|
971f69fe1f | ||
|
|
7468c691ad | ||
|
|
620c2ec141 | ||
|
|
3081b6f98b | ||
|
|
8630339e43 | ||
|
|
528d10091f | ||
|
|
149c3cf3dd | ||
|
|
f12c3cf9dd | ||
|
|
1f08a0efb3 | ||
|
|
6cf85d1d01 | ||
|
|
b5101a460d | ||
|
|
a5784ef09e | ||
|
|
722446510f | ||
|
|
6eb52e200c | ||
|
|
4c4b7023dd | ||
|
|
2d07883cab | ||
|
|
e8ae07aabf | ||
|
|
677e95e2f5 | ||
|
|
122ec25bf0 | ||
|
|
d013ad9eba | ||
|
|
d918ca6fa5 | ||
|
|
852819180f | ||
|
|
218111eb72 | ||
|
|
a714004738 | ||
|
|
6b29f2d83d | ||
|
|
f099def733 | ||
|
|
3e43aed1ac | ||
|
|
46edb55de9 | ||
|
|
506764b3f8 | ||
|
|
db16a861a1 | ||
|
|
c29f3bcf2e | ||
|
|
45d64f229e | ||
|
|
a28d7918d7 | ||
|
|
1531cfb7ee | ||
|
|
16a89f21dc | ||
|
|
79e5160e37 | ||
|
|
510e966dc0 | ||
|
|
8238d3207e | ||
|
|
e37bc4ad40 | ||
|
|
0f78b8b917 | ||
|
|
19061fe04f | ||
|
|
9c901eb65f | ||
|
|
ae33ff3a6e | ||
|
|
59dbd625c1 | ||
|
|
8eb4e355b9 | ||
|
|
8b76b01bfe | ||
|
|
ec57b4b024 | ||
|
|
dd51fc5a4a | ||
|
|
38cd5fd9f6 | ||
|
|
f54d95e803 | ||
|
|
46d6caa47c | ||
|
|
e2adea2188 | ||
|
|
0cabe0cfa7 | ||
|
|
dd5f437871 | ||
|
|
de035c9481 | ||
|
|
d3ed53f11b | ||
|
|
97b65ce19c | ||
|
|
55d0ef40b0 | ||
|
|
bf963253ec | ||
|
|
a6c56416fc | ||
|
|
cd3c482d5e | ||
|
|
c5cbb77fc7 | ||
|
|
d7f21f02b3 | ||
|
|
87dc8ce9b3 | ||
|
|
2e22aa1a9c | ||
|
|
8c56dbf971 | ||
|
|
a78169e9e0 | ||
|
|
39a60c8120 | ||
|
|
c57d8760bd | ||
|
|
3eed2a8780 | ||
|
|
23b0268a7f | ||
|
|
3d0de946bb | ||
|
|
fd0ee469ab | ||
|
|
7fababd44e | ||
|
|
fd9407dfc8 | ||
|
|
074d24df1e | ||
|
|
caf74e507e | ||
|
|
1a1ba48390 | ||
|
|
9724ca4e22 | ||
|
|
4f4676a142 | ||
|
|
866b8ead91 | ||
|
|
aa274960ff | ||
|
|
54d6eb851b | ||
|
|
86d7276f24 | ||
|
|
0918651438 | ||
|
|
e633254ccf | ||
|
|
34f6d94701 | ||
|
|
1e9b8d853d | ||
|
|
2903c56bda | ||
|
|
6b490e33c4 | ||
|
|
891f4a38f6 | ||
|
|
433f763814 | ||
|
|
402b0d0bd1 | ||
|
|
19c3d5b743 | ||
|
|
1212f22858 | ||
|
|
c64aec9ce7 | ||
|
|
272d9297b6 | ||
|
|
f5bfc71899 | ||
|
|
42e1ded55f | ||
|
|
6c410af5a8 | ||
|
|
57ae676181 | ||
|
|
7896ca6e52 | ||
|
|
c4a315dd5e | ||
|
|
bf26959b72 | ||
|
|
a1fb0be1d7 | ||
|
|
bea09a7fa6 | ||
|
|
fb4ee7f0c5 | ||
|
|
842dd85b26 | ||
|
|
f674e83215 | ||
|
|
48d591a010 | ||
|
|
d9d3d3065b | ||
|
|
340a723b87 | ||
|
|
a72ab70fee | ||
|
|
3d053dbb6d | ||
|
|
5baeb14cf3 | ||
|
|
1c0c436757 | ||
|
|
90d720e7a5 | ||
|
|
6ab6a97fe6 | ||
|
|
647a66ebba | ||
|
|
463ccf8664 | ||
|
|
8d7d2be24f | ||
|
|
63a0ffd36a | ||
|
|
a2245f8ff1 | ||
|
|
dd5b105c73 | ||
|
|
74591fb5bd | ||
|
|
9814b2a5f3 | ||
|
|
30091dc29f | ||
|
|
9bf7f302a7 | ||
|
|
ea35a81c42 | ||
|
|
6f4f36c0fb | ||
|
|
a2dbd9bd97 | ||
|
|
f468e653b5 | ||
|
|
b1c7e3393d | ||
|
|
1c0913dc4e | ||
|
|
9f3679880a | ||
|
|
ba809f26d4 | ||
|
|
0a6090bfd8 | ||
|
|
bcac165fab | ||
|
|
843de8b786 | ||
|
|
5ba908fa5f | ||
|
|
5c5197fe4f | ||
|
|
2d450d1315 | ||
|
|
162208f94d | ||
|
|
4e38ced488 | ||
|
|
bc36404c79 | ||
|
|
d3d96b041a | ||
|
|
fa97ef34ca | ||
|
|
37b2d046be | ||
|
|
fa2e407873 | ||
|
|
c61cf57db5 | ||
|
|
ef3690f2db | ||
|
|
14d01ec7bd | ||
|
|
5dc9f70386 | ||
|
|
88e593bf96 | ||
|
|
efde2e6be6 | ||
|
|
5a78a3bc46 | ||
|
|
b276d803d4 | ||
|
|
da51ac430b | ||
|
|
01eb0a3c7f | ||
|
|
dc2b3cf4f8 | ||
|
|
11f2c6b115 | ||
|
|
c4621a5ed2 | ||
|
|
1c953a4d5b | ||
|
|
c184b32d4c | ||
|
|
26d9d4ec2c | ||
|
|
c64ce7aa9c | ||
|
|
4a5057000a | ||
|
|
487601d85d | ||
|
|
acdc4fdfec | ||
|
|
3ccdcbc088 | ||
|
|
d044e33c20 | ||
|
|
9e67691ffb | ||
|
|
6de5d25062 | ||
|
|
d490752d58 | ||
|
|
c24e32041b | ||
|
|
f8a9708aa7 | ||
|
|
75648f99a5 | ||
|
|
4de88828d9 | ||
|
|
acba2fc9b4 | ||
|
|
ec0b6bd2c3 | ||
|
|
822a992f91 | ||
|
|
ad223e2042 | ||
|
|
31110f1caf | ||
|
|
270a111433 | ||
|
|
0f0f787a37 | ||
|
|
4cc43a96f6 |
No files matched your search
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
** Log Files **
|
||||
These should be KeyDB logs, not syslogs or logs from your container manager. If you are reporting a crash there will be a line in your log stating:
|
||||
"=== KEYDB BUG REPORT START: Cut & paste starting from here ==="
|
||||
|
||||
Please copy everything after this line.
|
||||
|
||||
**To Reproduce**
|
||||
Do you know how to reproduce this? If so please provide repro steps.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
.*.swp
|
||||
core
|
||||
*.o
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
@@ -27,7 +28,7 @@ release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
redis.ds
|
||||
src/redis.conf
|
||||
src/keydb.conf
|
||||
src/nodes.conf
|
||||
deps/lua/src/lua
|
||||
deps/lua/src/luac
|
||||
|
||||
+4
-3
@@ -4,11 +4,11 @@ matrix:
|
||||
include:
|
||||
- os: linux
|
||||
script: make
|
||||
env: COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
|
||||
env: COMPILER_NAME=g++-6 CXX=g++-6 CC=gcc-6
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- g++-5
|
||||
- g++-6
|
||||
- nasm
|
||||
- uuid-dev
|
||||
sources: &sources
|
||||
@@ -16,10 +16,11 @@ matrix:
|
||||
- ubuntu-toolchain-r-test
|
||||
- os: linux
|
||||
script: make MALLOC=libc
|
||||
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-stdlib=libc++" LDFLAGS="-stdlib=libc++"
|
||||
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-I/usr/include/libcxxabi/" LDFLAGS="-lc++"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc++abi-dev
|
||||
- clang-3.8
|
||||
- libc++-dev
|
||||
- libc++abi-dev
|
||||
|
||||
Vendored
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"zmalloc.h": "c",
|
||||
"stat.h": "c",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"future": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"scoped_allocator": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp"
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -14,9 +14,7 @@ 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 at the Reddit sub:
|
||||
|
||||
http://reddit.com/r/redis
|
||||
all the support in the mailing list.
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
@@ -24,7 +22,12 @@ 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 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**.
|
||||
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.
|
||||
|
||||
2. If in step 1 you get an acknowledgment from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -35,6 +38,13 @@ each source file that you contribute.
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
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.
|
||||
|
||||
Thanks!
|
||||
+1
-1
@@ -4,7 +4,7 @@ FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
|
||||
build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev \
|
||||
&& apt-get clean
|
||||
|
||||
CMD make
|
||||
@@ -1,38 +1,52 @@
|
||||

|
||||
[](https://travis-ci.org/JohnSully/KeyDB) [](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://stackshare.io/eq-alpha-technology-inc/eq-alpha-technology-inc)
|
||||
|
||||
##### Need Help? Check out our extensive [documentation](https://docs.keydb.dev).
|
||||
|
||||
##### Have feedback? Take our quick survey: https://www.surveymonkey.com/r/Y9XNS93
|
||||
|
||||
What is KeyDB?
|
||||
--------------
|
||||
|
||||
KeyDB is a high performance fork of Redis focusing on multithreading, memory efficiency, and high throughput. In addition to multithreading KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/KeyDB-(Redis-Fork):-Active-Replica-Support), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
|
||||
KeyDB is a high performance fork of Redis with a focus on multithreading, memory efficiency, and high throughput. In addition to multithreading, KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/Active-Replication), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
|
||||
|
||||
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency.
|
||||
KeyDB maintains full compatibility with the Redis protocol, modules, and scripts. This includes the atomicity guarantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
|
||||
|
||||
KeyDB has full compatibility with the Redis protocol, modules, and scripts. This includes full support for transactions, and atomic execution of scripts. For more information see our architecture section below.
|
||||
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
|
||||
|
||||
<img src="https://keydb.dev/assets/img/blog/5x_opspersecVSdatasize.PNG"/>
|
||||
|
||||
See the full benchmark results and setup information here: https://docs.keydb.dev/blog/2019/10/07/blog-post/
|
||||
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focuses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
|
||||
|
||||
Because of this difference of opinion features which are right for KeyDB may not be appropriate for Redis. A fork allows us to explore this new development path and implement features which may never be a part of Redis. KeyDB keeps in sync with upstream Redis changes, and where applicable we upstream bug fixes and changes. It is our hope that the two projects can continue to grow and learn from each other.
|
||||
|
||||
Additional Resources
|
||||
--------------------
|
||||
|
||||
Try our docker container: https://hub.docker.com/r/eqalpha/keydb
|
||||
|
||||
Talk on Gitter: https://gitter.im/KeyDB
|
||||
|
||||
Visit our Website: https://keydb.dev
|
||||
|
||||
See options for channel partners and support contracts: https://keydb.dev/support.html
|
||||
|
||||
Learn with KeyDB’s official documentation site: https://docs.keydb.dev
|
||||
|
||||
[Subscribe to the KeyDB mailing list](https://eqalpha.us20.list-manage.com/subscribe/post?u=978f486c2f95589b24591a9cc&id=4ab9220500)
|
||||
|
||||
Management GUI: We recommend [FastoNoSQL](https://fastonosql.com/) which has official KeyDB support.
|
||||
|
||||
New: Active Replica Support
|
||||
---------------------------
|
||||
|
||||
New! KeyDB now has support for Active Replicas. This feature greatly simplifies hot-spare failover and allows you to distribute writes over replicas instead of just a single master. For more information [see the wiki page](https://github.com/JohnSully/KeyDB/wiki/KeyDB-(Redis-Fork):-Active-Replica-Support).
|
||||
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
The Redis maintainers have continually reiterated that they do not plan to support multithreading. While we have great respect for the redis team, we feel the analysis justifying this decision is incorrect. In addition we wanted open source implementations of features currently only available in proprietary modules. We feel a fork is the best way to accelerate development in the areas of most interest to us.
|
||||
|
||||
We plan to track the Redis repo closely and hope our projects can learn from each other.
|
||||
|
||||
Benchmarking KeyDB
|
||||
------------------
|
||||
|
||||
<img src=https://cdn-images-1.medium.com/max/1400/1*s7mTb7Qb0kxc951mz8bdgA.png width=420 height=300/><img src=https://cdn-images-1.medium.com/max/1400/1*R00A5U4AFGohGOYHMfT6fA.png height=300/>
|
||||
|
||||
Please note keydb-benchmark and redis-benchmark are currently single threaded and too slow to properly benchmark KeyDB. We recommend using a redis cluster benchmark tool such as [memtier](https://github.com/RedisLabs/memtier_benchmark). Please ensure your machine has enough cores for both KeyDB and memteir if testing locally. KeyDB expects exclusive use of any cores assigned to it.
|
||||
|
||||
For more details on how we benchmarked KeyDB along with performance numbers check out our blog post: [Redis Should Be Multithreaded](https://medium.com/@john_63123/redis-should-be-multi-threaded-e28319cab744?source=friends_link&sk=7ce8e9fe3ec8224a4d27ef075d085457)
|
||||
@@ -55,25 +69,32 @@ If you would like to use the [FLASH backed](https://github.com/JohnSully/KeyDB/w
|
||||
|
||||
If you would like KeyDB to dump and load directly to AWS S3 this option specifies the bucket. Using this option with the traditional RDB options will result in KeyDB backing up twice to both locations. If both are specified KeyDB will first attempt to load from the local dump file and if that fails load from S3. This requires the AWS CLI tools to be installed and configured which are used under the hood to transfer the data.
|
||||
|
||||
active-replica yes
|
||||
|
||||
If you are using active-active replication set `active-replica` option to “yes”. This will enable both instances to accept reads and writes while remaining synced. [Click here](https://docs.keydb.dev/docs/active-rep/) to see more on active-rep in our docs section. There are also [docker examples]( https://docs.keydb.dev/docs/docker-active-rep/) on docs.
|
||||
|
||||
All other configuration options behave as you'd expect. Your existing configuration files should continue to work unchanged.
|
||||
|
||||
Building KeyDB
|
||||
--------------
|
||||
|
||||
KeyDB can be compiled and is tested for use on Linux. KeyDB currently relies on SO_REUSEADDR's load balancing behavior which is available only in Linux. When we support marshalling connections across threads we plan to support other operating systems such as FreeBSD.
|
||||
KeyDB can be compiled and is tested for use on Linux. KeyDB currently relies on SO_REUSEPORT's load balancing behavior which is available only in Linux. When we support marshalling connections across threads we plan to support other operating systems such as FreeBSD.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
% sudo apt install build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev
|
||||
% sudo apt install build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev
|
||||
|
||||
Compiling is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
You can enable flash support with (Note: autoconf and autotools must be installed):
|
||||
You can enable flash support with:
|
||||
|
||||
% make MALLOC=memkind
|
||||
|
||||
***Note that the following dependencies may be needed:
|
||||
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
---------
|
||||
|
||||
@@ -176,7 +197,7 @@ then in another terminal try the following:
|
||||
(integer) 2
|
||||
keydb>
|
||||
|
||||
You can find the list of all the available commands at http://redis.io/commands.
|
||||
You can find the list of all the available commands at https://docs.keydb.dev/docs/commands/
|
||||
|
||||
Installing KeyDB
|
||||
-----------------
|
||||
@@ -219,23 +240,18 @@ Future work:
|
||||
|
||||
Docker Build
|
||||
------------
|
||||
|
||||
Run the following commands for a full source download and build:
|
||||
|
||||
Build the latest binaries from the github unstable branch within a docker container. Note this is built for Ubuntu 18.04.
|
||||
Simply make a directory you would like to have the latest binaries dumped in, then run the following commmand with your updated path:
|
||||
```
|
||||
git clone git@github.com:JohnSully/KeyDB.git
|
||||
docker run -it --rm -v `pwd`/KeyDB:/build -w /build devopsdood/keydb-builder make
|
||||
$ docker run -it --rm -v /path-to-dump-binaries:/keydb_bin eqalpha/keydb-build-bin
|
||||
```
|
||||
You should receive the following files: keydb-benchmark, keydb-check-aof, keydb-check-rdb, keydb-cli, keydb-sentinel, keydb-server
|
||||
|
||||
Then you have fresh binaries built, you can also pass any other options to the make command above after the word make. E.g.
|
||||
|
||||
```docker run -it --rm -v `pwd`/KeyDB:/build -w /build devopsdood/keydb-builder make MAllOC=memkind```
|
||||
|
||||
The above commands will build you binaries in the src directory. Standard `make install` without Docker command will work after if you wish to install
|
||||
|
||||
If you'd prefer you can build the Dockerfile in the repo instead of pulling the above container for use:
|
||||
|
||||
`docker build -t KeyDB .`
|
||||
If you are looking to enable flash support with the build (make MALLOC=memkind) then use the following command:
|
||||
```
|
||||
$ docker run -it --rm -v /path-to-dump-binaries:/keydb_bin eqalpha/keydb-build-bin:flash
|
||||
```
|
||||
Please note that you will need libcurl4-openssl-dev in order to run keydb. With flash version you may need libnuma-dev and libtool installed in order to run the binaries. Keep this in mind especially when running in a container. For a copy of all our Dockerfiles, please see them on [docs]( https://docs.keydb.dev/docs/dockerfiles/).
|
||||
|
||||
Code contributions
|
||||
-----------------
|
||||
|
||||
Vendored
+1
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
="repl-stream-db"
|
||||
="repl-id"
|
||||
="repl-offset"
|
||||
="lua"
|
||||
="redis-ver"
|
||||
="ctime"
|
||||
="used-mem"
|
||||
="aof-preamble"
|
||||
="redis-bits"
|
||||
="mvcc-tstamp"
|
||||
="keydb-subexpire-key"
|
||||
="keydb-subexpire-when"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+128
-127
@@ -1,9 +1,9 @@
|
||||
# Redis configuration file example.
|
||||
# KeyDB configuration file example.
|
||||
#
|
||||
# Note that in order to read the configuration file, Redis must be
|
||||
# Note that in order to read the configuration file, KeyDB must be
|
||||
# started with the file path as first argument:
|
||||
#
|
||||
# ./keydb-server /path/to/redis.conf
|
||||
# ./keydb-server /path/to/keydb.conf
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specify
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
@@ -20,12 +20,12 @@
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all Redis servers but also need
|
||||
# have a standard template that goes to all KeyDB servers but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# from admin or Redis Sentinel. Since Redis always uses the last processed
|
||||
# from admin or KeyDB Sentinel. Since KeyDB always uses the last processed
|
||||
# line as value of a configuration directive, you'd better put includes
|
||||
# at the beginning of this file to avoid overwriting config change at runtime.
|
||||
#
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
################################## NETWORK #####################################
|
||||
|
||||
# By default, if no "bind" configuration directive is specified, Redis listens
|
||||
# By default, if no "bind" configuration directive is specified, KeyDB listens
|
||||
# for connections from all the network interfaces available on the server.
|
||||
# It is possible to listen to just one or multiple selected interfaces using
|
||||
# the "bind" configuration directive, followed by one or more IP addresses.
|
||||
@@ -55,11 +55,11 @@
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1 ::1
|
||||
#
|
||||
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
|
||||
# ~~~ WARNING ~~~ If the computer running KeyDB is directly exposed to the
|
||||
# internet, binding to all the interfaces is dangerous and will expose the
|
||||
# instance to everybody on the internet. So by default we uncomment the
|
||||
# following bind directive, that will force Redis to listen only into
|
||||
# the IPv4 loopback interface address (this means Redis will be able to
|
||||
# following bind directive, that will force KeyDB to listen only into
|
||||
# the IPv4 loopback interface address (this means KeyDB will be able to
|
||||
# accept connections only from clients running into the same computer it
|
||||
# is running).
|
||||
#
|
||||
@@ -69,7 +69,7 @@
|
||||
bind 127.0.0.1
|
||||
|
||||
# Protected mode is a layer of security protection, in order to avoid that
|
||||
# Redis instances left open on the internet are accessed and exploited.
|
||||
# KeyDB instances left open on the internet are accessed and exploited.
|
||||
#
|
||||
# When protected mode is on and if:
|
||||
#
|
||||
@@ -82,13 +82,13 @@ bind 127.0.0.1
|
||||
# sockets.
|
||||
#
|
||||
# By default protected mode is enabled. You should disable it only if
|
||||
# you are sure you want clients from other hosts to connect to Redis
|
||||
# you are sure you want clients from other hosts to connect to KeyDB
|
||||
# even if no authentication is configured, nor a specific set of interfaces
|
||||
# are explicitly listed using the "bind" directive.
|
||||
protected-mode yes
|
||||
|
||||
# Accept connections on the specified port, default is 6379 (IANA #815344).
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
# If port 0 is specified KeyDB will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# TCP listen() backlog.
|
||||
@@ -103,10 +103,10 @@ tcp-backlog 511
|
||||
# Unix socket.
|
||||
#
|
||||
# Specify the path for the Unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# incoming connections. There is no default, so KeyDB will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocket /tmp/keydb.sock
|
||||
# unixsocketperm 700
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
@@ -126,19 +126,19 @@ timeout 0
|
||||
# On other kernels the period depends on the kernel configuration.
|
||||
#
|
||||
# A reasonable value for this option is 300 seconds, which is the new
|
||||
# Redis default starting with Redis 3.2.1.
|
||||
# KeyDB default starting with Redis 3.2.1.
|
||||
tcp-keepalive 300
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
# By default KeyDB does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that KeyDB will write a pid file in /var/run/keydb.pid when daemonized.
|
||||
daemonize no
|
||||
|
||||
# If you run Redis from upstart or systemd, Redis can interact with your
|
||||
# If you run KeyDB from upstart or systemd, KeyDB can interact with your
|
||||
# supervision tree. Options:
|
||||
# supervised no - no supervision interaction
|
||||
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
|
||||
# supervised upstart - signal upstart by putting KeyDB into SIGSTOP mode
|
||||
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
|
||||
# supervised auto - detect upstart or systemd method based on
|
||||
# UPSTART_JOB or NOTIFY_SOCKET environment variables
|
||||
@@ -146,16 +146,16 @@ daemonize no
|
||||
# They do not enable continuous liveness pings back to your supervisor.
|
||||
supervised no
|
||||
|
||||
# If a pid file is specified, Redis writes it where specified at startup
|
||||
# If a pid file is specified, KeyDB writes it where specified at startup
|
||||
# and removes it at exit.
|
||||
#
|
||||
# When the server runs non daemonized, no pid file is created if none is
|
||||
# specified in the configuration. When the server is daemonized, the pid file
|
||||
# is used even if not specified, defaulting to "/var/run/redis.pid".
|
||||
# is used even if not specified, defaulting to "/var/run/keydb.pid".
|
||||
#
|
||||
# Creating a pid file is best effort: if Redis is not able to create it
|
||||
# Creating a pid file is best effort: if KeyDB is not able to create it
|
||||
# nothing bad happens, the server will start and run normally.
|
||||
pidfile /var/run/redis_6379.pid
|
||||
pidfile /var/run/keydb_6379.pid
|
||||
|
||||
# Specify the server verbosity level.
|
||||
# This can be one of:
|
||||
@@ -166,7 +166,7 @@ pidfile /var/run/redis_6379.pid
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Redis to log on the standard output. Note that if you use standard
|
||||
# KeyDB to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
@@ -175,7 +175,7 @@ logfile ""
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
# syslog-ident keydb
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
@@ -185,7 +185,7 @@ logfile ""
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
# By default Redis shows an ASCII art logo only when started to log to the
|
||||
# By default KeyDB shows an ASCII art logo only when started to log to the
|
||||
# standard output and if the standard output is a TTY. Basically this means
|
||||
# that normally a logo is displayed only in interactive sessions.
|
||||
#
|
||||
@@ -219,17 +219,17 @@ save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# By default Redis will stop accepting writes if RDB snapshots are enabled
|
||||
# By default KeyDB will stop accepting writes if RDB snapshots are enabled
|
||||
# (at least one save point) and the latest background save failed.
|
||||
# This will make the user aware (in a hard way) that data is not persisting
|
||||
# on disk properly, otherwise chances are that no one will notice and some
|
||||
# disaster will happen.
|
||||
#
|
||||
# If the background saving process will start working again Redis will
|
||||
# If the background saving process will start working again KeyDB will
|
||||
# automatically allow writes again.
|
||||
#
|
||||
# However if you have setup your proper monitoring of the Redis server
|
||||
# and persistence, you may want to disable this feature so that Redis will
|
||||
# However if you have setup your proper monitoring of the KeyDB server
|
||||
# and persistence, you may want to disable this feature so that KeyDB will
|
||||
# continue to work as usual even if there are problems with disk,
|
||||
# permissions, and so forth.
|
||||
stop-writes-on-bgsave-error yes
|
||||
@@ -264,18 +264,18 @@ dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
# Master-Replica replication. Use replicaof to make a KeyDB instance a copy of
|
||||
# another KeyDB server. A few things to understand ASAP about KeyDB replication.
|
||||
#
|
||||
# +------------------+ +---------------+
|
||||
# | Master | ---> | Replica |
|
||||
# | (receive writes) | | (exact copy) |
|
||||
# +------------------+ +---------------+
|
||||
#
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# 1) KeyDB replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of replicas.
|
||||
# 2) Redis replicas are able to perform a partial resynchronization with the
|
||||
# 2) KeyDB replicas are able to perform a partial resynchronization with the
|
||||
# master if the replication link is lost for a relatively small amount of
|
||||
# time. You may want to configure the replication backlog size (see the next
|
||||
# sections of this file) with a sensible value depending on your needs.
|
||||
@@ -292,7 +292,7 @@ dir ./
|
||||
#
|
||||
# masterauth <master-password>
|
||||
#
|
||||
# However this is not enough if you are using Redis ACLs (for Redis version
|
||||
# However this is not enough if you are using KeyDB ACLs (for Redis version
|
||||
# 6 or greater), and the default user is not capable of running the PSYNC
|
||||
# command and/or other commands needed for replication. In this case it's
|
||||
# better to configure a special user to use with replication, and specify the
|
||||
@@ -345,10 +345,10 @@ replica-read-only yes
|
||||
# synchronization". An RDB file is transmitted from the master to the replicas.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
# 1) Disk-backed: The KeyDB master creates a new process that writes the RDB
|
||||
# file on disk. Later the file is transferred by the parent
|
||||
# process to the replicas incrementally.
|
||||
# 2) Diskless: The Redis master creates a new process that directly writes the
|
||||
# 2) Diskless: The KeyDB master creates a new process that directly writes the
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
@@ -397,7 +397,7 @@ repl-diskless-sync-delay 5
|
||||
|
||||
# Disable TCP_NODELAY on the replica socket after SYNC?
|
||||
#
|
||||
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||
# If you select "yes" KeyDB will use a smaller number of TCP packets and
|
||||
# less bandwidth to send data to replicas. But this can add a delay for
|
||||
# the data to appear on the replica side, up to 40 milliseconds with
|
||||
# Linux kernels using a default configuration.
|
||||
@@ -436,8 +436,8 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The replica priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a replica to promote into a
|
||||
# The replica priority is an integer number published by KeyDB in the INFO output.
|
||||
# It is used by KeyDB Sentinel in order to select a replica to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
#
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
@@ -446,7 +446,7 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# However a special priority of 0 marks the replica as not able to perform the
|
||||
# role of master, so a replica with priority of 0 will never be selected by
|
||||
# Redis Sentinel for promotion.
|
||||
# KeyDB Sentinel for promotion.
|
||||
#
|
||||
# By default the priority is 100.
|
||||
replica-priority 100
|
||||
@@ -473,10 +473,10 @@ replica-priority 100
|
||||
# By default min-replicas-to-write is set to 0 (feature disabled) and
|
||||
# min-replicas-max-lag is set to 10.
|
||||
|
||||
# A Redis master is able to list the address and port of the attached
|
||||
# A KeyDB master is able to list the address and port of the attached
|
||||
# replicas in different ways. For example the "INFO replication" section
|
||||
# offers this information, which is used, among other tools, by
|
||||
# Redis Sentinel in order to discover replica instances.
|
||||
# KeyDB Sentinel in order to discover replica instances.
|
||||
# Another place where this info is available is in the output of the
|
||||
# "ROLE" command of a master.
|
||||
#
|
||||
@@ -504,7 +504,7 @@ replica-priority 100
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# Warning: since KeyDB is pretty fast an outside user can try up to
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
@@ -512,7 +512,7 @@ replica-priority 100
|
||||
# can be easily a long string from /dev/urandom or whatever, so by using a
|
||||
# long and unguessable password no brute force attack will be possible.
|
||||
|
||||
# Redis ACL users are defined in the following format:
|
||||
# KeyDB ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
@@ -539,7 +539,7 @@ replica-priority 100
|
||||
# +@<category> Allow the execution of all the commands in such category
|
||||
# with valid categories are like @admin, @set, @sortedset, ...
|
||||
# and so forth, see the full list in the server.c file where
|
||||
# the Redis command table is described and defined.
|
||||
# the KeyDB command table is described and defined.
|
||||
# The special category @all means all the commands, but currently
|
||||
# present in the server, and that will be loaded in the future
|
||||
# via modules.
|
||||
@@ -606,9 +606,9 @@ replica-priority 100
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
# format that is used inside redis.conf to describe users.
|
||||
# format that is used inside keydb.conf to describe users.
|
||||
#
|
||||
# aclfile /etc/redis/users.acl
|
||||
# aclfile /etc/keydb/users.acl
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
|
||||
# layer on top of the new ACL system. The option effect will be just setting
|
||||
@@ -646,12 +646,12 @@ replica-priority 100
|
||||
################################### CLIENTS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default
|
||||
# this limit is set to 10000 clients, however if the Redis server is not
|
||||
# this limit is set to 10000 clients, however if the KeyDB server is not
|
||||
# able to configure the process file limit to allow for the specified limit
|
||||
# the max number of allowed clients is set to the current file limit
|
||||
# minus 32 (as Redis reserves a few file descriptors for internal uses).
|
||||
# minus 32 (as KeyDB reserves a few file descriptors for internal uses).
|
||||
#
|
||||
# Once the limit is reached Redis will close all the new connections sending
|
||||
# Once the limit is reached KeyDB will close all the new connections sending
|
||||
# an error 'max number of clients reached'.
|
||||
#
|
||||
# maxclients 10000
|
||||
@@ -659,15 +659,15 @@ replica-priority 100
|
||||
############################## MEMORY MANAGEMENT ################################
|
||||
|
||||
# Set a memory usage limit to the specified amount of bytes.
|
||||
# When the memory limit is reached Redis will try to remove keys
|
||||
# When the memory limit is reached KeyDB will try to remove keys
|
||||
# according to the eviction policy selected (see maxmemory-policy).
|
||||
#
|
||||
# If Redis can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', Redis will start to reply with errors to commands
|
||||
# If KeyDB can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', KeyDB will start to reply with errors to commands
|
||||
# that would use more memory, like SET, LPUSH, and so on, and will continue
|
||||
# to reply to read-only commands like GET.
|
||||
#
|
||||
# This option is usually useful when using Redis as an LRU or LFU cache, or to
|
||||
# This option is usually useful when using KeyDB as an LRU or LFU cache, or to
|
||||
# set a hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have replicas attached to an instance with maxmemory on,
|
||||
@@ -683,7 +683,7 @@ replica-priority 100
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# MAXMEMORY POLICY: how KeyDB will select what to remove when maxmemory
|
||||
# is reached. You can select among five behaviors:
|
||||
#
|
||||
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
|
||||
@@ -701,7 +701,7 @@ replica-priority 100
|
||||
# Both LRU, LFU and volatile-ttl are implemented using approximated
|
||||
# randomized algorithms.
|
||||
#
|
||||
# Note: with any of the above policies, Redis will return an error on write
|
||||
# Note: with any of the above policies, KeyDB will return an error on write
|
||||
# operations, when there are no suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing these commands are: set setnx setex append
|
||||
@@ -716,7 +716,7 @@ replica-priority 100
|
||||
|
||||
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can tune it for speed or
|
||||
# accuracy. For default Redis will check five keys and pick the one that was
|
||||
# accuracy. For default KeyDB will check five keys and pick the one that was
|
||||
# used less recently, you can change the sample size using the following
|
||||
# configuration directive.
|
||||
#
|
||||
@@ -747,16 +747,16 @@ replica-priority 100
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
# KeyDB has two primitives to delete keys. One is called DEL and is a blocking
|
||||
# deletion of the object. It means that the server stops processing new commands
|
||||
# in order to reclaim all the memory associated with an object in a synchronous
|
||||
# way. If the key deleted is associated with a small object, the time needed
|
||||
# in order to execute the DEL command is very small and comparable to most other
|
||||
# O(1) or O(log_N) commands in Redis. However if the key is associated with an
|
||||
# O(1) or O(log_N) commands in KeyDB. However if the key is associated with an
|
||||
# aggregated value containing millions of elements, the server can block for
|
||||
# a long time (even seconds) in order to complete the operation.
|
||||
#
|
||||
# For the above reasons Redis also offers non blocking deletion primitives
|
||||
# For the above reasons KeyDB also offers non blocking deletion primitives
|
||||
# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and
|
||||
# FLUSHDB commands, in order to reclaim memory in background. Those commands
|
||||
# are executed in constant time. Another thread will incrementally free the
|
||||
@@ -764,9 +764,9 @@ replica-priority 100
|
||||
#
|
||||
# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
|
||||
# It's up to the design of the application to understand when it is a good
|
||||
# idea to use one or the other. However the Redis server sometimes has to
|
||||
# idea to use one or the other. However the KeyDB server sometimes has to
|
||||
# delete keys or flush the whole database as a side effect of other operations.
|
||||
# Specifically Redis deletes objects independently of a user call in the
|
||||
# Specifically KeyDB deletes objects independently of a user call in the
|
||||
# following scenarios:
|
||||
#
|
||||
# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
|
||||
@@ -796,20 +796,20 @@ replica-lazy-flush no
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. This mode is
|
||||
# good enough in many applications, but an issue with the Redis process or
|
||||
# By default KeyDB asynchronously dumps the dataset on disk. This mode is
|
||||
# good enough in many applications, but an issue with the KeyDB process or
|
||||
# a power outage may result into a few minutes of writes lost (depending on
|
||||
# the configured save points).
|
||||
#
|
||||
# The Append Only File is an alternative persistence mode that provides
|
||||
# much better durability. For instance using the default data fsync policy
|
||||
# (see later in the config file) Redis can lose just one second of writes in a
|
||||
# (see later in the config file) KeyDB can lose just one second of writes in a
|
||||
# dramatic event like a server power outage, or a single write if something
|
||||
# wrong with the Redis process itself happens, but the operating system is
|
||||
# wrong with the KeyDB process itself happens, but the operating system is
|
||||
# still running correctly.
|
||||
#
|
||||
# AOF and RDB persistence can be enabled at the same time without problems.
|
||||
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||
# If the AOF is enabled on startup KeyDB will load the AOF, that is the file
|
||||
# with the better durability guarantees.
|
||||
#
|
||||
# Please check http://redis.io/topics/persistence for more information.
|
||||
@@ -824,7 +824,7 @@ appendfilename "appendonly.aof"
|
||||
# instead of waiting for more data in the output buffer. Some OS will really flush
|
||||
# data on disk, some other OS will just try to do it ASAP.
|
||||
#
|
||||
# Redis supports three different modes:
|
||||
# KeyDB supports three different modes:
|
||||
#
|
||||
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||
# always: fsync after every write to the append only log. Slow, Safest.
|
||||
@@ -850,7 +850,7 @@ appendfsync everysec
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# KeyDB may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
@@ -858,7 +858,7 @@ appendfsync everysec
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving, the durability of Redis is
|
||||
# This means that while another child is saving, the durability of KeyDB is
|
||||
# the same as "appendfsync none". In practical terms, this means that it is
|
||||
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
@@ -869,10 +869,10 @@ appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
# Redis is able to automatically rewrite the log file implicitly calling
|
||||
# KeyDB is able to automatically rewrite the log file implicitly calling
|
||||
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
|
||||
#
|
||||
# This is how it works: Redis remembers the size of the AOF file after the
|
||||
# This is how it works: KeyDB remembers the size of the AOF file after the
|
||||
# latest rewrite (if no rewrite has happened since the restart, the size of
|
||||
# the AOF at startup is used).
|
||||
#
|
||||
@@ -888,19 +888,19 @@ no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# An AOF file may be found to be truncated at the end during the Redis
|
||||
# An AOF file may be found to be truncated at the end during the KeyDB
|
||||
# startup process, when the AOF data gets loaded back into memory.
|
||||
# This may happen when the system where Redis is running
|
||||
# This may happen when the system where KeyDB is running
|
||||
# crashes, especially when an ext4 filesystem is mounted without the
|
||||
# data=ordered option (however this can't happen when Redis itself
|
||||
# data=ordered option (however this can't happen when KeyDB itself
|
||||
# crashes or aborts but the operating system still works correctly).
|
||||
#
|
||||
# Redis can either exit with an error when this happens, or load as much
|
||||
# KeyDB can either exit with an error when this happens, or load as much
|
||||
# data as possible (the default now) and start if the AOF file is found
|
||||
# to be truncated at the end. The following option controls this behavior.
|
||||
#
|
||||
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
|
||||
# the Redis server starts emitting a log to inform the user of the event.
|
||||
# the KeyDB server starts emitting a log to inform the user of the event.
|
||||
# Otherwise if the option is set to no, the server aborts with an error
|
||||
# and refuses to start. When the option is set to no, the user requires
|
||||
# to fix the AOF file using the "keydb-check-aof" utility before to restart
|
||||
@@ -908,17 +908,17 @@ auto-aof-rewrite-min-size 64mb
|
||||
#
|
||||
# Note that if the AOF file will be found to be corrupted in the middle
|
||||
# the server will still exit with an error. This option only applies when
|
||||
# Redis will try to read more data from the AOF file but not enough bytes
|
||||
# KeyDB will try to read more data from the AOF file but not enough bytes
|
||||
# will be found.
|
||||
aof-load-truncated yes
|
||||
|
||||
# When rewriting the AOF file, Redis is able to use an RDB preamble in the
|
||||
# When rewriting the AOF file, KeyDB is able to use an RDB preamble in the
|
||||
# AOF file for faster rewrites and recoveries. When this option is turned
|
||||
# on the rewritten AOF file is composed of two different stanzas:
|
||||
#
|
||||
# [RDB file][AOF tail]
|
||||
#
|
||||
# When loading Redis recognizes that the AOF file starts with the "REDIS"
|
||||
# When loading KeyDB recognizes that the AOF file starts with the "REDIS"
|
||||
# string and loads the prefixed RDB file, and continues loading the AOF
|
||||
# tail.
|
||||
aof-use-rdb-preamble yes
|
||||
@@ -927,7 +927,7 @@ aof-use-rdb-preamble yes
|
||||
|
||||
# Max execution time of a Lua script in milliseconds.
|
||||
#
|
||||
# If the maximum execution time is reached Redis will log that a script is
|
||||
# If the maximum execution time is reached KeyDB will log that a script is
|
||||
# still in execution after the maximum allowed time and will start to
|
||||
# reply to queries with an error.
|
||||
#
|
||||
@@ -941,23 +941,17 @@ aof-use-rdb-preamble yes
|
||||
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||
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
|
||||
################################ KEYDB CLUSTER ###############################
|
||||
|
||||
# Normal KeyDB instances can't be part of a KeyDB Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a KeyDB instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
#
|
||||
# cluster-enabled yes
|
||||
|
||||
# Every cluster node has a cluster configuration file. This file is not
|
||||
# intended to be edited by hand. It is created and updated by Redis nodes.
|
||||
# Every Redis Cluster node requires a different cluster configuration file.
|
||||
# intended to be edited by hand. It is created and updated by KeyDB nodes.
|
||||
# Every KeyDB Cluster node requires a different cluster configuration file.
|
||||
# Make sure that instances running in the same system do not have
|
||||
# overlapping cluster configuration file names.
|
||||
#
|
||||
@@ -1033,7 +1027,7 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-migration-barrier 1
|
||||
|
||||
# By default Redis Cluster nodes stop accepting queries if they detect there
|
||||
# By default KeyDB Cluster nodes stop accepting queries if they detect there
|
||||
# is at least an hash slot uncovered (no available node is serving it).
|
||||
# This way if the cluster is partially down (for example a range of hash slots
|
||||
# are no longer covered) all the cluster becomes, eventually, unavailable.
|
||||
@@ -1061,11 +1055,11 @@ lua-time-limit 5000
|
||||
|
||||
########################## CLUSTER DOCKER/NAT support ########################
|
||||
|
||||
# In certain deployments, Redis Cluster nodes address discovery fails, because
|
||||
# In certain deployments, KeyDB Cluster nodes address discovery fails, because
|
||||
# addresses are NAT-ted or because ports are forwarded (the typical case is
|
||||
# Docker and other containers).
|
||||
#
|
||||
# In order to make Redis Cluster working in such environments, a static
|
||||
# In order to make KeyDB Cluster working in such environments, a static
|
||||
# configuration where each node knows its public address is needed. The
|
||||
# following two options are used for this scope, and are:
|
||||
#
|
||||
@@ -1078,7 +1072,7 @@ lua-time-limit 5000
|
||||
# so that other nodes will be able to correctly map the address of the node
|
||||
# publishing the information.
|
||||
#
|
||||
# If the above options are not used, the normal Redis Cluster auto-detection
|
||||
# If the above options are not used, the normal KeyDB Cluster auto-detection
|
||||
# will be used instead.
|
||||
#
|
||||
# Note that when remapped, the bus port may not be at the fixed offset of
|
||||
@@ -1094,14 +1088,14 @@ lua-time-limit 5000
|
||||
|
||||
################################## SLOW LOG ###################################
|
||||
|
||||
# The Redis Slow Log is a system to log queries that exceeded a specified
|
||||
# The KeyDB Slow Log is a system to log queries that exceeded a specified
|
||||
# execution time. The execution time does not include the I/O operations
|
||||
# like talking with the client, sending the reply and so forth,
|
||||
# but just the time needed to actually execute the command (this is the only
|
||||
# stage of command execution where the thread is blocked and can not serve
|
||||
# other requests in the meantime).
|
||||
#
|
||||
# You can configure the slow log with two parameters: one tells Redis
|
||||
# You can configure the slow log with two parameters: one tells KeyDB
|
||||
# what is the execution time, in microseconds, to exceed in order for the
|
||||
# command to get logged, and the other parameter is the length of the
|
||||
# slow log. When a new command is logged the oldest one is removed from the
|
||||
@@ -1118,9 +1112,9 @@ slowlog-max-len 128
|
||||
|
||||
################################ LATENCY MONITOR ##############################
|
||||
|
||||
# The Redis latency monitoring subsystem samples different operations
|
||||
# The KeyDB latency monitoring subsystem samples different operations
|
||||
# at runtime in order to collect data related to possible sources of
|
||||
# latency of a Redis instance.
|
||||
# latency of a KeyDB instance.
|
||||
#
|
||||
# Via the LATENCY command this information is available to the user that can
|
||||
# print graphs and obtain reports.
|
||||
@@ -1139,7 +1133,7 @@ latency-monitor-threshold 0
|
||||
|
||||
############################# EVENT NOTIFICATION ##############################
|
||||
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# KeyDB can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
@@ -1149,7 +1143,7 @@ latency-monitor-threshold 0
|
||||
# PUBLISH __keyspace@0__:foo del
|
||||
# PUBLISH __keyevent@0__:del foo
|
||||
#
|
||||
# It is possible to select the events that Redis will notify among a set
|
||||
# It is possible to select the events that KeyDB will notify among a set
|
||||
# of classes. Every class is identified by a single character:
|
||||
#
|
||||
# K Keyspace events, published with __keyspace@<db>__ prefix.
|
||||
@@ -1185,12 +1179,12 @@ notify-keyspace-events ""
|
||||
|
||||
############################### GOPHER SERVER #################################
|
||||
|
||||
# Redis contains an implementation of the Gopher protocol, as specified in
|
||||
# KeyDB contains an implementation of the Gopher protocol, as specified in
|
||||
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
||||
#
|
||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||
# to the web, and the implementation both server and client side is so simple
|
||||
# that the Redis server has just 100 lines of code in order to implement this
|
||||
# that the KeyDB server has just 100 lines of code in order to implement this
|
||||
# support.
|
||||
#
|
||||
# What do you do with Gopher nowadays? Well Gopher never *really* died, and
|
||||
@@ -1200,18 +1194,18 @@ notify-keyspace-events ""
|
||||
# controlled, and it's cool to create an alternative space for people that
|
||||
# want a bit of fresh air.
|
||||
#
|
||||
# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
|
||||
# Anyway for the 10nth birthday of the KeyDB, we gave it the Gopher protocol
|
||||
# as a gift.
|
||||
#
|
||||
# --- HOW IT WORKS? ---
|
||||
#
|
||||
# The Redis Gopher support uses the inline protocol of Redis, and specifically
|
||||
# The KeyDB Gopher support uses the inline protocol of KeyDB, and specifically
|
||||
# two kind of inline requests that were anyway illegal: an empty request
|
||||
# or any request that starts with "/" (there are no Redis commands starting
|
||||
# or any request that starts with "/" (there are no KeyDB commands starting
|
||||
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
|
||||
# path of the Gopher protocol implementation and are served as usually as well.
|
||||
#
|
||||
# If you open a connection to Redis when Gopher is enabled and send it
|
||||
# If you open a connection to KeyDB when Gopher is enabled and send it
|
||||
# a string like "/foo", if there is a key named "/foo" it is served via the
|
||||
# Gopher protocol.
|
||||
#
|
||||
@@ -1222,7 +1216,7 @@ notify-keyspace-events ""
|
||||
#
|
||||
# --- SECURITY WARNING ---
|
||||
#
|
||||
# If you plan to put Redis on the internet in a publicly accessible address
|
||||
# If you plan to put KeyDB on the internet in a publicly accessible address
|
||||
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
||||
# Once a password is set:
|
||||
#
|
||||
@@ -1316,8 +1310,8 @@ stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
# order to help rehashing the main KeyDB hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation KeyDB uses (see dict.c)
|
||||
# performs a lazy rehashing: the more operation you run into a hash table
|
||||
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
@@ -1328,7 +1322,7 @@ stream-node-max-entries 100
|
||||
#
|
||||
# If unsure:
|
||||
# use "activerehashing no" if you have hard latency requirements and it is
|
||||
# not a good thing in your environment that Redis can reply from time to time
|
||||
# not a good thing in your environment that KeyDB can reply from time to time
|
||||
# to queries with 2 milliseconds delay.
|
||||
#
|
||||
# use "activerehashing yes" if you don't have such hard requirements but
|
||||
@@ -1380,21 +1374,21 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
#
|
||||
# client-query-buffer-limit 1gb
|
||||
|
||||
# In the Redis protocol, bulk requests, that are, elements representing single
|
||||
# In the KeyDB protocol, bulk requests, that are, elements representing single
|
||||
# strings, are normally limited ot 512 mb. However you can change this limit
|
||||
# here.
|
||||
#
|
||||
# proto-max-bulk-len 512mb
|
||||
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# KeyDB calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeout, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are performed with the same frequency, but Redis checks for
|
||||
# Not all tasks are performed with the same frequency, but KeyDB checks for
|
||||
# tasks to perform according to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
# Redis is idle, but at the same time will make Redis more responsive when
|
||||
# KeyDB is idle, but at the same time will make KeyDB more responsive when
|
||||
# there are many keys expiring at the same time, and timeouts may be
|
||||
# handled with more precision.
|
||||
#
|
||||
@@ -1408,7 +1402,7 @@ hz 10
|
||||
# avoid too many clients are processed for each background task invocation
|
||||
# in order to avoid latency spikes.
|
||||
#
|
||||
# Since the default HZ value by default is conservatively set to 10, Redis
|
||||
# Since the default HZ value by default is conservatively set to 10, KeyDB
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
#
|
||||
@@ -1425,22 +1419,22 @@ dynamic-hz yes
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
# When redis saves RDB file, if the following option is enabled
|
||||
# When KeyDB saves RDB file, if the following option is enabled
|
||||
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
# big latency spikes.
|
||||
rdb-save-incremental-fsync yes
|
||||
|
||||
# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
|
||||
# KeyDB LFU eviction (see maxmemory setting) can be tuned. However it is a good
|
||||
# idea to start with the default settings and only change them after investigating
|
||||
# how to improve the performances and how the keys LFU change over time, which
|
||||
# is possible to inspect via the OBJECT FREQ command.
|
||||
#
|
||||
# There are two tunable parameters in the Redis LFU implementation: the
|
||||
# There are two tunable parameters in the KeyDB LFU implementation: the
|
||||
# counter logarithm factor and the counter decay time. It is important to
|
||||
# understand what the two parameters mean before changing them.
|
||||
#
|
||||
# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis
|
||||
# The LFU counter is just 8 bits per key, it's maximum value is 255, so KeyDB
|
||||
# uses a probabilistic increment with logarithmic behavior. Given the value
|
||||
# of the old counter, when a key is accessed, the counter is incremented in
|
||||
# this way:
|
||||
@@ -1492,7 +1486,7 @@ rdb-save-incremental-fsync yes
|
||||
# What is active defragmentation?
|
||||
# -------------------------------
|
||||
#
|
||||
# Active (online) defragmentation allows a Redis server to compact the
|
||||
# Active (online) defragmentation allows a KeyDB server to compact the
|
||||
# spaces left between small allocations and deallocations of data in memory,
|
||||
# thus allowing to reclaim back memory.
|
||||
#
|
||||
@@ -1504,7 +1498,7 @@ rdb-save-incremental-fsync yes
|
||||
# in an "hot" way, while the server is running.
|
||||
#
|
||||
# Basically when the fragmentation is over a certain level (see the
|
||||
# configuration options below) Redis will start to create new copies of the
|
||||
# configuration options below) KeyDB will start to create new copies of the
|
||||
# values in contiguous memory regions by exploiting certain specific Jemalloc
|
||||
# features (in order to understand if an allocation is causing fragmentation
|
||||
# and to allocate it in a better place), and at the same time, will release the
|
||||
@@ -1513,8 +1507,8 @@ rdb-save-incremental-fsync yes
|
||||
#
|
||||
# Important things to understand:
|
||||
#
|
||||
# 1. This feature is disabled by default, and only works if you compiled Redis
|
||||
# to use the copy of Jemalloc we ship with the source code of Redis.
|
||||
# 1. This feature is disabled by default, and only works if you compiled KeyDB
|
||||
# to use the copy of Jemalloc we ship with the source code of KeyDB.
|
||||
# This is the default with Linux builds.
|
||||
#
|
||||
# 2. You never need to enable this feature if you don't have fragmentation
|
||||
@@ -1567,3 +1561,10 @@ server-threads 2
|
||||
# replicas will still sync in the normal way and incorrect ordering when
|
||||
# bringing up replicas can result in data loss (the first master will win).
|
||||
# active-replica yes
|
||||
|
||||
# Enable Pro? KeyDB pro provides support for pro only features
|
||||
# note: you may omit the license key to demo pro features for a limited time
|
||||
# enable-pro [License Key]
|
||||
|
||||
# Enable FLASH support? (Pro Only)
|
||||
# storage-provider flash /path/to/flash/db
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/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 "${@}"
|
||||
+49
-19
@@ -20,8 +20,8 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
STD=-std=c99 -pedantic -DREDIS_STATIC=''
|
||||
CXX_STD=-std=c++14 -pedantic -fno-rtti
|
||||
STD=-std=c11 -pedantic -DREDIS_STATIC=''
|
||||
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
|
||||
ifneq (,$(findstring clang,$(CC)))
|
||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
@@ -45,6 +45,15 @@ endif
|
||||
endif
|
||||
|
||||
USEASM?=true
|
||||
|
||||
ifneq ($(SANITIZE),)
|
||||
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
|
||||
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
|
||||
LDFLAGS+= -fsanitize=$(SANITIZE)
|
||||
MALLOC=libc
|
||||
USEASM=false
|
||||
endif
|
||||
|
||||
# Do we use our assembly spinlock? X64 only
|
||||
ifeq ($(uname_S),Linux)
|
||||
ifeq ($(uname_M),x86_64)
|
||||
@@ -65,12 +74,18 @@ endif
|
||||
# To get ARM stack traces if Redis crashes we need a special C flag.
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
CXXFLAGS+=-funwind-tables
|
||||
else
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
CXXFLAGS+=-funwind-tables
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
|
||||
# Backwards compatibility for selecting an allocator
|
||||
ifeq ($(USE_TCMALLOC),yes)
|
||||
MALLOC=tcmalloc
|
||||
@@ -92,22 +107,24 @@ endif
|
||||
-include .make-settings
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(CXXFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
FINAL_LIBS+=-lm -lcurl
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
CFLAGS+= -m64
|
||||
CXXFLAGS+= -m64
|
||||
LDFLAGS+= -m64
|
||||
endif
|
||||
DEBUG=-g
|
||||
DEBUG_FLAGS=-g
|
||||
export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS
|
||||
export CFLAGS CXXFLAGS LDFLAGS DEBUG DEBUG_FLAGS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_CXXFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
@@ -124,6 +141,7 @@ ifeq ($(uname_S),OpenBSD)
|
||||
FINAL_LIBS+= -lpthread
|
||||
ifeq ($(USE_BACKTRACE),yes)
|
||||
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_CXXFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_LDFLAGS+= -L/usr/local/lib
|
||||
FINAL_LIBS+= -lexecinfo
|
||||
endif
|
||||
@@ -140,6 +158,8 @@ else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+=-ldl -pthread -lrt -luuid
|
||||
FINAL_CFLAGS += -DMOTD
|
||||
FINAL_CXXFLAGS += -DMOTD
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -177,8 +197,8 @@ ifeq ($(MALLOC),memkind)
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_CXX=$(QUIET_CC)$(CC) $(FINAL_CXXFLAGS)
|
||||
REDIS_NASM=$(QUIET_CC)nasm -felf64
|
||||
REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS)
|
||||
KEYDB_AS=$(QUIET_CC) as --64 -g
|
||||
REDIS_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS)
|
||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||
|
||||
@@ -190,18 +210,18 @@ MAKECOLOR="\033[32;1m"
|
||||
ENDCOLOR="\033[0m"
|
||||
|
||||
ifndef V
|
||||
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-server
|
||||
REDIS_SENTINEL_NAME=keydb-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o $(ASM_OBJ)
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o motd.o $(ASM_OBJ)
|
||||
REDIS_CLI_NAME=keydb-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o $(ASM_OBJ)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
||||
REDIS_BENCHMARK_NAME=keydb-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o $(ASM_OBJ)
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_CHECK_RDB_NAME=keydb-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=keydb-check-aof
|
||||
|
||||
@@ -225,10 +245,13 @@ persist-settings: distclean
|
||||
echo OPT=$(OPT) >> .make-settings
|
||||
echo MALLOC=$(MALLOC) >> .make-settings
|
||||
echo CFLAGS=$(CFLAGS) >> .make-settings
|
||||
echo CXXFLAGS=$(CXXFLAGS) >> .make-settings
|
||||
echo LDFLAGS=$(LDFLAGS) >> .make-settings
|
||||
echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings
|
||||
echo REDIS_CXXFLAGS=$(REDIS_CXXFLAGS) >> .make-settings
|
||||
echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CXXFLAGS=$(FINAL_CXXFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings
|
||||
-(cd modules && $(MAKE))
|
||||
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS))
|
||||
@@ -239,6 +262,8 @@ persist-settings: distclean
|
||||
# Clean everything, persist settings and build dependencies if anything changed
|
||||
ifneq ($(strip $(PREV_FINAL_CFLAGS)), $(strip $(FINAL_CFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else ifneq ($(strip $(PREV_FINAL_CXXFLAGS)), $(strip $(FINAL_CXXFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else
|
||||
@@ -270,23 +295,28 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
|
||||
|
||||
dict-benchmark: dict.cpp zmalloc.c sds.c siphash.c
|
||||
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||
|
||||
|
||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||
-include $(DEP)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build,
|
||||
# building it should complete before building any other object. Instead of
|
||||
# depending on a single artifact, build all dependencies first.
|
||||
%.o: %.c .make-prerequisites
|
||||
$(REDIS_CC) -c $<
|
||||
$(REDIS_CC) -MMD -c $<
|
||||
|
||||
%.o: %.cpp .make-prerequisites
|
||||
$(REDIS_CXX) -c $<
|
||||
$(REDIS_CXX) -MMD -c $<
|
||||
|
||||
%.o: %.asm .make-prerequisites
|
||||
$(REDIS_NASM) $<
|
||||
$(KEYDB_AS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -312,7 +342,7 @@ lcov:
|
||||
@genhtml --legend -o lcov-html redis.info
|
||||
|
||||
test-sds: sds.c sds.h
|
||||
$(REDIS_CC) sds.c zmalloc.c -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
|
||||
$(REDIS_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
|
||||
/tmp/sds_test
|
||||
|
||||
.PHONY: lcov
|
||||
@@ -324,7 +354,7 @@ bench: $(REDIS_BENCHMARK_NAME)
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
|
||||
$(MAKE) CXXFLAGS="-m32" CFLAGS="-m32" LDFLAGS="-m32"
|
||||
|
||||
gcov:
|
||||
$(MAKE) REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
+33
-31
@@ -161,7 +161,7 @@ int ACLListMatchSds(void *a, void *b) {
|
||||
}
|
||||
|
||||
/* Method to free list elements from ACL users password/ptterns lists. */
|
||||
void ACLListFreeSds(void *item) {
|
||||
void ACLListFreeSds(const void *item) {
|
||||
sdsfree((sds)item);
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ void ACLFreeUser(user *u) {
|
||||
void ACLFreeUserAndKillClients(user *u) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(server.clients,&li);
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
if (c->puser == u) {
|
||||
@@ -295,7 +295,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
|
||||
* Note that this function does not check the ALLCOMMANDS flag of the user
|
||||
* but just the lowlevel bitmask.
|
||||
*
|
||||
* If the bit overflows the user internal represetation, zero is returned
|
||||
* If the bit overflows the user internal representation, zero is returned
|
||||
* in order to disallow the execution of the command in such edge case. */
|
||||
int ACLGetUserCommandBit(user *u, unsigned long id) {
|
||||
uint64_t word, bit;
|
||||
@@ -311,7 +311,7 @@ int ACLUserCanExecuteFutureCommands(user *u) {
|
||||
}
|
||||
|
||||
/* Set the specified command bit for the specified user to 'value' (0 or 1).
|
||||
* If the bit overflows the user internal represetation, no operation
|
||||
* If the bit overflows the user internal representation, no operation
|
||||
* is performed. As a side effect of calling this function with a value of
|
||||
* zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
|
||||
* to skip the command bit explicit test. */
|
||||
@@ -334,7 +334,7 @@ void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
|
||||
int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
|
||||
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
|
||||
if (!cflag) return C_ERR;
|
||||
dictIterator *di = dictGetIterator(server.orig_commands);
|
||||
dictIterator *di = dictGetIterator(g_pserver->orig_commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = (redisCommand*)dictGetVal(de);
|
||||
@@ -350,7 +350,7 @@ int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
|
||||
|
||||
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
|
||||
* in the subset of commands flagged with the specified category name.
|
||||
* If the categoty name is not valid, C_ERR is returend, otherwise C_OK is
|
||||
* If the category name is not valid, C_ERR is returned, otherwise C_OK is
|
||||
* returned and on and off are populated by reference. */
|
||||
int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
|
||||
const char *category)
|
||||
@@ -359,7 +359,7 @@ int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
|
||||
if (!cflag) return C_ERR;
|
||||
|
||||
*on = *off = 0;
|
||||
dictIterator *di = dictGetIterator(server.orig_commands);
|
||||
dictIterator *di = dictGetIterator(g_pserver->orig_commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = (redisCommand*)dictGetVal(de);
|
||||
@@ -428,7 +428,7 @@ sds ACLDescribeUserCommandRules(user *u) {
|
||||
}
|
||||
|
||||
/* Fix the final ACLs with single commands differences. */
|
||||
dictIterator *di = dictGetIterator(server.orig_commands);
|
||||
dictIterator *di = dictGetIterator(g_pserver->orig_commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = (redisCommand*)dictGetVal(de);
|
||||
@@ -533,7 +533,7 @@ sds ACLDescribeUser(user *u) {
|
||||
struct redisCommand *ACLLookupCommand(const char *name) {
|
||||
struct redisCommand *cmd;
|
||||
sds sdsname = sdsnew(name);
|
||||
cmd = (redisCommand*)dictFetchValue(server.orig_commands, sdsname);
|
||||
cmd = (redisCommand*)dictFetchValue(g_pserver->orig_commands, sdsname);
|
||||
sdsfree(sdsname);
|
||||
return cmd;
|
||||
}
|
||||
@@ -542,6 +542,8 @@ struct redisCommand *ACLLookupCommand(const char *name) {
|
||||
* and command ID. */
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
|
||||
if (u->allowed_subcommands && u->allowed_subcommands[id]) {
|
||||
for (int i = 0; u->allowed_subcommands[id][i]; i++)
|
||||
sdsfree(u->allowed_subcommands[id][i]);
|
||||
zfree(u->allowed_subcommands[id]);
|
||||
u->allowed_subcommands[id] = NULL;
|
||||
}
|
||||
@@ -624,7 +626,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||
* It is possible to specify multiple patterns.
|
||||
* allkeys Alias for ~*
|
||||
* resetkeys Flush the list of allowed keys patterns.
|
||||
* ><password> Add this passowrd to the list of valid password for the user.
|
||||
* ><password> Add this password to the list of valid password for the user.
|
||||
* For example >mypass will add "mypass" to the list.
|
||||
* This directive clears the "nopass" flag (see later).
|
||||
* <<password> Remove this password from the list of valid passwords.
|
||||
@@ -947,9 +949,9 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
return (user*)myuser;
|
||||
}
|
||||
|
||||
/* Check if the command ready to be excuted in the client 'c', and already
|
||||
* referenced by c->cmd, can be executed by this client according to the
|
||||
* ACls associated to the client user c->user.
|
||||
/* Check if the command is ready to be executed in the client 'c', already
|
||||
* referenced by c->cmd, and can be executed by this client according to the
|
||||
* ACLs associated to the client user c->user.
|
||||
*
|
||||
* If the user can execute the command ACL_OK is returned, otherwise
|
||||
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
|
||||
@@ -1120,8 +1122,8 @@ int ACLLoadConfiguredUsers(void) {
|
||||
}
|
||||
|
||||
/* This function loads the ACL from the specified filename: every line
|
||||
* is validated and shold be either empty or in the format used to specify
|
||||
* users in the redis.conf configuration or in the ACL file, that is:
|
||||
* is validated and should be either empty or in the format used to specify
|
||||
* users in the keydb.conf configuration or in the ACL file, that is:
|
||||
*
|
||||
* user <username> ... rules ...
|
||||
*
|
||||
@@ -1170,7 +1172,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
* to the real user mentioned in the ACL line. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
/* We do all the loading in a fresh insteance of the Users radix tree,
|
||||
/* We do all the loading in a fresh instance of the Users radix tree,
|
||||
* so if there are errors loading the ACL file we can rollback to the
|
||||
* old version. */
|
||||
rax *old_users = Users;
|
||||
@@ -1194,7 +1196,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
if (argv == NULL) {
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: unbalanced quotes in acl line. ",
|
||||
server.acl_filename, linenum);
|
||||
g_pserver->acl_filename, linenum);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1208,7 +1210,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
if (strcmp(argv[0],"user") || argc < 2) {
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d should start with user keyword followed "
|
||||
"by the username. ", server.acl_filename,
|
||||
"by the username. ", g_pserver->acl_filename,
|
||||
linenum);
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
@@ -1223,7 +1225,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
const char *errmsg = ACLSetUserStringError();
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: %s. ",
|
||||
server.acl_filename, linenum, errmsg);
|
||||
g_pserver->acl_filename, linenum, errmsg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1246,7 +1248,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
}
|
||||
|
||||
/* Note that the same rules already applied to the fake user, so
|
||||
* we just assert that everything goess well: it should. */
|
||||
* we just assert that everything goes well: it should. */
|
||||
for (j = 2; j < argc; j++)
|
||||
serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
|
||||
|
||||
@@ -1345,17 +1347,17 @@ cleanup:
|
||||
|
||||
/* This function is called once the server is already running, modules are
|
||||
* loaded, and we are ready to start, in order to load the ACLs either from
|
||||
* the pending list of users defined in redis.conf, or from the ACL file.
|
||||
* the pending list of users defined in keydb.conf, or from the ACL file.
|
||||
* The function will just exit with an error if the user is trying to mix
|
||||
* both the loading methods. */
|
||||
void ACLLoadUsersAtStartup(void) {
|
||||
if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
|
||||
if (g_pserver->acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Configuring Redis with users defined in redis.conf and at "
|
||||
"Configuring KeyDB with users defined in keydb.conf and at "
|
||||
"the same setting an ACL file path is invalid. This setup "
|
||||
"is very likely to lead to configuration errors and security "
|
||||
"holes, please define either an ACL file or declare users "
|
||||
"directly in your redis.conf, but not both.");
|
||||
"directly in your keydb.conf, but not both.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1365,8 +1367,8 @@ void ACLLoadUsersAtStartup(void) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (server.acl_filename[0] != '\0') {
|
||||
sds errors = ACLLoadFromFile(server.acl_filename);
|
||||
if (g_pserver->acl_filename[0] != '\0') {
|
||||
sds errors = ACLLoadFromFile(g_pserver->acl_filename);
|
||||
if (errors) {
|
||||
serverLog(LL_WARNING,
|
||||
"Aborting Redis startup because of ACL errors: %s", errors);
|
||||
@@ -1526,13 +1528,13 @@ void aclCommand(client *c) {
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else if (server.acl_filename[0] == '\0' &&
|
||||
} else if (g_pserver->acl_filename[0] == '\0' &&
|
||||
(!strcasecmp(sub,"load") || !strcasecmp(sub,"save")))
|
||||
{
|
||||
addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration.");
|
||||
return;
|
||||
} else if (!strcasecmp(sub,"load") && c->argc == 2) {
|
||||
sds errors = ACLLoadFromFile(server.acl_filename);
|
||||
sds errors = ACLLoadFromFile(g_pserver->acl_filename);
|
||||
if (errors == NULL) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
@@ -1540,7 +1542,7 @@ void aclCommand(client *c) {
|
||||
sdsfree(errors);
|
||||
}
|
||||
} else if (!strcasecmp(sub,"save") && c->argc == 2) {
|
||||
if (ACLSaveToFile(server.acl_filename) == C_OK) {
|
||||
if (ACLSaveToFile(g_pserver->acl_filename) == C_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,"There was an error trying to save the ACLs. "
|
||||
@@ -1561,7 +1563,7 @@ void aclCommand(client *c) {
|
||||
}
|
||||
int arraylen = 0;
|
||||
void *dl = addReplyDeferredLen(c);
|
||||
dictIterator *di = dictGetIterator(server.orig_commands);
|
||||
dictIterator *di = dictGetIterator(g_pserver->orig_commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = (redisCommand*)dictGetVal(de);
|
||||
@@ -1609,7 +1611,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
|
||||
setDeferredSetLen(c, flaglen, flagcount);
|
||||
}
|
||||
|
||||
/* AUTH <passowrd>
|
||||
/* AUTH <password>
|
||||
* AUTH <username> <password> (Redis >= 6.0 form)
|
||||
*
|
||||
* When the user is omitted it means that we are trying to authenticate
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ typedef struct list {
|
||||
listNode *head;
|
||||
listNode *tail;
|
||||
void *(*dup)(void *ptr);
|
||||
void (*free)(void *ptr);
|
||||
void (*free)(const void *ptr);
|
||||
int (*match)(void *ptr, void *key);
|
||||
unsigned long len;
|
||||
} list;
|
||||
|
||||
+64
-21
@@ -46,10 +46,8 @@
|
||||
|
||||
#include "ae.h"
|
||||
#include "fastlock.h"
|
||||
extern "C" {
|
||||
#include "zmalloc.h"
|
||||
#include "config.h"
|
||||
}
|
||||
|
||||
#ifdef USE_MUTEX
|
||||
thread_local int cOwnLock = 0;
|
||||
@@ -82,11 +80,11 @@ public:
|
||||
mutex_wrapper g_lock;
|
||||
|
||||
#else
|
||||
fastlock g_lock;
|
||||
fastlock g_lock("AE (global)");
|
||||
#endif
|
||||
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
|
||||
|
||||
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSER FAILURE\n"); *((volatile int*)0) = 1; } while(0)
|
||||
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSERT FAILURE %s: %d\n", __FILE__, __LINE__); *((volatile int*)1) = 1; } while(0)
|
||||
|
||||
/* Include the best multiplexing layer supported by this system.
|
||||
* The following should be ordered by performances, descending. */
|
||||
@@ -193,6 +191,36 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike write() this is an all or nothing thing. We will block if a partial write is hit
|
||||
ssize_t safe_write(int fd, const void *pv, size_t cb)
|
||||
{
|
||||
const char *pcb = (const char*)pv;
|
||||
ssize_t written = 0;
|
||||
do
|
||||
{
|
||||
ssize_t rval = write(fd, pcb, cb);
|
||||
if (rval > 0)
|
||||
{
|
||||
pcb += rval;
|
||||
cb -= rval;
|
||||
written += rval;
|
||||
}
|
||||
else if (errno == EAGAIN)
|
||||
{
|
||||
if (written == 0)
|
||||
break;
|
||||
// if we've already written something then we're committed so keep trying
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rval == 0)
|
||||
return written;
|
||||
return rval;
|
||||
}
|
||||
} while (cb);
|
||||
return written;
|
||||
}
|
||||
|
||||
int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
aeFileProc *proc, void *clientData, int fSynchronous)
|
||||
{
|
||||
@@ -201,7 +229,7 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
|
||||
int ret = AE_OK;
|
||||
|
||||
aeCommand cmd;
|
||||
aeCommand cmd = {};
|
||||
cmd.op = AE_ASYNC_OP::CreateFileEvent;
|
||||
cmd.fd = fd;
|
||||
cmd.mask = mask;
|
||||
@@ -209,20 +237,22 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
cmd.clientData = clientData;
|
||||
cmd.pctl = nullptr;
|
||||
if (fSynchronous)
|
||||
cmd.pctl = new aeCommandControl();
|
||||
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
cmd.pctl->mutexcv.lock();
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
auto size = safe_write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
{
|
||||
AE_ASSERT(size == sizeof(cmd) || size <= 0);
|
||||
AE_ASSERT(errno == EAGAIN);
|
||||
ret = AE_ERR;
|
||||
}
|
||||
|
||||
if (fSynchronous)
|
||||
{
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
cmd.pctl->cv.wait(ulock);
|
||||
ret = cmd.pctl->rval;
|
||||
delete cmd.pctl;
|
||||
@@ -238,12 +268,13 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
|
||||
proc(arg);
|
||||
return AE_OK;
|
||||
}
|
||||
aeCommand cmd;
|
||||
aeCommand cmd = {};
|
||||
cmd.op = AE_ASYNC_OP::PostFunction;
|
||||
cmd.proc = proc;
|
||||
cmd.clientData = arg;
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
AE_ASSERT(size == sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
return AE_ERR;
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
@@ -255,20 +286,24 @@ int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynch
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
aeCommand cmd;
|
||||
aeCommand cmd = {};
|
||||
cmd.op = AE_ASYNC_OP::PostCppFunction;
|
||||
cmd.pfn = new std::function<void()>(fn);
|
||||
cmd.pfn = new (MALLOC_LOCAL) std::function<void()>(fn);
|
||||
cmd.pctl = nullptr;
|
||||
if (fSynchronous)
|
||||
cmd.pctl = new aeCommandControl();
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
cmd.pctl->mutexcv.lock();
|
||||
}
|
||||
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
return AE_ERR;
|
||||
AE_ASSERT(size == sizeof(cmd));
|
||||
int ret = AE_OK;
|
||||
if (fSynchronous)
|
||||
{
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
cmd.pctl->cv.wait(ulock);
|
||||
ret = cmd.pctl->rval;
|
||||
delete cmd.pctl;
|
||||
@@ -298,7 +333,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
for (i = 0; i < setsize; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
|
||||
fastlock_init(&eventLoop->flock);
|
||||
fastlock_init(&eventLoop->flock, "event loop");
|
||||
int rgfd[2];
|
||||
if (pipe(rgfd) < 0)
|
||||
goto err;
|
||||
@@ -355,10 +390,18 @@ extern "C" void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
zfree(eventLoop->fired);
|
||||
zfree(eventLoop);
|
||||
fastlock_free(&eventLoop->flock);
|
||||
close(eventLoop->fdCmdRead);
|
||||
close(eventLoop->fdCmdWrite);
|
||||
|
||||
auto *te = eventLoop->timeEventHead;
|
||||
while (te)
|
||||
{
|
||||
auto *teNext = te->next;
|
||||
zfree(te);
|
||||
te = teNext;
|
||||
}
|
||||
zfree(eventLoop);
|
||||
}
|
||||
|
||||
extern "C" void aeStop(aeEventLoop *eventLoop) {
|
||||
@@ -805,9 +848,9 @@ void aeAcquireLock()
|
||||
g_lock.lock();
|
||||
}
|
||||
|
||||
int aeTryAcquireLock()
|
||||
int aeTryAcquireLock(int fWeak)
|
||||
{
|
||||
return g_lock.try_lock();
|
||||
return g_lock.try_lock(!!fWeak);
|
||||
}
|
||||
|
||||
void aeReleaseLock()
|
||||
|
||||
@@ -159,7 +159,7 @@ int aeGetSetSize(aeEventLoop *eventLoop);
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||
|
||||
void aeAcquireLock();
|
||||
int aeTryAcquireLock();
|
||||
int aeTryAcquireLock(int fWeak);
|
||||
void aeReleaseLock();
|
||||
int aeThreadOwnsLock();
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
class AeLocker
|
||||
{
|
||||
bool m_fArmed = false;
|
||||
|
||||
public:
|
||||
AeLocker()
|
||||
{
|
||||
}
|
||||
|
||||
void arm(client *c) // if a client is passed, then the client is already locked
|
||||
{
|
||||
if (c != nullptr)
|
||||
{
|
||||
serverAssert(!m_fArmed);
|
||||
serverAssert(c->lock.fOwnLock());
|
||||
|
||||
if (!aeTryAcquireLock(true /*fWeak*/)) // avoid locking the client if we can
|
||||
{
|
||||
bool fOwnClientLock = true;
|
||||
int clientNesting = 1;
|
||||
for (;;)
|
||||
{
|
||||
if (fOwnClientLock)
|
||||
{
|
||||
clientNesting = c->lock.unlock_recursive();
|
||||
fOwnClientLock = false;
|
||||
}
|
||||
aeAcquireLock();
|
||||
if (!c->lock.try_lock(false)) // ensure a strong try because aeAcquireLock is expensive
|
||||
{
|
||||
aeReleaseLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
c->lock.lock_recursive(clientNesting);
|
||||
}
|
||||
|
||||
m_fArmed = true;
|
||||
}
|
||||
else if (!m_fArmed)
|
||||
{
|
||||
m_fArmed = true;
|
||||
aeAcquireLock();
|
||||
}
|
||||
}
|
||||
|
||||
void disarm()
|
||||
{
|
||||
serverAssert(m_fArmed);
|
||||
m_fArmed = false;
|
||||
aeReleaseLock();
|
||||
}
|
||||
|
||||
bool isArmed() const
|
||||
{
|
||||
return m_fArmed;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
m_fArmed = false;
|
||||
}
|
||||
|
||||
~AeLocker()
|
||||
{
|
||||
if (m_fArmed)
|
||||
aeReleaseLock();
|
||||
}
|
||||
};
|
||||
+11
-7
@@ -476,7 +476,7 @@ static int anetV6Only(char *err, int s) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort)
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
int s = -1, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
@@ -498,8 +498,12 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (fReusePort && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
if (fReusePort && !fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) {
|
||||
s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (fReusePort && fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -515,14 +519,14 @@ end:
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
|
||||
+2
-2
@@ -62,8 +62,8 @@ int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
|
||||
+251
-186
@@ -65,14 +65,14 @@ typedef struct aofrwblock {
|
||||
} aofrwblock;
|
||||
|
||||
/* This function free the old AOF rewrite buffer if needed, and initialize
|
||||
* a fresh new one. It tests for server.aof_rewrite_buf_blocks equal to NULL
|
||||
* a fresh new one. It tests for g_pserver->aof_rewrite_buf_blocks equal to NULL
|
||||
* so can be used for the first initialization as well. */
|
||||
void aofRewriteBufferReset(void) {
|
||||
if (server.aof_rewrite_buf_blocks)
|
||||
listRelease(server.aof_rewrite_buf_blocks);
|
||||
if (g_pserver->aof_rewrite_buf_blocks)
|
||||
listRelease(g_pserver->aof_rewrite_buf_blocks);
|
||||
|
||||
server.aof_rewrite_buf_blocks = listCreate();
|
||||
listSetFreeMethod(server.aof_rewrite_buf_blocks,zfree);
|
||||
g_pserver->aof_rewrite_buf_blocks = listCreate();
|
||||
listSetFreeMethod(g_pserver->aof_rewrite_buf_blocks,zfree);
|
||||
}
|
||||
|
||||
/* Return the current size of the AOF rewrite buffer. */
|
||||
@@ -81,7 +81,7 @@ unsigned long aofRewriteBufferSize(void) {
|
||||
listIter li;
|
||||
unsigned long size = 0;
|
||||
|
||||
listRewind(server.aof_rewrite_buf_blocks,&li);
|
||||
listRewind(g_pserver->aof_rewrite_buf_blocks,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
aofrwblock *block = (aofrwblock*)listNodeValue(ln);
|
||||
size += block->used;
|
||||
@@ -97,6 +97,7 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
aofrwblock *block;
|
||||
ssize_t nwritten;
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(el == g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el); // MUST run on main thread
|
||||
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
@@ -104,28 +105,43 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
UNUSED(mask);
|
||||
|
||||
while(1) {
|
||||
ln = listFirst(server.aof_rewrite_buf_blocks);
|
||||
ln = listFirst(g_pserver->aof_rewrite_buf_blocks);
|
||||
block = (aofrwblock*)(ln ? ln->value : NULL);
|
||||
if (server.aof_stop_sending_diff || !block) {
|
||||
aeDeleteFileEvent(el,server.aof_pipe_write_data_to_child,
|
||||
if (g_pserver->aof_stop_sending_diff || !block) {
|
||||
aeDeleteFileEvent(el,g_pserver->aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE);
|
||||
return;
|
||||
}
|
||||
if (block->used > 0) {
|
||||
nwritten = write(server.aof_pipe_write_data_to_child,
|
||||
nwritten = write(g_pserver->aof_pipe_write_data_to_child,
|
||||
block->buf,block->used);
|
||||
if (nwritten <= 0) return;
|
||||
memmove(block->buf,block->buf+nwritten,block->used-nwritten);
|
||||
block->used -= nwritten;
|
||||
block->free += nwritten;
|
||||
}
|
||||
if (block->used == 0) listDelNode(server.aof_rewrite_buf_blocks,ln);
|
||||
if (block->used == 0) listDelNode(g_pserver->aof_rewrite_buf_blocks,ln);
|
||||
}
|
||||
}
|
||||
|
||||
void installAofRewriteEvent()
|
||||
{
|
||||
serverTL->fRetrySetAofEvent = false;
|
||||
if (!g_pserver->aof_rewrite_pending) {
|
||||
g_pserver->aof_rewrite_pending = true;
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [] {
|
||||
g_pserver->aof_rewrite_pending = false;
|
||||
if (g_pserver->aof_pipe_write_data_to_child >= 0)
|
||||
aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
serverTL->fRetrySetAofEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */
|
||||
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
listNode *ln = listLast(server.aof_rewrite_buf_blocks);
|
||||
listNode *ln = listLast(g_pserver->aof_rewrite_buf_blocks);
|
||||
aofrwblock *block = (aofrwblock*)(ln ? ln->value : NULL);
|
||||
|
||||
while(len) {
|
||||
@@ -148,11 +164,11 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
block = (aofrwblock*)zmalloc(sizeof(*block), MALLOC_LOCAL);
|
||||
block->free = AOF_RW_BUF_BLOCK_SIZE;
|
||||
block->used = 0;
|
||||
listAddNodeTail(server.aof_rewrite_buf_blocks,block);
|
||||
listAddNodeTail(g_pserver->aof_rewrite_buf_blocks,block);
|
||||
|
||||
/* Log every time we cross more 10 or 100 blocks, respectively
|
||||
* as a notice or warning. */
|
||||
numblocks = listLength(server.aof_rewrite_buf_blocks);
|
||||
numblocks = listLength(g_pserver->aof_rewrite_buf_blocks);
|
||||
if (((numblocks+1) % 10) == 0) {
|
||||
int level = ((numblocks+1) % 100) == 0 ? LL_WARNING :
|
||||
LL_NOTICE;
|
||||
@@ -164,10 +180,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
if (aeGetFileEvents(serverTL->el,server.aof_pipe_write_data_to_child) == 0) {
|
||||
aeCreateFileEvent(serverTL->el, server.aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
installAofRewriteEvent();
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
@@ -178,7 +191,7 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
listIter li;
|
||||
ssize_t count = 0;
|
||||
|
||||
listRewind(server.aof_rewrite_buf_blocks,&li);
|
||||
listRewind(g_pserver->aof_rewrite_buf_blocks,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
aofrwblock *block = (aofrwblock*)listNodeValue(ln);
|
||||
ssize_t nwritten;
|
||||
@@ -199,6 +212,12 @@ 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) {
|
||||
@@ -209,18 +228,18 @@ void aof_background_fsync(int fd) {
|
||||
void killAppendOnlyChild(void) {
|
||||
int statloc;
|
||||
/* No AOFRW child? return. */
|
||||
if (server.aof_child_pid == -1) return;
|
||||
if (g_pserver->aof_child_pid == -1) return;
|
||||
/* Kill AOFRW child, wait for child exit. */
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) server.aof_child_pid);
|
||||
if (kill(server.aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != server.aof_child_pid);
|
||||
(long) g_pserver->aof_child_pid);
|
||||
if (kill(g_pserver->aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != g_pserver->aof_child_pid);
|
||||
}
|
||||
/* Reset the buffer accumulating changes while the child saves. */
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(server.aof_child_pid);
|
||||
server.aof_child_pid = -1;
|
||||
server.aof_rewrite_time_start = -1;
|
||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
||||
g_pserver->aof_child_pid = -1;
|
||||
g_pserver->aof_rewrite_time_start = -1;
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
closeChildInfoPipe();
|
||||
@@ -230,14 +249,14 @@ void killAppendOnlyChild(void) {
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
* at runtime using the CONFIG command. */
|
||||
void stopAppendOnly(void) {
|
||||
serverAssert(server.aof_state != AOF_OFF);
|
||||
serverAssert(g_pserver->aof_state != AOF_OFF);
|
||||
flushAppendOnlyFile(1);
|
||||
redis_fsync(server.aof_fd);
|
||||
close(server.aof_fd);
|
||||
redis_fsync(g_pserver->aof_fd);
|
||||
close(g_pserver->aof_fd);
|
||||
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1;
|
||||
server.aof_state = AOF_OFF;
|
||||
g_pserver->aof_fd = -1;
|
||||
g_pserver->aof_selected_db = -1;
|
||||
g_pserver->aof_state = AOF_OFF;
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
|
||||
@@ -247,27 +266,27 @@ int startAppendOnly(void) {
|
||||
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
|
||||
int newfd;
|
||||
|
||||
newfd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
|
||||
serverAssert(server.aof_state == AOF_OFF);
|
||||
newfd = open(g_pserver->aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
|
||||
serverAssert(g_pserver->aof_state == AOF_OFF);
|
||||
if (newfd == -1) {
|
||||
char *cwdp = getcwd(cwd,MAXPATHLEN);
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Redis needs to enable the AOF but can't open the "
|
||||
"append only file %s (in server root dir %s): %s",
|
||||
server.aof_filename,
|
||||
g_pserver->aof_filename,
|
||||
cwdp ? cwdp : "unknown",
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (server.rdb_child_pid != -1) {
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
* accumulating the AOF buffer. */
|
||||
if (server.aof_child_pid != -1) {
|
||||
if (g_pserver->aof_child_pid != -1) {
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
@@ -279,9 +298,9 @@ int startAppendOnly(void) {
|
||||
}
|
||||
/* We correctly switched on AOF, now wait for the rewrite to be complete
|
||||
* in order to append data on disk. */
|
||||
server.aof_state = AOF_WAIT_REWRITE;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
server.aof_fd = newfd;
|
||||
g_pserver->aof_state = AOF_WAIT_REWRITE;
|
||||
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||
g_pserver->aof_fd = newfd;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -337,29 +356,46 @@ void flushAppendOnlyFile(int force) {
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) return;
|
||||
if (serverTL->fRetrySetAofEvent)
|
||||
installAofRewriteEvent();
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
if (sdslen(g_pserver->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 (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
g_pserver->aof_fsync_offset != g_pserver->aof_current_size &&
|
||||
g_pserver->unixtime > g_pserver->aof_last_fsync &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
sync_in_progress = aofFsyncInProgress();
|
||||
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
|
||||
/* With this append fsync policy we do background fsyncing.
|
||||
* If the fsync is still in progress we can try to delay
|
||||
* the write for a couple of seconds. */
|
||||
if (sync_in_progress) {
|
||||
if (server.aof_flush_postponed_start == 0) {
|
||||
if (g_pserver->aof_flush_postponed_start == 0) {
|
||||
/* No previous write postponing, remember that we are
|
||||
* postponing the flush and return. */
|
||||
server.aof_flush_postponed_start = server.unixtime;
|
||||
g_pserver->aof_flush_postponed_start = g_pserver->unixtime;
|
||||
return;
|
||||
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
|
||||
} else if (g_pserver->unixtime - g_pserver->aof_flush_postponed_start < 2) {
|
||||
/* We were already waiting for fsync to finish, but for less
|
||||
* than two seconds this is still ok. Postpone again. */
|
||||
return;
|
||||
}
|
||||
/* Otherwise fall trough, and go write since we can't wait
|
||||
* over two seconds. */
|
||||
server.aof_delayed_fsync++;
|
||||
g_pserver->aof_delayed_fsync++;
|
||||
serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
|
||||
}
|
||||
}
|
||||
@@ -370,7 +406,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* or alike */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
nwritten = aofWrite(g_pserver->aof_fd,g_pserver->aof_buf,sdslen(g_pserver->aof_buf));
|
||||
latencyEndMonitor(latency);
|
||||
/* We want to capture different events for delayed writes:
|
||||
* when the delay happens with a pending fsync, or with a saving child
|
||||
@@ -379,7 +415,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@@ -387,16 +423,16 @@ void flushAppendOnlyFile(int force) {
|
||||
latencyAddSampleIfNeeded("aof-write",latency);
|
||||
|
||||
/* We performed the write so reset the postponed flush sentinel to zero. */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
g_pserver->aof_flush_postponed_start = 0;
|
||||
|
||||
if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
|
||||
if (nwritten != (ssize_t)sdslen(g_pserver->aof_buf)) {
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
|
||||
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
|
||||
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
|
||||
if ((g_pserver->unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
|
||||
can_log = 1;
|
||||
last_write_error_log = server.unixtime;
|
||||
last_write_error_log = g_pserver->unixtime;
|
||||
}
|
||||
|
||||
/* Log the AOF write error and record the error code. */
|
||||
@@ -404,7 +440,7 @@ void flushAppendOnlyFile(int force) {
|
||||
if (can_log) {
|
||||
serverLog(LL_WARNING,"Error writing to the AOF file: %s",
|
||||
strerror(errno));
|
||||
server.aof_last_write_errno = errno;
|
||||
g_pserver->aof_last_write_errno = errno;
|
||||
}
|
||||
} else {
|
||||
if (can_log) {
|
||||
@@ -412,10 +448,10 @@ void flushAppendOnlyFile(int force) {
|
||||
"the AOF file: (nwritten=%lld, "
|
||||
"expected=%lld)",
|
||||
(long long)nwritten,
|
||||
(long long)sdslen(server.aof_buf));
|
||||
(long long)sdslen(g_pserver->aof_buf));
|
||||
}
|
||||
|
||||
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
|
||||
if (ftruncate(g_pserver->aof_fd, g_pserver->aof_current_size) == -1) {
|
||||
if (can_log) {
|
||||
serverLog(LL_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
@@ -427,11 +463,11 @@ void flushAppendOnlyFile(int force) {
|
||||
* -1 since there is no longer partial data into the AOF. */
|
||||
nwritten = -1;
|
||||
}
|
||||
server.aof_last_write_errno = ENOSPC;
|
||||
g_pserver->aof_last_write_errno = ENOSPC;
|
||||
}
|
||||
|
||||
/* Handle the AOF write error. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
@@ -442,55 +478,60 @@ void flushAppendOnlyFile(int force) {
|
||||
/* Recover from failed write leaving data into the buffer. However
|
||||
* set an error to stop accepting writes as long as the error
|
||||
* condition is not cleared. */
|
||||
server.aof_last_write_status = C_ERR;
|
||||
g_pserver->aof_last_write_status = C_ERR;
|
||||
|
||||
/* Trim the sds buffer if there was a partial write, and there
|
||||
* was no way to undo it with ftruncate(2). */
|
||||
if (nwritten > 0) {
|
||||
server.aof_current_size += nwritten;
|
||||
sdsrange(server.aof_buf,nwritten,-1);
|
||||
g_pserver->aof_current_size += nwritten;
|
||||
sdsrange(g_pserver->aof_buf,nwritten,-1);
|
||||
}
|
||||
return; /* We'll try again on the next call... */
|
||||
}
|
||||
} else {
|
||||
/* Successful write(2). If AOF was in error state, restore the
|
||||
* OK state and log the event. */
|
||||
if (server.aof_last_write_status == C_ERR) {
|
||||
if (g_pserver->aof_last_write_status == C_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
"AOF write error looks solved, Redis can write again.");
|
||||
server.aof_last_write_status = C_OK;
|
||||
g_pserver->aof_last_write_status = C_OK;
|
||||
}
|
||||
}
|
||||
server.aof_current_size += nwritten;
|
||||
g_pserver->aof_current_size += nwritten;
|
||||
|
||||
/* Re-use AOF buffer when it is small enough. The maximum comes from the
|
||||
* arena size of 4k minus some overhead (but is otherwise arbitrary). */
|
||||
if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
|
||||
sdsclear(server.aof_buf);
|
||||
if ((sdslen(g_pserver->aof_buf)+sdsavail(g_pserver->aof_buf)) < 4000) {
|
||||
sdsclear(g_pserver->aof_buf);
|
||||
} else {
|
||||
sdsfree(server.aof_buf);
|
||||
server.aof_buf = sdsempty();
|
||||
sdsfree(g_pserver->aof_buf);
|
||||
g_pserver->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 &&
|
||||
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
|
||||
if (g_pserver->aof_no_fsync_on_rewrite &&
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1))
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* redis_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
* flushing metadata. */
|
||||
latencyStartMonitor(latency);
|
||||
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
redis_fsync(g_pserver->aof_fd); /* Let's try to get this data on the disk */
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
} 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_last_fsync = server.unixtime;
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||
} else if ((g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
g_pserver->unixtime > g_pserver->aof_last_fsync)) {
|
||||
if (!sync_in_progress) {
|
||||
aof_background_fsync(g_pserver->aof_fd);
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
}
|
||||
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,13 +603,13 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
|
||||
/* The DB this command was targeting is not the same as the last command
|
||||
* we appended. To issue a SELECT command is needed. */
|
||||
if (dictid != server.aof_selected_db) {
|
||||
if (dictid != g_pserver->aof_selected_db) {
|
||||
char seldb[64];
|
||||
|
||||
snprintf(seldb,sizeof(seldb),"%d",dictid);
|
||||
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
|
||||
(unsigned long)strlen(seldb),seldb);
|
||||
server.aof_selected_db = dictid;
|
||||
g_pserver->aof_selected_db = dictid;
|
||||
}
|
||||
|
||||
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
|
||||
@@ -594,10 +635,10 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
}
|
||||
serverAssert(!(exarg && pxarg));
|
||||
if (exarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1],
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.expireCommand,argv[1],
|
||||
exarg);
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
} else {
|
||||
/* All the other commands don't need translation or need the
|
||||
@@ -609,14 +650,14 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
/* Append to the AOF buffer. This will be flushed on disk just before
|
||||
* of re-entering the event loop, so before the client will get a
|
||||
* positive reply about the operation performed. */
|
||||
if (server.aof_state == AOF_ON)
|
||||
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
|
||||
if (g_pserver->aof_state == AOF_ON)
|
||||
g_pserver->aof_buf = sdscatlen(g_pserver->aof_buf,buf,sdslen(buf));
|
||||
|
||||
/* If a background append only file rewriting is in progress we want to
|
||||
* accumulate the differences between the child DB and the current one
|
||||
* in a buffer, so that when the child process will do its work we
|
||||
* can append the differences to the new append only file. */
|
||||
if (server.aof_child_pid != -1)
|
||||
if (g_pserver->aof_child_pid != -1)
|
||||
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
|
||||
|
||||
sdsfree(buf);
|
||||
@@ -643,7 +684,7 @@ client *createFakeClient(void) {
|
||||
c->flags = 0;
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* We set the fake client as a slave waiting for the synchronization
|
||||
/* We set the fake client as a replica waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
c->reply = listCreate();
|
||||
@@ -655,7 +696,7 @@ client *createFakeClient(void) {
|
||||
c->puser = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
fastlock_init(&c->lock);
|
||||
fastlock_init(&c->lock, "fake client");
|
||||
fastlock_lock(&c->lock);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -686,7 +727,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
struct client *fakeClient;
|
||||
FILE *fp = fopen(filename,"r");
|
||||
struct redis_stat sb;
|
||||
int old_aof_state = server.aof_state;
|
||||
int old_aof_state = g_pserver->aof_state;
|
||||
long loops = 0;
|
||||
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
|
||||
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
|
||||
@@ -702,14 +743,15 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* a zero length file at startup, that will remain like that if no write
|
||||
* operation is received. */
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
g_pserver->aof_current_size = 0;
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
|
||||
* to the same file we're about to read. */
|
||||
server.aof_state = AOF_OFF;
|
||||
g_pserver->aof_state = AOF_OFF;
|
||||
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
@@ -726,7 +768,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
|
||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||
rioInitWithFile(&rdb,fileno(fp));
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
if (rdbLoadRio(&rdb,&rsi,1) != C_OK) {
|
||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||
@@ -748,7 +790,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
processEventsWhileBlocked(serverTL - server.rgthreadvar);
|
||||
processEventsWhileBlocked(serverTL - g_pserver->rgthreadvar);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
@@ -798,7 +840,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
if (cmd == cserver.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
@@ -821,7 +863,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
fakeClient->cmd = NULL;
|
||||
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
||||
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
@@ -838,10 +880,11 @@ int loadAppendOnlyFile(char *filename) {
|
||||
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
server.aof_state = old_aof_state;
|
||||
g_pserver->aof_state = old_aof_state;
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
return C_OK;
|
||||
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
@@ -852,7 +895,7 @@ readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
}
|
||||
|
||||
uxeof: /* Unexpected AOF end of file. */
|
||||
if (server.aof_load_truncated) {
|
||||
if (g_pserver->aof_load_truncated) {
|
||||
serverLog(LL_WARNING,"!!! Warning: short read while loading the AOF file !!!");
|
||||
serverLog(LL_WARNING,"!!! Truncating the AOF at offset %llu !!!",
|
||||
(unsigned long long) valid_up_to);
|
||||
@@ -866,7 +909,7 @@ uxeof: /* Unexpected AOF end of file. */
|
||||
} else {
|
||||
/* Make sure the AOF file descriptor points to the end of the
|
||||
* file after the truncate call. */
|
||||
if (server.aof_fd != -1 && lseek(server.aof_fd,0,SEEK_END) == -1) {
|
||||
if (g_pserver->aof_fd != -1 && lseek(g_pserver->aof_fd,0,SEEK_END) == -1) {
|
||||
serverLog(LL_WARNING,"Can't seek the end of the AOF file: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
@@ -877,7 +920,7 @@ uxeof: /* Unexpected AOF end of file. */
|
||||
}
|
||||
}
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./keydb-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
|
||||
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./keydb-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the g_pserver->");
|
||||
exit(1);
|
||||
|
||||
fmterr: /* Format error. */
|
||||
@@ -891,7 +934,7 @@ fmterr: /* Format error. */
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Delegate writing an object to writing a bulk string or bulk long long.
|
||||
* This is not placed in rio.c since that adds the server.h dependency. */
|
||||
* This is not placed in rio.c since that adds the g_pserver->h dependency. */
|
||||
int rioWriteBulkObject(rio *r, robj *obj) {
|
||||
/* Avoid using getDecodedObject to help copy-on-write (we are often
|
||||
* in a child process when this function is called). */
|
||||
@@ -1266,8 +1309,8 @@ ssize_t aofReadDiffFromParent(void) {
|
||||
ssize_t nread, total = 0;
|
||||
|
||||
while ((nread =
|
||||
read(server.aof_pipe_read_data_from_parent,buf,sizeof(buf))) > 0) {
|
||||
server.aof_child_diff = sdscatlen(server.aof_child_diff,buf,nread);
|
||||
read(g_pserver->aof_pipe_read_data_from_parent,buf,sizeof(buf))) > 0) {
|
||||
g_pserver->aof_child_diff = sdscatlen(g_pserver->aof_child_diff,buf,nread);
|
||||
total += nread;
|
||||
}
|
||||
return total;
|
||||
@@ -1279,9 +1322,9 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
size_t processed = 0;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = server.db+j;
|
||||
redisDb *db = g_pserver->db+j;
|
||||
dict *d = db->pdict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
@@ -1294,13 +1337,12 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr;
|
||||
robj key, *o;
|
||||
long long expiretime;
|
||||
|
||||
keystr = (sds)dictGetKey(de);
|
||||
o = (robj*)dictGetVal(de);
|
||||
initStaticStringObject(key,keystr);
|
||||
|
||||
expiretime = getExpire(db,&key);
|
||||
expireEntry *pexpire = getExpire(db,&key);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
@@ -1326,11 +1368,23 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,expiretime) == 0) goto werr;
|
||||
if (pexpire != nullptr) {
|
||||
for (auto &subExpire : *pexpire) {
|
||||
if (subExpire.subkey() == nullptr)
|
||||
{
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
}
|
||||
else
|
||||
{
|
||||
char cmd[]="*4\r\n$12\r\nEXPIREMEMBER\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) goto werr;
|
||||
}
|
||||
if (rioWriteBulkLongLong(aof,subExpire.when()) == 0) goto werr; // common
|
||||
}
|
||||
}
|
||||
/* Read some diff from the parent process from time to time. */
|
||||
if (aof->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES) {
|
||||
@@ -1372,13 +1426,13 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
server.aof_child_diff = sdsempty();
|
||||
rioInitWithFile(&aof,fileno(fp));
|
||||
g_pserver->aof_child_diff = sdsempty();
|
||||
rioInitWithFile(&aof,fp);
|
||||
|
||||
if (server.aof_rewrite_incremental_fsync)
|
||||
if (g_pserver->aof_rewrite_incremental_fsync)
|
||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||
|
||||
if (server.aof_use_rdb_preamble) {
|
||||
if (g_pserver->aof_use_rdb_preamble) {
|
||||
int error;
|
||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
errno = error;
|
||||
@@ -1401,7 +1455,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
* happens after 20 ms without new data). */
|
||||
start = mstime();
|
||||
while(mstime()-start < 1000 && nodata < 20) {
|
||||
if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
|
||||
if (aeWait(g_pserver->aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
|
||||
{
|
||||
nodata++;
|
||||
continue;
|
||||
@@ -1412,13 +1466,13 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* Ask the master to stop sending diffs. */
|
||||
if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
|
||||
if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK)
|
||||
if (write(g_pserver->aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
|
||||
if (anetNonBlock(NULL,g_pserver->aof_pipe_read_ack_from_parent) != ANET_OK)
|
||||
goto werr;
|
||||
/* We read the ACK from the server using a 10 seconds timeout. Normally
|
||||
* it should reply ASAP, but just in case we lose its reply, we are sure
|
||||
* the child will eventually get terminated. */
|
||||
if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
|
||||
if (syncRead(g_pserver->aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
|
||||
byte != '!') goto werr;
|
||||
serverLog(LL_NOTICE,"Parent agreed to stop sending diffs. Finalizing AOF...");
|
||||
|
||||
@@ -1428,8 +1482,8 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
/* Write the received diff to the file. */
|
||||
serverLog(LL_NOTICE,
|
||||
"Concatenating %.2f MB of AOF diff received from parent.",
|
||||
(double) sdslen(server.aof_child_diff) / (1024*1024));
|
||||
if (rioWrite(&aof,server.aof_child_diff,sdslen(server.aof_child_diff)) == 0)
|
||||
(double) sdslen(g_pserver->aof_child_diff) / (1024*1024));
|
||||
if (rioWrite(&aof,g_pserver->aof_child_diff,sdslen(g_pserver->aof_child_diff)) == 0)
|
||||
goto werr;
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
@@ -1469,8 +1523,8 @@ void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
if (read(fd,&byte,1) == 1 && byte == '!') {
|
||||
serverLog(LL_NOTICE,"AOF rewrite child asks to stop sending diffs.");
|
||||
server.aof_stop_sending_diff = 1;
|
||||
if (write(server.aof_pipe_write_ack_to_child,"!",1) != 1) {
|
||||
g_pserver->aof_stop_sending_diff = 1;
|
||||
if (write(g_pserver->aof_pipe_write_ack_to_child,"!",1) != 1) {
|
||||
/* If we can't send the ack, inform the user, but don't try again
|
||||
* since in the other side the children will use a timeout if the
|
||||
* kernel can't buffer our write, or, the children was
|
||||
@@ -1481,7 +1535,7 @@ void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
/* Remove the handler since this can be called only one time during a
|
||||
* rewrite. */
|
||||
aeDeleteFileEventAsync(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,server.aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEvent(el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
}
|
||||
|
||||
/* Create the pipes used for parent - child process IPC during rewrite.
|
||||
@@ -1501,14 +1555,14 @@ int aofCreatePipes(void) {
|
||||
if (anetNonBlock(NULL,fds[1]) != ANET_OK) goto error;
|
||||
if (aeCreateFileEvent(serverTL->el, fds[2], AE_READABLE, aofChildPipeReadable, NULL) == AE_ERR) goto error;
|
||||
|
||||
server.aof_pipe_write_data_to_child = fds[1];
|
||||
server.aof_pipe_read_data_from_parent = fds[0];
|
||||
server.aof_pipe_write_ack_to_parent = fds[3];
|
||||
server.aof_pipe_read_ack_from_child = fds[2];
|
||||
server.el_alf_pip_read_ack_from_child = serverTL->el;
|
||||
server.aof_pipe_write_ack_to_child = fds[5];
|
||||
server.aof_pipe_read_ack_from_parent = fds[4];
|
||||
server.aof_stop_sending_diff = 0;
|
||||
g_pserver->aof_pipe_write_data_to_child = fds[1];
|
||||
g_pserver->aof_pipe_read_data_from_parent = fds[0];
|
||||
g_pserver->aof_pipe_write_ack_to_parent = fds[3];
|
||||
g_pserver->aof_pipe_read_ack_from_child = fds[2];
|
||||
g_pserver->el_alf_pip_read_ack_from_child = serverTL->el;
|
||||
g_pserver->aof_pipe_write_ack_to_child = fds[5];
|
||||
g_pserver->aof_pipe_read_ack_from_parent = fds[4];
|
||||
g_pserver->aof_stop_sending_diff = 0;
|
||||
return C_OK;
|
||||
|
||||
error:
|
||||
@@ -1519,14 +1573,25 @@ error:
|
||||
}
|
||||
|
||||
void aofClosePipes(void) {
|
||||
aeDeleteFileEventAsync(server.el_alf_pip_read_ack_from_child,server.aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEventAsync(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,server.aof_pipe_write_data_to_child,AE_WRITABLE);
|
||||
close(server.aof_pipe_write_data_to_child);
|
||||
close(server.aof_pipe_read_data_from_parent);
|
||||
close(server.aof_pipe_write_ack_to_parent);
|
||||
close(server.aof_pipe_read_ack_from_child);
|
||||
close(server.aof_pipe_write_ack_to_child);
|
||||
close(server.aof_pipe_read_ack_from_parent);
|
||||
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
|
||||
int res = aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofAckPipe,AE_READABLE);
|
||||
close (fdAofAckPipe);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
|
||||
int fdAofWritePipe = g_pserver->aof_pipe_write_data_to_child;
|
||||
res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofWritePipe,AE_WRITABLE);
|
||||
close(fdAofWritePipe);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
g_pserver->aof_pipe_write_data_to_child = -1;
|
||||
|
||||
close(g_pserver->aof_pipe_read_data_from_parent);
|
||||
close(g_pserver->aof_pipe_write_ack_to_parent);
|
||||
close(g_pserver->aof_pipe_write_ack_to_child);
|
||||
close(g_pserver->aof_pipe_read_ack_from_parent);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -1538,10 +1603,10 @@ void aofClosePipes(void) {
|
||||
* 1) The user calls BGREWRITEAOF
|
||||
* 2) Redis calls this function, that forks():
|
||||
* 2a) the child rewrite the append only file in a temp file.
|
||||
* 2b) the parent accumulates differences in server.aof_rewrite_buf.
|
||||
* 2b) the parent accumulates differences in g_pserver->aof_rewrite_buf.
|
||||
* 3) When the child finished '2a' exists.
|
||||
* 4) The parent will trap the exit code, if it's OK, will append the
|
||||
* data accumulated into server.aof_rewrite_buf into the temp file, and
|
||||
* data accumulated into g_pserver->aof_rewrite_buf into the temp file, and
|
||||
* finally will rename(2) the temp file in the actual file name.
|
||||
* The the new file is reopened as the new append only file. Profit!
|
||||
*/
|
||||
@@ -1549,7 +1614,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
@@ -1569,7 +1634,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
server.child_info_data.cow_size = private_dirty;
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_AOF);
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
@@ -1577,9 +1642,9 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
}
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
closeChildInfoPipe();
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1590,15 +1655,15 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
}
|
||||
serverLog(LL_NOTICE,
|
||||
"Background append only file rewriting started by pid %d",childpid);
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
server.aof_rewrite_time_start = time(NULL);
|
||||
server.aof_child_pid = childpid;
|
||||
g_pserver->aof_rewrite_scheduled = 0;
|
||||
g_pserver->aof_rewrite_time_start = time(NULL);
|
||||
g_pserver->aof_child_pid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||
* accumulated by the parent into g_pserver->aof_rewrite_buf will start
|
||||
* with a SELECT statement and it will be safe to merge. */
|
||||
server.aof_selected_db = -1;
|
||||
g_pserver->aof_selected_db = -1;
|
||||
replicationScriptCacheFlush();
|
||||
return C_OK;
|
||||
}
|
||||
@@ -1606,10 +1671,10 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
}
|
||||
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (server.aof_child_pid != -1) {
|
||||
if (g_pserver->aof_child_pid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (server.rdb_child_pid != -1) {
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
} else if (g_pserver->rdb_child_pid != -1) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
@@ -1628,7 +1693,7 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
/* Update the g_pserver->aof_current_size field explicitly using stat(2)
|
||||
* to check the size of the file. This is useful after a rewrite or after
|
||||
* a restart, normally the size is updated just adding the write length
|
||||
* to the current length, that is much faster. */
|
||||
@@ -1637,11 +1702,11 @@ void aofUpdateCurrentSize(void) {
|
||||
mstime_t latency;
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
if (redis_fstat(server.aof_fd,&sb) == -1) {
|
||||
if (redis_fstat(g_pserver->aof_fd,&sb) == -1) {
|
||||
serverLog(LL_WARNING,"Unable to obtain the AOF file length. stat: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
server.aof_current_size = sb.st_size;
|
||||
g_pserver->aof_current_size = sb.st_size;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fstat",latency);
|
||||
@@ -1663,7 +1728,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
* rewritten AOF. */
|
||||
latencyStartMonitor(latency);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
||||
(int)server.aof_child_pid);
|
||||
(int)g_pserver->aof_child_pid);
|
||||
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
||||
if (newfd == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1692,14 +1757,14 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
*
|
||||
* 1) AOF is DISABLED and this was a one time rewrite. The temporary
|
||||
* file will be renamed to the configured file. When this file already
|
||||
* exists, it will be unlinked, which may block the server.
|
||||
* exists, it will be unlinked, which may block the g_pserver->
|
||||
*
|
||||
* 2) AOF is ENABLED and the rewritten AOF will immediately start
|
||||
* receiving writes. After the temporary file is renamed to the
|
||||
* configured file, the original AOF file descriptor will be closed.
|
||||
* Since this will be the last reference to that file, closing it
|
||||
* causes the underlying file to be unlinked, which may block the
|
||||
* server.
|
||||
* g_pserver->
|
||||
*
|
||||
* To mitigate the blocking effect of the unlink operation (either
|
||||
* caused by rename(2) in scenario 1, or by close(2) in scenario 2), we
|
||||
@@ -1710,13 +1775,13 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
* guarantee atomicity for this switch has already happened by then, so
|
||||
* we don't care what the outcome or duration of that close operation
|
||||
* is, as long as the file descriptor is released again. */
|
||||
if (server.aof_fd == -1) {
|
||||
if (g_pserver->aof_fd == -1) {
|
||||
/* AOF disabled */
|
||||
|
||||
/* Don't care if this fails: oldfd will be -1 and we handle that.
|
||||
* One notable case of -1 return is if the old file does
|
||||
* not exist. */
|
||||
oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);
|
||||
oldfd = open(g_pserver->aof_filename,O_RDONLY|O_NONBLOCK);
|
||||
} else {
|
||||
/* AOF enabled */
|
||||
oldfd = -1; /* We'll set this to the current AOF filedes later. */
|
||||
@@ -1725,11 +1790,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
/* Rename the temporary file. This will not unlink the target file if
|
||||
* it exists, because we reference it with "oldfd". */
|
||||
latencyStartMonitor(latency);
|
||||
if (rename(tmpfile,server.aof_filename) == -1) {
|
||||
if (rename(tmpfile,g_pserver->aof_filename) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Error trying to rename the temporary AOF file %s into %s: %s",
|
||||
tmpfile,
|
||||
server.aof_filename,
|
||||
g_pserver->aof_filename,
|
||||
strerror(errno));
|
||||
close(newfd);
|
||||
if (oldfd != -1) close(oldfd);
|
||||
@@ -1738,34 +1803,34 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-rename",latency);
|
||||
|
||||
if (server.aof_fd == -1) {
|
||||
if (g_pserver->aof_fd == -1) {
|
||||
/* AOF disabled, we don't need to set the AOF file descriptor
|
||||
* to this new file, so we can close it. */
|
||||
close(newfd);
|
||||
} else {
|
||||
/* AOF enabled, replace the old fd with the new one. */
|
||||
oldfd = server.aof_fd;
|
||||
server.aof_fd = newfd;
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
oldfd = g_pserver->aof_fd;
|
||||
g_pserver->aof_fd = newfd;
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
redis_fsync(newfd);
|
||||
else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
else if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
aof_background_fsync(newfd);
|
||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
sdsfree(server.aof_buf);
|
||||
server.aof_buf = sdsempty();
|
||||
sdsfree(g_pserver->aof_buf);
|
||||
g_pserver->aof_buf = sdsempty();
|
||||
}
|
||||
|
||||
server.aof_lastbgrewrite_status = C_OK;
|
||||
g_pserver->aof_lastbgrewrite_status = C_OK;
|
||||
|
||||
serverLog(LL_NOTICE, "Background AOF rewrite finished successfully");
|
||||
/* Change state from WAIT_REWRITE to ON if needed */
|
||||
if (server.aof_state == AOF_WAIT_REWRITE)
|
||||
server.aof_state = AOF_ON;
|
||||
if (g_pserver->aof_state == AOF_WAIT_REWRITE)
|
||||
g_pserver->aof_state = AOF_ON;
|
||||
|
||||
/* Asynchronously close the overwritten AOF. */
|
||||
if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);
|
||||
@@ -1776,11 +1841,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
/* 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;
|
||||
g_pserver->aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
g_pserver->aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
@@ -1789,11 +1854,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
cleanup:
|
||||
aofClosePipes();
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(server.aof_child_pid);
|
||||
server.aof_child_pid = -1;
|
||||
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
|
||||
server.aof_rewrite_time_start = -1;
|
||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
||||
g_pserver->aof_child_pid = -1;
|
||||
g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start;
|
||||
g_pserver->aof_rewrite_time_start = -1;
|
||||
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
||||
if (server.aof_state == AOF_WAIT_REWRITE)
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
if (g_pserver->aof_state == AOF_WAIT_REWRITE)
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
}
|
||||
+3
-1
@@ -36,4 +36,6 @@ const char *ascii_logo =
|
||||
" Port: %d\n"
|
||||
" PID: %ld\n"
|
||||
" \n"
|
||||
" \n\n";
|
||||
" %s\n"
|
||||
" \n"
|
||||
" \n";
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
* Currently there is only a single operation, that is a background close(2)
|
||||
* system call. This is needed as when the process is the last owner of a
|
||||
* reference to a file closing it means unlinking it, and the deletion of the
|
||||
* file is slow, blocking the server.
|
||||
* file is slow, blocking the g_pserver->
|
||||
*
|
||||
* In the future we'll either continue implementing new things we need or
|
||||
* we'll switch to libeio. However there are probably long term uses for this
|
||||
@@ -85,7 +85,7 @@ struct bio_job {
|
||||
|
||||
void *bioProcessBackgroundJobs(void *arg);
|
||||
void lazyfreeFreeObjectFromBioThread(robj *o);
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2);
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, expireset *set);
|
||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt);
|
||||
|
||||
/* Make sure we have enough stack to perform all the things we do in the
|
||||
@@ -196,7 +196,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
if (job->arg1)
|
||||
lazyfreeFreeObjectFromBioThread((robj*)job->arg1);
|
||||
else if (job->arg2 && job->arg3)
|
||||
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(dict*)job->arg3);
|
||||
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(expireset*)job->arg3);
|
||||
else if (job->arg3)
|
||||
lazyfreeFreeSlotsMapFromBioThread((rax*)job->arg3);
|
||||
} else {
|
||||
|
||||
+189
-109
@@ -37,7 +37,7 @@
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with a input string length up to 512 MB. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t redisPopcount(const void *s, long count) {
|
||||
size_t bits = 0;
|
||||
unsigned char *p = (unsigned char*)s;
|
||||
uint32_t *p4;
|
||||
@@ -98,7 +98,7 @@ size_t redisPopcount(void *s, long count) {
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
long redisBitpos(const void *s, unsigned long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
@@ -396,6 +396,8 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
#define BITOP_OR 1
|
||||
#define BITOP_XOR 2
|
||||
#define BITOP_NOT 3
|
||||
#define BITOP_LSHIFT 4
|
||||
#define BITOP_RSHIFT 5
|
||||
|
||||
#define BITFIELDOP_GET 0
|
||||
#define BITFIELDOP_SET 1
|
||||
@@ -503,17 +505,17 @@ robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||
*
|
||||
* If the source object is NULL the function is guaranteed to return NULL
|
||||
* and set 'len' to 0. */
|
||||
unsigned char *getObjectReadOnlyString(robj *o, long *len, char *llbuf) {
|
||||
const unsigned char *getObjectReadOnlyString(robj_roptr o, long *len, char *llbuf) {
|
||||
serverAssert(o->type == OBJ_STRING);
|
||||
unsigned char *p = NULL;
|
||||
const unsigned char *p = NULL;
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (o && o->encoding == OBJ_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
p = (const unsigned char*) llbuf;
|
||||
if (len) *len = ll2string(llbuf,LONG_STR_SIZE,(long)ptrFromObj(o));
|
||||
} else if (o) {
|
||||
p = (unsigned char*) ptrFromObj(o);
|
||||
p = (const unsigned char*) ptrFromObj(o);
|
||||
if (len) *len = sdslen(szFromObj(o));
|
||||
} else {
|
||||
if (len) *len = 0;
|
||||
@@ -556,13 +558,13 @@ void setbitCommand(client *c) {
|
||||
((uint8_t*)ptrFromObj(o))[byte] = byteval;
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
/* GETBIT key offset */
|
||||
void getbitCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
char llbuf[32];
|
||||
size_t bitoffset;
|
||||
size_t byte, bit;
|
||||
@@ -571,7 +573,7 @@ void getbitCommand(client *c) {
|
||||
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
|
||||
return;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
@@ -590,9 +592,11 @@ void getbitCommand(client *c) {
|
||||
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
|
||||
void bitopCommand(client *c) {
|
||||
char *opname = szFromObj(c->argv[1]);
|
||||
robj *o, *targetkey = c->argv[2];
|
||||
unsigned long op, j, numkeys;
|
||||
robj **objects; /* Array of source objects. */
|
||||
robj *targetkey = c->argv[2];
|
||||
robj_roptr o;
|
||||
int op;
|
||||
unsigned long j, numkeys;
|
||||
robj_roptr *objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
and max len. */
|
||||
@@ -608,6 +612,10 @@ void bitopCommand(client *c) {
|
||||
op = BITOP_XOR;
|
||||
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
||||
op = BITOP_NOT;
|
||||
else if (!strcasecmp(opname, "lshift"))
|
||||
op = BITOP_LSHIFT;
|
||||
else if (!strcasecmp(opname, "rshift"))
|
||||
op = BITOP_RSHIFT;
|
||||
else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
@@ -619,16 +627,33 @@ void bitopCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool fShiftOp = (op == BITOP_LSHIFT) || (op == BITOP_RSHIFT);
|
||||
long long shift = 0;
|
||||
|
||||
/* Sanity check: SHIFTS only accept a single arg and an integer */
|
||||
if (fShiftOp) {
|
||||
if (c->argc != 5) {
|
||||
addReplyError(c,"BITOP SHIFT must be called with a single source key and an integer shift.");
|
||||
return;
|
||||
}
|
||||
if (getLongLongFromObject(c->argv[4], &shift) != C_OK) {
|
||||
addReplyError(c, "BITOP SHIFT's last parameter must be an integer");
|
||||
return;
|
||||
}
|
||||
if (op == BITOP_RSHIFT)
|
||||
shift = -shift;
|
||||
}
|
||||
|
||||
/* Lookup keys, and store pointers to the string objects into an array. */
|
||||
numkeys = c->argc - 3;
|
||||
numkeys = c->argc - (fShiftOp ? 4 : 3);
|
||||
src = (unsigned char**)zmalloc(sizeof(unsigned char*) * numkeys, MALLOC_LOCAL);
|
||||
len = (unsigned long*)zmalloc(sizeof(long) * numkeys, MALLOC_LOCAL);
|
||||
objects = (robj**)zmalloc(sizeof(robj*) * numkeys, MALLOC_LOCAL);
|
||||
objects = (robj_roptr*)zmalloc(sizeof(robj_roptr) * numkeys, MALLOC_LOCAL);
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
o = lookupKeyRead(c->db,c->argv[j+3]);
|
||||
/* Handle non-existing keys as empty strings. */
|
||||
if (o == NULL) {
|
||||
objects[j] = NULL;
|
||||
if (o == nullptr) {
|
||||
objects[j] = nullptr;
|
||||
src[j] = NULL;
|
||||
len[j] = 0;
|
||||
minlen = 0;
|
||||
@@ -653,94 +678,143 @@ void bitopCommand(client *c) {
|
||||
if (j == 0 || len[j] < minlen) minlen = len[j];
|
||||
}
|
||||
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
if (fShiftOp)
|
||||
{
|
||||
long newlen = (long)maxlen + shift/CHAR_BIT;
|
||||
if (shift > 0 && (shift % CHAR_BIT) != 0)
|
||||
newlen++;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
if (newlen < 0)
|
||||
newlen = 0;
|
||||
|
||||
if (newlen)
|
||||
{
|
||||
res = (unsigned char*) sdsnewlen(NULL,newlen);
|
||||
if (shift >= 0)
|
||||
{ // left shift
|
||||
long byteoffset = shift/CHAR_BIT;
|
||||
memset(res, 0, byteoffset);
|
||||
long srcLen = newlen - byteoffset - ((shift % CHAR_BIT) ? 1 : 0);
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
// now the bitshift+copy
|
||||
unsigned bitshift = shift % CHAR_BIT;
|
||||
unsigned char carry = 0;
|
||||
for (long iSrc = 0; iSrc < srcLen; ++iSrc)
|
||||
{
|
||||
res[byteoffset+iSrc] = (src[0][iSrc] << bitshift) | carry;
|
||||
carry = src[0][iSrc] >> (CHAR_BIT - bitshift);
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
if (bitshift)
|
||||
res[newlen-1] = carry;
|
||||
}
|
||||
else
|
||||
{ // right shift
|
||||
long byteoffset = -shift/CHAR_BIT;
|
||||
unsigned bitshift = -shift % CHAR_BIT;
|
||||
if (bitshift)
|
||||
++byteoffset;
|
||||
res[0] = (src[0][byteoffset] << (CHAR_BIT-bitshift));
|
||||
if (byteoffset > 0)
|
||||
res[0] |= (src[0][byteoffset-1] >> bitshift);
|
||||
for (long idx = 1; idx < newlen; ++idx)
|
||||
{
|
||||
res[idx] = (src[0][byteoffset+idx] << (CHAR_BIT-bitshift)) | (src[0][byteoffset+idx-1] >> bitshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
maxlen = newlen; // this is to ensure we DEL below if newlen was 0
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND: output &= byte; break;
|
||||
case BITOP_OR: output |= byte; break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
#endif
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND: output &= byte; break;
|
||||
case BITOP_OR: output |= byte; break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
@@ -753,7 +827,7 @@ void bitopCommand(client *c) {
|
||||
|
||||
/* Store the computed value into the target key */
|
||||
if (maxlen) {
|
||||
o = createObject(OBJ_STRING,res);
|
||||
robj *o = createObject(OBJ_STRING,res);
|
||||
setKey(c->db,targetkey,o);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
decrRefCount(o);
|
||||
@@ -761,19 +835,19 @@ void bitopCommand(client *c) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
}
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
|
||||
}
|
||||
|
||||
/* BITCOUNT key [start end] */
|
||||
void bitcountCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
long start, end, strlen;
|
||||
unsigned char *p;
|
||||
const unsigned char *p;
|
||||
char llbuf[LONG_STR_SIZE];
|
||||
|
||||
/* Lookup, check for type, and return 0 for non existing keys. */
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
|
||||
@@ -816,9 +890,9 @@ void bitcountCommand(client *c) {
|
||||
|
||||
/* BITPOS key bit [start [end]] */
|
||||
void bitposCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
long bit, start, end, strlen;
|
||||
unsigned char *p;
|
||||
const unsigned char *p;
|
||||
char llbuf[LONG_STR_SIZE];
|
||||
int end_given = 0;
|
||||
|
||||
@@ -834,7 +908,7 @@ void bitposCommand(client *c) {
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the fist clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == nullptr) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
@@ -912,7 +986,7 @@ struct bitfieldOp {
|
||||
};
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
size_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
@@ -994,12 +1068,18 @@ 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)) return;
|
||||
if (o != nullptr && checkType(c,o,OBJ_STRING)) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
highest_write_offset)) == NULL) return;
|
||||
highest_write_offset)) == nullptr) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addReplyArrayLen(c,numops);
|
||||
@@ -1084,10 +1164,10 @@ void bitfieldCommand(client *c) {
|
||||
/* GET */
|
||||
unsigned char buf[9];
|
||||
long strlen = 0;
|
||||
unsigned char *src = NULL;
|
||||
const unsigned char *src = NULL;
|
||||
char llbuf[LONG_STR_SIZE];
|
||||
|
||||
if (o != NULL)
|
||||
if (o != nullptr)
|
||||
src = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
|
||||
/* For GET we use a trick: before executing the operation
|
||||
@@ -1119,7 +1199,7 @@ void bitfieldCommand(client *c) {
|
||||
if (changes) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty += changes;
|
||||
g_pserver->dirty += changes;
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
+24
-21
@@ -64,6 +64,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <mutex>
|
||||
|
||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
||||
|
||||
@@ -110,8 +111,8 @@ void blockClient(client *c, int btype) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
c->flags |= CLIENT_BLOCKED;
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
server.blocked_clients_by_type[btype]++;
|
||||
g_pserver->blocked_clients++;
|
||||
g_pserver->blocked_clients_by_type[btype]++;
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -122,8 +123,8 @@ void processUnblockedClients(int iel) {
|
||||
|
||||
listNode *ln;
|
||||
client *c;
|
||||
list *unblocked_clients = server.rgthreadvar[iel].unblocked_clients;
|
||||
serverAssert(iel == (serverTL - server.rgthreadvar));
|
||||
list *unblocked_clients = g_pserver->rgthreadvar[iel].unblocked_clients;
|
||||
serverAssert(iel == (serverTL - g_pserver->rgthreadvar));
|
||||
|
||||
while (listLength(unblocked_clients)) {
|
||||
ln = listFirst(unblocked_clients);
|
||||
@@ -171,7 +172,7 @@ void queueClientForReprocessing(client *c) {
|
||||
fastlock_lock(&c->lock);
|
||||
if (!(c->flags & CLIENT_UNBLOCKED)) {
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.rgthreadvar[c->iel].unblocked_clients,c);
|
||||
listAddNodeTail(g_pserver->rgthreadvar[c->iel].unblocked_clients,c);
|
||||
}
|
||||
fastlock_unlock(&c->lock);
|
||||
}
|
||||
@@ -180,6 +181,7 @@ void queueClientForReprocessing(client *c) {
|
||||
* of operation the client is blocking for. */
|
||||
void unblockClient(client *c) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(c->lock.fOwnLock());
|
||||
if (c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM) {
|
||||
@@ -193,8 +195,8 @@ void unblockClient(client *c) {
|
||||
}
|
||||
/* Clear the flags, and put the client in the unblocked list so that
|
||||
* we'll process new commands in its query buffer ASAP. */
|
||||
server.blocked_clients--;
|
||||
server.blocked_clients_by_type[c->btype]--;
|
||||
g_pserver->blocked_clients--;
|
||||
g_pserver->blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
queueClientForReprocessing(c);
|
||||
@@ -219,8 +221,8 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
|
||||
/* Mass-unblock clients because something changed in the instance that makes
|
||||
* blocking no longer safe. For example clients blocked in list operations
|
||||
* in an instance which turns from master to slave is unsafe, so this function
|
||||
* is called when a master turns into a slave.
|
||||
* in an instance which turns from master to replica is unsafe, so this function
|
||||
* is called when a master turns into a replica.
|
||||
*
|
||||
* The semantics is to send an -UNBLOCKED error to the client, disconnecting
|
||||
* it at the same time. */
|
||||
@@ -229,7 +231,7 @@ void disconnectAllBlockedClients(void) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
|
||||
@@ -252,7 +254,7 @@ void disconnectAllBlockedClients(void) {
|
||||
*
|
||||
* All the keys with at least one client blocked that received at least
|
||||
* one new element via some write operation are accumulated into
|
||||
* the server.ready_keys list. This function will run the list and will
|
||||
* the g_pserver->ready_keys list. This function will run the list and will
|
||||
* serve clients accordingly. Note that the function will iterate again and
|
||||
* again as a result of serving BRPOPLPUSH we can have new blocking clients
|
||||
* to serve because of the PUSH side of BRPOPLPUSH.
|
||||
@@ -268,15 +270,15 @@ void disconnectAllBlockedClients(void) {
|
||||
* do, the function is already fair. */
|
||||
void handleClientsBlockedOnKeys(void) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
while(listLength(server.ready_keys) != 0) {
|
||||
while(listLength(g_pserver->ready_keys) != 0) {
|
||||
list *l;
|
||||
|
||||
/* Point server.ready_keys to a fresh list and save the current one
|
||||
/* Point g_pserver->ready_keys to a fresh list and save the current one
|
||||
* locally. This way as we run the old list we are free to call
|
||||
* signalKeyAsReady() that may push new elements in server.ready_keys
|
||||
* signalKeyAsReady() that may push new elements in g_pserver->ready_keys
|
||||
* when handling clients blocked into BRPOPLPUSH. */
|
||||
l = server.ready_keys;
|
||||
server.ready_keys = listCreate();
|
||||
l = g_pserver->ready_keys;
|
||||
g_pserver->ready_keys = listCreate();
|
||||
|
||||
while(listLength(l) != 0) {
|
||||
listNode *ln = listFirst(l);
|
||||
@@ -301,6 +303,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
std::unique_lock<decltype(client::lock)> lock(receiver->lock);
|
||||
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
@@ -385,8 +388,8 @@ void handleClientsBlockedOnKeys(void) {
|
||||
/* Replicate the command. */
|
||||
robj *argv[2];
|
||||
struct redisCommand *cmd = where == ZSET_MIN ?
|
||||
server.zpopminCommand :
|
||||
server.zpopmaxCommand;
|
||||
cserver.zpopminCommand :
|
||||
cserver.zpopmaxCommand;
|
||||
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
|
||||
argv[1] = rl->key;
|
||||
incrRefCount(rl->key);
|
||||
@@ -613,7 +616,7 @@ void unblockClientWaitingData(client *c) {
|
||||
}
|
||||
|
||||
/* If the specified key has clients blocked waiting for list pushes, this
|
||||
* function will put the key reference into the server.ready_keys list.
|
||||
* function will put the key reference into the g_pserver->ready_keys list.
|
||||
* Note that db->ready_keys is a hash table that allows us to avoid putting
|
||||
* the same key again and again in the list in case of multiple pushes
|
||||
* made by a script or in the context of MULTI/EXEC.
|
||||
@@ -628,12 +631,12 @@ void signalKeyAsReady(redisDb *db, robj *key) {
|
||||
/* Key was already signaled? No need to queue it again. */
|
||||
if (dictFind(db->ready_keys,key) != NULL) return;
|
||||
|
||||
/* Ok, we need to queue this key into server.ready_keys. */
|
||||
/* Ok, we need to queue this key into g_pserver->ready_keys. */
|
||||
rl = (readyList*)zmalloc(sizeof(*rl), MALLOC_SHARED);
|
||||
rl->key = key;
|
||||
rl->db = db;
|
||||
incrRefCount(key);
|
||||
listAddNodeTail(server.ready_keys,rl);
|
||||
listAddNodeTail(g_pserver->ready_keys,rl);
|
||||
|
||||
/* We also add the key in the db->ready_keys dictionary in order
|
||||
* to avoid adding it multiple times into a list with a simple O(1)
|
||||
|
||||
+22
-22
@@ -34,52 +34,52 @@
|
||||
* RDB / AOF saving process from the child to the parent (for instance
|
||||
* the amount of copy on write memory used) */
|
||||
void openChildInfoPipe(void) {
|
||||
if (pipe(server.child_info_pipe) == -1) {
|
||||
if (pipe(g_pserver->child_info_pipe) == -1) {
|
||||
/* On error our two file descriptors should be still set to -1,
|
||||
* but we call anyway cloesChildInfoPipe() since can't hurt. */
|
||||
closeChildInfoPipe();
|
||||
} else if (anetNonBlock(NULL,server.child_info_pipe[0]) != ANET_OK) {
|
||||
} else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) {
|
||||
closeChildInfoPipe();
|
||||
} else {
|
||||
memset(&server.child_info_data,0,sizeof(server.child_info_data));
|
||||
memset(&g_pserver->child_info_data,0,sizeof(g_pserver->child_info_data));
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the pipes opened with openChildInfoPipe(). */
|
||||
void closeChildInfoPipe(void) {
|
||||
if (server.child_info_pipe[0] != -1 ||
|
||||
server.child_info_pipe[1] != -1)
|
||||
if (g_pserver->child_info_pipe[0] != -1 ||
|
||||
g_pserver->child_info_pipe[1] != -1)
|
||||
{
|
||||
close(server.child_info_pipe[0]);
|
||||
close(server.child_info_pipe[1]);
|
||||
server.child_info_pipe[0] = -1;
|
||||
server.child_info_pipe[1] = -1;
|
||||
close(g_pserver->child_info_pipe[0]);
|
||||
close(g_pserver->child_info_pipe[1]);
|
||||
g_pserver->child_info_pipe[0] = -1;
|
||||
g_pserver->child_info_pipe[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send COW data to parent. The child should call this function after populating
|
||||
* the corresponding fields it want to sent (according to the process type). */
|
||||
void sendChildInfo(int ptype) {
|
||||
if (server.child_info_pipe[1] == -1) return;
|
||||
server.child_info_data.magic = CHILD_INFO_MAGIC;
|
||||
server.child_info_data.process_type = ptype;
|
||||
ssize_t wlen = sizeof(server.child_info_data);
|
||||
if (write(server.child_info_pipe[1],&server.child_info_data,wlen) != wlen) {
|
||||
if (g_pserver->child_info_pipe[1] == -1) return;
|
||||
g_pserver->child_info_data.magic = CHILD_INFO_MAGIC;
|
||||
g_pserver->child_info_data.process_type = ptype;
|
||||
ssize_t wlen = sizeof(g_pserver->child_info_data);
|
||||
if (write(g_pserver->child_info_pipe[1],&g_pserver->child_info_data,wlen) != wlen) {
|
||||
/* Nothing to do on error, this will be detected by the other side. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive COW data from parent. */
|
||||
void receiveChildInfo(void) {
|
||||
if (server.child_info_pipe[0] == -1) return;
|
||||
ssize_t wlen = sizeof(server.child_info_data);
|
||||
if (read(server.child_info_pipe[0],&server.child_info_data,wlen) == wlen &&
|
||||
server.child_info_data.magic == CHILD_INFO_MAGIC)
|
||||
if (g_pserver->child_info_pipe[0] == -1) return;
|
||||
ssize_t wlen = sizeof(g_pserver->child_info_data);
|
||||
if (read(g_pserver->child_info_pipe[0],&g_pserver->child_info_data,wlen) == wlen &&
|
||||
g_pserver->child_info_data.magic == CHILD_INFO_MAGIC)
|
||||
{
|
||||
if (server.child_info_data.process_type == CHILD_INFO_TYPE_RDB) {
|
||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
||||
if (g_pserver->child_info_data.process_type == CHILD_INFO_TYPE_RDB) {
|
||||
g_pserver->stat_rdb_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
} else if (g_pserver->child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
g_pserver->stat_aof_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
+450
-392
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <assert.h>
|
||||
|
||||
/*************************************************
|
||||
* compactvector - similar to std::vector but optimized for minimal memory
|
||||
*
|
||||
* Notable differences:
|
||||
* - Limited to 2^32 elements
|
||||
* - Grows linearly not exponentially
|
||||
*
|
||||
*************************************************/
|
||||
|
||||
template<typename T, bool MEMMOVE_SAFE=false>
|
||||
class compactvector
|
||||
{
|
||||
static_assert(MEMMOVE_SAFE || std::is_trivially_copyable<T>::value, "compactvector requires trivially copyable types");
|
||||
T *m_data = nullptr;
|
||||
unsigned m_celem = 0;
|
||||
unsigned m_max = 0;
|
||||
|
||||
public:
|
||||
typedef T* iterator;
|
||||
|
||||
compactvector() noexcept = default;
|
||||
~compactvector() noexcept
|
||||
{
|
||||
clear(); // call dtors
|
||||
zfree(m_data);
|
||||
}
|
||||
|
||||
compactvector(compactvector &) noexcept = delete;
|
||||
|
||||
compactvector(compactvector &&src) noexcept
|
||||
{
|
||||
m_data = src.m_data;
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
src.m_data = nullptr;
|
||||
src.m_celem = 0;
|
||||
src.m_max = 0;
|
||||
}
|
||||
|
||||
compactvector &operator=(const compactvector&) noexcept = delete;
|
||||
compactvector &operator=(compactvector &&src) noexcept
|
||||
{
|
||||
zfree(m_data);
|
||||
m_data = src.m_data;
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
src.m_data = nullptr;
|
||||
src.m_celem = 0;
|
||||
src.m_max = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T* begin() { return m_data; }
|
||||
inline const T* begin() const { return m_data; }
|
||||
|
||||
inline T* end() { return m_data + m_celem; }
|
||||
inline const T* end() const { return m_data + m_celem; }
|
||||
|
||||
T* insert(T* where, T &val)
|
||||
{
|
||||
assert(where >= m_data);
|
||||
size_t idx = where - m_data;
|
||||
if (m_celem >= m_max)
|
||||
{
|
||||
if (m_max < 2)
|
||||
m_max = 2;
|
||||
else
|
||||
m_max = m_max + 4;
|
||||
|
||||
m_data = (T*)zrealloc(m_data, sizeof(T) * m_max, MALLOC_LOCAL);
|
||||
m_max = zmalloc_usable(m_data) / sizeof(T);
|
||||
}
|
||||
assert(idx < m_max);
|
||||
where = m_data + idx;
|
||||
memmove(reinterpret_cast<void*>(m_data + idx + 1), reinterpret_cast<const void*>(m_data + idx), (m_celem - idx)*sizeof(T));
|
||||
new(m_data + idx) T(std::move(val));
|
||||
++m_celem;
|
||||
return where;
|
||||
}
|
||||
|
||||
T &operator[](size_t idx)
|
||||
{
|
||||
assert(idx < m_celem);
|
||||
return m_data[idx];
|
||||
}
|
||||
const T &operator[](size_t idx) const
|
||||
{
|
||||
assert(idx < m_celem);
|
||||
return m_data[idx];
|
||||
}
|
||||
|
||||
T& back() { assert(m_celem > 0); return m_data[m_celem-1]; }
|
||||
const T& back() const { assert(m_celem > 0); return m_data[m_celem-1]; }
|
||||
|
||||
void erase(T* where)
|
||||
{
|
||||
assert(where >= m_data);
|
||||
size_t idx = where - m_data;
|
||||
assert(idx < m_celem);
|
||||
where->~T();
|
||||
memmove(reinterpret_cast<void*>(where), reinterpret_cast<const void*>(where+1), ((m_celem - idx - 1)*sizeof(T)));
|
||||
--m_celem;
|
||||
|
||||
if (m_celem == 0)
|
||||
{
|
||||
zfree(m_data);
|
||||
m_data = nullptr;
|
||||
m_max = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void shrink_to_fit()
|
||||
{
|
||||
if (m_max == m_celem)
|
||||
return;
|
||||
m_data = (T*)zrealloc(m_data, sizeof(T) * m_celem, MALLOC_LOCAL);
|
||||
m_max = m_celem; // NOTE: We do not get the usable size here, because this could cause us to continually realloc
|
||||
}
|
||||
|
||||
size_t bytes_used() const
|
||||
{
|
||||
return sizeof(this) + (m_max * sizeof(T));
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (size_t idx = 0; idx < m_celem; ++idx)
|
||||
m_data[idx].~T();
|
||||
zfree(m_data);
|
||||
m_data = nullptr;
|
||||
m_celem = 0;
|
||||
m_max = 0;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return m_celem == 0;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return m_celem;
|
||||
}
|
||||
|
||||
T* data() noexcept { return m_data; }
|
||||
const T* data() const noexcept { return m_data; }
|
||||
};
|
||||
static_assert(sizeof(compactvector<void*>) <= 16, "not compact");
|
||||
+541
-682
File diff suppressed because it is too large.
Load diff
+135
@@ -0,0 +1,135 @@
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
void freeCronObject(robj_roptr o)
|
||||
{
|
||||
delete reinterpret_cast<const cronjob*>(ptrFromObj(o));
|
||||
}
|
||||
|
||||
// CRON [name] [single shot] [optional: start] [delay] [script] [numkeys] [key N] [arg N]
|
||||
void cronCommand(client *c)
|
||||
{
|
||||
int arg_offset = 0;
|
||||
static const int ARG_NAME = 1;
|
||||
static const int ARG_SINGLESHOT = 2;
|
||||
static const int ARG_EXPIRE = 3;
|
||||
#define ARG_SCRIPT (4+arg_offset)
|
||||
#define ARG_NUMKEYS (5+arg_offset)
|
||||
#define ARG_KEYSTART (6+arg_offset)
|
||||
|
||||
bool fSingleShot = false;
|
||||
if (strcasecmp("single", szFromObj(c->argv[ARG_SINGLESHOT])) == 0) {
|
||||
fSingleShot = true;
|
||||
} else {
|
||||
if (strcasecmp("repeat", szFromObj(c->argv[ARG_SINGLESHOT])) != 0) {
|
||||
addReply(c, shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
long long interval;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[ARG_EXPIRE], &interval, "missing expire time") != C_OK)
|
||||
return;
|
||||
|
||||
long long base = g_pserver->mstime;
|
||||
if (getLongLongFromObject(c->argv[ARG_EXPIRE+1], &base) == C_OK) {
|
||||
arg_offset++;
|
||||
std::swap(base, interval);
|
||||
}
|
||||
|
||||
if (interval <= 0)
|
||||
{
|
||||
addReplyError(c, "interval must be positive");
|
||||
return;
|
||||
}
|
||||
|
||||
long numkeys = 0;
|
||||
if (c->argc > ARG_NUMKEYS)
|
||||
{
|
||||
if (getLongFromObjectOrReply(c, c->argv[ARG_NUMKEYS], &numkeys, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc < (6 + numkeys)) {
|
||||
addReplyError(c, "Missing arguments or numkeys is too big");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<cronjob> spjob = std::make_unique<cronjob>();
|
||||
spjob->script = sdsstring(sdsdup(szFromObj(c->argv[ARG_SCRIPT])));
|
||||
spjob->interval = (uint64_t)interval;
|
||||
spjob->startTime = (uint64_t)base;
|
||||
spjob->fSingleShot = fSingleShot;
|
||||
spjob->dbNum = c->db - g_pserver->db;
|
||||
for (long i = 0; i < numkeys; ++i)
|
||||
spjob->veckeys.emplace_back(sdsdup(szFromObj(c->argv[ARG_KEYSTART+i])));
|
||||
for (long i = ARG_KEYSTART + numkeys; i < c->argc; ++i)
|
||||
spjob->vecargs.emplace_back(sdsdup(szFromObj(c->argv[i])));
|
||||
|
||||
robj *o = createObject(OBJ_CRON, spjob.release());
|
||||
setKey(c->db, c->argv[ARG_NAME], o);
|
||||
decrRefCount(o);
|
||||
// use an expire to trigger execution. Note: We use a subkey expire here so legacy clients don't delete it.
|
||||
setExpire(c, c->db, c->argv[ARG_NAME], c->argv[ARG_NAME], base + interval);
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
void executeCronJobExpireHook(const char *key, robj *o)
|
||||
{
|
||||
serverAssert(o->type == OBJ_CRON);
|
||||
cronjob *job = (cronjob*)ptrFromObj(o);
|
||||
|
||||
client *cFake = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
cFake->lock.lock();
|
||||
cFake->authenticated = 1;
|
||||
cFake->puser = nullptr;
|
||||
selectDb(cFake, job->dbNum);
|
||||
serverAssert(cFake->argc == 0);
|
||||
|
||||
// Setup the args for the EVAL command
|
||||
cFake->argc = 3 + job->veckeys.size() + job->vecargs.size();
|
||||
cFake->argv = (robj**)zmalloc(sizeof(robj*) * cFake->argc, MALLOC_LOCAL);
|
||||
cFake->argv[0] = createStringObject("EVAL", 4);
|
||||
cFake->argv[1] = createStringObject(job->script.get(), job->script.size());
|
||||
cFake->argv[2] = createStringObjectFromLongLong(job->veckeys.size());
|
||||
for (size_t i = 0; i < job->veckeys.size(); ++i)
|
||||
cFake->argv[3+i] = createStringObject(job->veckeys[i].get(), job->veckeys[i].size());
|
||||
for (size_t i = 0; i < job->vecargs.size(); ++i)
|
||||
cFake->argv[3+job->veckeys.size()+i] = createStringObject(job->vecargs[i].get(), job->vecargs[i].size());
|
||||
|
||||
evalCommand(cFake);
|
||||
resetClient(cFake);
|
||||
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
int dbId = job->dbNum;
|
||||
if (job->fSingleShot)
|
||||
{
|
||||
dbSyncDelete(cFake->db, keyobj);
|
||||
}
|
||||
else
|
||||
{
|
||||
job->startTime += job->interval;
|
||||
if (job->startTime < (uint64_t)g_pserver->mstime)
|
||||
{
|
||||
// If we are more than one interval in the past then fast forward to
|
||||
// the first interval still in the future. If startTime wasn't zero align
|
||||
// this to the original startTime, if it was zero align to now
|
||||
if (job->startTime == job->interval)
|
||||
{ // startTime was 0
|
||||
job->startTime = g_pserver->mstime + job->interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delta = g_pserver->mstime - job->startTime;
|
||||
auto multiple = (delta / job->interval)+1;
|
||||
job->startTime += job->interval * multiple;
|
||||
}
|
||||
}
|
||||
setExpire(cFake, cFake->db, keyobj, keyobj, job->startTime);
|
||||
}
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_KEYEVENT, "CRON Executed", keyobj, dbId);
|
||||
decrRefCount(keyobj);
|
||||
|
||||
// o is invalid at this point
|
||||
freeClient(cFake);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
struct cronjob
|
||||
{
|
||||
sdsstring script;
|
||||
uint64_t interval;
|
||||
uint64_t startTime;
|
||||
std::vector<sdsstring> veckeys;
|
||||
std::vector<sdsstring> vecargs;
|
||||
int dbNum = 0;
|
||||
bool fSingleShot = false;
|
||||
};
|
||||
|
||||
void freeCronObject(robj_roptr o);
|
||||
void executeCronJobExpireHook(const char *key, robj *o);
|
||||
void cronCommand(client *c);
|
||||
+406
-163
File diff suppressed because it is too large.
Load diff
+137
-53
@@ -27,6 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define NO_DEPRECATE_FREE 1 // we are required to call the real free() in this CU
|
||||
#include "server.h"
|
||||
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
||||
#include "crc64.h"
|
||||
@@ -45,6 +46,7 @@ typedef ucontext_t sigcontext_t;
|
||||
#include <fcntl.h>
|
||||
#include "bio.h"
|
||||
#include <unistd.h>
|
||||
#include <cxxabi.h>
|
||||
#endif /* HAVE_BACKTRACE */
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
@@ -53,6 +55,8 @@ typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int g_fInCrash = false;
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
|
||||
/* Compute the sha1 of string at 's' with 'len' bytes long.
|
||||
@@ -61,7 +65,7 @@ typedef ucontext_t sigcontext_t;
|
||||
* "add" digests relative to unordered elements.
|
||||
*
|
||||
* So digest(a,b,c,d) will be the same of digest(b,a,c,d) */
|
||||
extern "C" void xorDigest(unsigned char *digest, const void *ptr, size_t len) {
|
||||
void xorDigest(unsigned char *digest, const void *ptr, size_t len) {
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20];
|
||||
const unsigned char *s = (const unsigned char*)ptr;
|
||||
@@ -95,9 +99,9 @@ void xorStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
* Also note that mixdigest("foo") followed by mixdigest("bar")
|
||||
* will lead to a different digest compared to "fo", "obar".
|
||||
*/
|
||||
void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
void mixDigest(unsigned char *digest, const void *ptr, size_t len) {
|
||||
SHA1_CTX ctx;
|
||||
char *s = (char*)ptr;
|
||||
const char *s = (const char*)ptr;
|
||||
|
||||
xorDigest(digest,s,len);
|
||||
SHA1Init(&ctx);
|
||||
@@ -105,7 +109,7 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
SHA1Final(digest,&ctx);
|
||||
}
|
||||
|
||||
void mixStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
void mixStringObjectDigest(unsigned char *digest, robj_roptr o) {
|
||||
o = getDecodedObject(o);
|
||||
mixDigest(digest,ptrFromObj(o),sdslen(szFromObj(o)));
|
||||
decrRefCount(o);
|
||||
@@ -119,12 +123,16 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
* Note that this function does not reset the initial 'digest' passed, it
|
||||
* will continue mixing this object digest to anything that was already
|
||||
* present. */
|
||||
void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
|
||||
void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj_roptr o) {
|
||||
uint32_t aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
long long expiretime = getExpire(db,keyobj);
|
||||
expireEntry *pexpire = getExpire(db,keyobj);
|
||||
long long expiretime = -1;
|
||||
char buf[128];
|
||||
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expiretime);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixStringObjectDigest(digest,o);
|
||||
@@ -265,8 +273,8 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
|
||||
memset(final,0,20); /* Start with a clean result */
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
|
||||
if (dictSize(db->pdict) == 0) continue;
|
||||
di = dictGetSafeIterator(db->pdict);
|
||||
@@ -344,7 +352,7 @@ NULL
|
||||
(RESTART_SERVER_GRACEFULLY|RESTART_SERVER_CONFIG_REWRITE) :
|
||||
RESTART_SERVER_NONE;
|
||||
restartServer(flags,delay);
|
||||
addReplyError(c,"failed to restart the server. Check server logs.");
|
||||
addReplyError(c,"failed to restart the g_pserver-> Check server logs.");
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"oom")) {
|
||||
void *ptr = zmalloc(ULONG_MAX, MALLOC_LOCAL); /* Should trigger an out of memory. */
|
||||
zfree(ptr);
|
||||
@@ -373,16 +381,16 @@ NULL
|
||||
serverLog(LL_WARNING,"DB reloaded by DEBUG RELOAD");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"loadaof")) {
|
||||
if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1);
|
||||
if (g_pserver->aof_state != AOF_OFF) flushAppendOnlyFile(1);
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
protectClient(c);
|
||||
int ret = loadAppendOnlyFile(server.aof_filename);
|
||||
int ret = loadAppendOnlyFile(g_pserver->aof_filename);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
server.dirty = 0; /* Prevent AOF / replication */
|
||||
g_pserver->dirty = 0; /* Prevent AOF / replication */
|
||||
serverLog(LL_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"object") && c->argc == 3) {
|
||||
@@ -434,7 +442,7 @@ NULL
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, val->refcount,
|
||||
(void*)val, static_cast<int>(val->getrefcount(std::memory_order_relaxed)),
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
|
||||
@@ -523,7 +531,7 @@ NULL
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
|
||||
robj_roptr o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
|
||||
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
|
||||
|
||||
sds d = sdsempty();
|
||||
@@ -595,12 +603,12 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"set-active-expire") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
server.active_expire_enabled = atoi(szFromObj(c->argv[2]));
|
||||
g_pserver->active_expire_enabled = atoi(szFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"lua-always-replicate-commands") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
server.lua_always_replicate_commands = atoi(szFromObj(c->argv[2]));
|
||||
g_pserver->lua_always_replicate_commands = atoi(szFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"error") && c->argc == 3) {
|
||||
sds errstr = sdsnewlen("-",1);
|
||||
@@ -627,18 +635,18 @@ NULL
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK)
|
||||
return;
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
if (dbid < 0 || dbid >= cserver.dbnum) {
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].pdict);
|
||||
dictGetStats(buf,sizeof(buf),g_pserver->db[dbid].pdict);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||
stats = sdscat(stats,buf);
|
||||
stats = sdscatprintf(stats,"[Expires set]\n");
|
||||
g_pserver->db[dbid].setexpire->getstats(buf, sizeof(buf));
|
||||
stats = sdscat(stats, buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) {
|
||||
@@ -674,10 +682,30 @@ NULL
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2)
|
||||
{
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2) {
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 3) {
|
||||
c->flags |= CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY;
|
||||
if (!strcasecmp(szFromObj(c->argv[2]), "yes"))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)zcalloc(sizeof(redisMaster), MALLOC_LOCAL);
|
||||
mi->master = c;
|
||||
listAddNodeHead(g_pserver->masters, mi);
|
||||
}
|
||||
else if (strcasecmp(szFromObj(c->argv[2]), "flagonly")) // if we didn't set flagonly assume its an unset
|
||||
{
|
||||
serverAssert(c->flags & CLIENT_MASTER);
|
||||
if (listLength(g_pserver->masters))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)listNodeValue(listFirst(g_pserver->masters));
|
||||
serverAssert(mi->master == c);
|
||||
listDelNode(g_pserver->masters, listFirst(g_pserver->masters));
|
||||
zfree(mi);
|
||||
}
|
||||
c->flags &= ~(CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -691,9 +719,9 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED ===");
|
||||
serverLog(LL_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
|
||||
#ifdef HAVE_BACKTRACE
|
||||
server.assert_failed = estr;
|
||||
server.assert_file = file;
|
||||
server.assert_line = line;
|
||||
g_pserver->assert_failed = estr;
|
||||
g_pserver->assert_file = file;
|
||||
g_pserver->assert_line = line;
|
||||
serverLog(LL_WARNING,"(forcing SIGSEGV to print the bug report.)");
|
||||
#endif
|
||||
*((char*)-1) = 'x';
|
||||
@@ -704,7 +732,7 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||
serverLog(LL_WARNING,"client->flags = %d", c->flags);
|
||||
serverLog(LL_WARNING,"client->flags = %llu", static_cast<unsigned long long>(c->flags));
|
||||
serverLog(LL_WARNING,"client->fd = %d", c->fd);
|
||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||
for (j=0; j < c->argc; j++) {
|
||||
@@ -719,14 +747,14 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
arg = buf;
|
||||
}
|
||||
serverLog(LL_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)",
|
||||
j, arg, c->argv[j]->refcount);
|
||||
j, arg, static_cast<int>(c->argv[j]->getrefcount(std::memory_order_relaxed)));
|
||||
}
|
||||
}
|
||||
|
||||
void serverLogObjectDebugInfo(const robj *o) {
|
||||
void serverLogObjectDebugInfo(robj_roptr o) {
|
||||
serverLog(LL_WARNING,"Object type: %d", o->type);
|
||||
serverLog(LL_WARNING,"Object encoding: %d", o->encoding);
|
||||
serverLog(LL_WARNING,"Object refcount: %d", o->refcount);
|
||||
serverLog(LL_WARNING,"Object refcount: %d", static_cast<int>(o->getrefcount(std::memory_order_relaxed)));
|
||||
if (o->type == OBJ_STRING && sdsEncodedObject(o)) {
|
||||
serverLog(LL_WARNING,"Object raw string len: %zu", sdslen(szFromObj(o)));
|
||||
if (sdslen(szFromObj(o)) < 4096) {
|
||||
@@ -747,13 +775,13 @@ void serverLogObjectDebugInfo(const robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
void _serverAssertPrintObject(const robj *o) {
|
||||
void _serverAssertPrintObject(robj_roptr o) {
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED OBJECT CONTEXT ===");
|
||||
serverLogObjectDebugInfo(o);
|
||||
}
|
||||
|
||||
void _serverAssertWithInfo(const client *c, const robj *o, const char *estr, const char *file, int line) {
|
||||
void _serverAssertWithInfo(const client *c, robj_roptr o, const char *estr, const char *file, int line) {
|
||||
if (c) _serverAssertPrintClientInfo(c);
|
||||
if (o) _serverAssertPrintObject(o);
|
||||
_serverAssert(estr,file,line);
|
||||
@@ -778,10 +806,10 @@ void _serverPanic(const char *file, int line, const char *msg, ...) {
|
||||
}
|
||||
|
||||
void bugReportStart(void) {
|
||||
if (server.bug_report_start == 0) {
|
||||
if (g_pserver->bug_report_start == 0) {
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ===\n");
|
||||
server.bug_report_start = 1;
|
||||
"\n\n=== KEYDB BUG REPORT START: Cut & paste starting from here ===\n");
|
||||
g_pserver->bug_report_start = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1125,19 +1153,74 @@ void logRegisters(ucontext_t *uc) {
|
||||
*
|
||||
* Close it with closeDirectLogFiledes(). */
|
||||
int openDirectLogFiledes(void) {
|
||||
int log_to_stdout = server.logfile[0] == '\0';
|
||||
int log_to_stdout = g_pserver->logfile[0] == '\0';
|
||||
int fd = log_to_stdout ?
|
||||
STDOUT_FILENO :
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
open(g_pserver->logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Used to close what closeDirectLogFiledes() returns. */
|
||||
void closeDirectLogFiledes(int fd) {
|
||||
int log_to_stdout = server.logfile[0] == '\0';
|
||||
int log_to_stdout = g_pserver->logfile[0] == '\0';
|
||||
if (!log_to_stdout) close(fd);
|
||||
}
|
||||
|
||||
void safe_write(int fd, const void *pv, ssize_t cb)
|
||||
{
|
||||
ssize_t offset = 0;
|
||||
do
|
||||
{
|
||||
ssize_t cbWrite = write(fd, reinterpret_cast<const char*>(pv)+offset, cb-offset);
|
||||
if (cbWrite <= 0)
|
||||
return;
|
||||
offset += cbWrite;
|
||||
} while (offset < cb);
|
||||
}
|
||||
|
||||
void backtrace_symbols_demangle_fd(void **trace, size_t csym, int fd)
|
||||
{
|
||||
char **syms = backtrace_symbols(trace, csym);
|
||||
char symbuf[1024];
|
||||
for (size_t itrace = 0; itrace < csym; ++itrace)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
// First find the symbol (preceded by a '(')
|
||||
char *pchSymStart = syms[itrace];
|
||||
while (*pchSymStart != '(' && *pchSymStart != '\0')
|
||||
++pchSymStart;
|
||||
if (*pchSymStart != '\0')
|
||||
++pchSymStart; // skip the '('
|
||||
char *pchSymEnd = pchSymStart;
|
||||
while (*pchSymEnd != '+' && *pchSymEnd != '\0')
|
||||
++pchSymEnd;
|
||||
|
||||
if ((pchSymEnd - pchSymStart) < 1023)
|
||||
{
|
||||
memcpy(symbuf, pchSymStart, pchSymEnd - pchSymStart);
|
||||
symbuf[pchSymEnd - pchSymStart] = '\0';
|
||||
char *sz = abi::__cxa_demangle(symbuf, nullptr, nullptr, &status);
|
||||
if (status == 0)
|
||||
{
|
||||
safe_write(fd, syms[itrace], pchSymStart - syms[itrace]);
|
||||
safe_write(fd, sz, strlen(sz));
|
||||
safe_write(fd, pchSymEnd, (syms[itrace] + strlen(syms[itrace])-pchSymEnd));
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_write(fd, syms[itrace], strlen(syms[itrace]));
|
||||
}
|
||||
free(sz);
|
||||
}
|
||||
else {
|
||||
safe_write(fd, syms[itrace], strlen(syms[itrace]));
|
||||
}
|
||||
safe_write(fd, "\n", 1);
|
||||
}
|
||||
free(syms);
|
||||
}
|
||||
|
||||
/* Logs the stack trace using the backtrace() call. This function is designed
|
||||
* to be called from signal handlers safely. */
|
||||
void logStackTrace(ucontext_t *uc) {
|
||||
@@ -1154,12 +1237,12 @@ void logStackTrace(ucontext_t *uc) {
|
||||
const char *msg2 = "\nBacktrace:\n";
|
||||
if (write(fd,msg1,strlen(msg1)) == -1) {/* Avoid warning. */};
|
||||
trace[0] = getMcontextEip(uc);
|
||||
backtrace_symbols_fd(trace, 1, fd);
|
||||
backtrace_symbols_demangle_fd(trace, 1, fd);
|
||||
if (write(fd,msg2,strlen(msg2)) == -1) {/* Avoid warning. */};
|
||||
}
|
||||
|
||||
/* Write symbols to log file */
|
||||
backtrace_symbols_fd(trace+1, trace_size, fd);
|
||||
backtrace_symbols_demangle_fd(trace+1, trace_size, fd);
|
||||
|
||||
/* Cleanup */
|
||||
closeDirectLogFiledes(fd);
|
||||
@@ -1169,9 +1252,9 @@ void logStackTrace(ucontext_t *uc) {
|
||||
* currently being served by Redis. May be NULL if Redis is not serving a
|
||||
* client right now. */
|
||||
void logCurrentClient(void) {
|
||||
if (server.current_client == NULL) return;
|
||||
if (serverTL->current_client == NULL) return;
|
||||
|
||||
client *cc = server.current_client;
|
||||
client *cc = serverTL->current_client;
|
||||
sds client;
|
||||
int j;
|
||||
|
||||
@@ -1299,6 +1382,7 @@ void dumpX86Calls(void *addr, size_t len) {
|
||||
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
g_fInCrash = true;
|
||||
void *eip = getMcontextEip(uc);
|
||||
sds infostring, clients;
|
||||
struct sigaction act;
|
||||
@@ -1306,7 +1390,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,
|
||||
"Redis %s crashed by signal: %d", REDIS_VERSION, sig);
|
||||
"KeyDB %s crashed by signal: %d", KEYDB_REAL_VERSION, sig);
|
||||
if (eip != NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Crashed running the instruction at: %p", eip);
|
||||
@@ -1316,8 +1400,8 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
"Accessing address: %p", (void*)info->si_addr);
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Failed assertion: %s (%s:%d)", server.assert_failed,
|
||||
server.assert_file, server.assert_line);
|
||||
"Failed assertion: %s (%s:%d)", g_pserver->assert_failed,
|
||||
g_pserver->assert_file, g_pserver->assert_line);
|
||||
|
||||
/* Log the stack trace */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ STACK TRACE ------\n");
|
||||
@@ -1382,14 +1466,14 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
}
|
||||
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
"\n=== KEYDB BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash by opening an issue on github:\n\n"
|
||||
" http://github.com/antirez/redis/issues\n\n"
|
||||
" https://github.com/JohnSully/KeyDB/issues\n\n"
|
||||
" Suspect RAM error? Use keydb-server --test-memory to verify it.\n\n"
|
||||
);
|
||||
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
if (server.daemonize && server.supervised == 0) unlink(server.pidfile);
|
||||
if (cserver.daemonize && cserver.supervised == 0) unlink(cserver.pidfile);
|
||||
|
||||
/* Make sure we exit with the right signal at the end. So for instance
|
||||
* the core will be dumped if enabled. */
|
||||
@@ -1465,7 +1549,7 @@ void watchdogScheduleSignal(int period) {
|
||||
void enableWatchdog(int period) {
|
||||
int min_period;
|
||||
|
||||
if (server.watchdog_period == 0) {
|
||||
if (g_pserver->watchdog_period == 0) {
|
||||
struct sigaction act;
|
||||
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
@@ -1478,16 +1562,16 @@ void enableWatchdog(int period) {
|
||||
/* If the configured period is smaller than twice the timer period, it is
|
||||
* too short for the software watchdog to work reliably. Fix it now
|
||||
* if needed. */
|
||||
min_period = (1000/server.hz)*2;
|
||||
min_period = (1000/g_pserver->hz)*2;
|
||||
if (period < min_period) period = min_period;
|
||||
watchdogScheduleSignal(period); /* Adjust the current timer. */
|
||||
server.watchdog_period = period;
|
||||
g_pserver->watchdog_period = period;
|
||||
}
|
||||
|
||||
/* Disable the software watchdog. */
|
||||
void disableWatchdog(void) {
|
||||
struct sigaction act;
|
||||
if (server.watchdog_period == 0) return; /* Already disabled. */
|
||||
if (g_pserver->watchdog_period == 0) return; /* Already disabled. */
|
||||
watchdogScheduleSignal(0); /* Stop the current timer. */
|
||||
|
||||
/* Set the signal handler to SIG_IGN, this will also remove pending
|
||||
@@ -1496,5 +1580,5 @@ void disableWatchdog(void) {
|
||||
act.sa_flags = 0;
|
||||
act.sa_handler = SIG_IGN;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
server.watchdog_period = 0;
|
||||
g_pserver->watchdog_period = 0;
|
||||
}
|
||||
+90
-72
@@ -47,7 +47,8 @@ extern "C" 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, unsigned int hash, long *defragged);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@@ -59,14 +60,14 @@ void* activeDefragAlloc(void *ptr) {
|
||||
size_t size;
|
||||
void *newptr;
|
||||
if(!je_get_defrag_hint(ptr, &bin_util, &run_util)) {
|
||||
server.stat_active_defrag_misses++;
|
||||
g_pserver->stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* if this run is more utilized than the average utilization in this bin
|
||||
* (or it is full), skip it. This will eventually move all the allocations
|
||||
* from relatively empty runs into relatively full runs. */
|
||||
if (run_util > bin_util || run_util == 1<<16) {
|
||||
server.stat_active_defrag_misses++;
|
||||
g_pserver->stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* move this allocation to a new allocation.
|
||||
@@ -102,7 +103,7 @@ sds activeDefragSds(sds sdsptr) {
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob, long *defragged) {
|
||||
robj *ret = NULL;
|
||||
if (ob->refcount!=1)
|
||||
if (ob->getrefcount(std::memory_order_relaxed)!=1)
|
||||
return NULL;
|
||||
|
||||
/* try to defrag robj (only if not an EMBSTR type (handled below). */
|
||||
@@ -353,7 +354,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
sdsele = (sds)ln->value;
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
unsigned int hash = dictGetHash(d, sdsele);
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
@@ -390,7 +391,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, unsigned int hash, long *defragged) {
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
@@ -406,6 +407,20 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
auto itr = set.find(oldkey);
|
||||
if (itr != set.end())
|
||||
{
|
||||
expireEntry eNew(std::move(*itr));
|
||||
eNew.setKeyUnsafe(newkey);
|
||||
set.erase(itr);
|
||||
set.insert(eNew);
|
||||
serverAssert(set.find(newkey) != set.end());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
long defragged = 0;
|
||||
@@ -442,7 +457,7 @@ long scanLaterList(robj *ob) {
|
||||
quicklist *ql = (quicklist*)ptrFromObj(ob);
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
server.stat_active_defrag_scanned+=ql->len;
|
||||
g_pserver->stat_active_defrag_scanned+=ql->len;
|
||||
return activeDefragQuickListNodes(ql);
|
||||
}
|
||||
|
||||
@@ -455,7 +470,7 @@ void scanLaterZsetCallback(void *privdata, const dictEntry *_de) {
|
||||
dictEntry *de = (dictEntry*)_de;
|
||||
scanLaterZsetData *data = (scanLaterZsetData*)privdata;
|
||||
data->defragged += activeDefragZsetEntry(data->zs, de);
|
||||
server.stat_active_defrag_scanned++;
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
long scanLaterZset(robj *ob, unsigned long *cursor) {
|
||||
@@ -474,7 +489,7 @@ void scanLaterSetCallback(void *privdata, const dictEntry *_de) {
|
||||
sds sdsele = (sds)dictGetKey(de), newsds;
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
(*defragged)++, de->key = newsds;
|
||||
server.stat_active_defrag_scanned++;
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
long scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
@@ -495,7 +510,7 @@ void scanLaterHashCallback(void *privdata, const dictEntry *_de) {
|
||||
sdsele = (sds)dictGetVal(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
(*defragged)++, de->v.val = newsds;
|
||||
server.stat_active_defrag_scanned++;
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
long scanLaterHash(robj *ob, unsigned long *cursor) {
|
||||
@@ -514,7 +529,7 @@ long defragQuicklist(redisDb *db, dictEntry *kde) {
|
||||
serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
|
||||
if ((newql = (quicklist*)activeDefragAlloc(ql)))
|
||||
defragged++, ob->m_ptr = ql = newql;
|
||||
if (ql->len > server.active_defrag_max_scan_fields)
|
||||
if (ql->len > cserver.active_defrag_max_scan_fields)
|
||||
defragLater(db, kde);
|
||||
else
|
||||
defragged += activeDefragQuickListNodes(ql);
|
||||
@@ -537,7 +552,7 @@ long defragZsetSkiplist(redisDb *db, dictEntry *kde) {
|
||||
defragged++, zs->zsl = newzsl;
|
||||
if ((newheader = (zskiplistNode*)activeDefragAlloc(zs->zsl->header)))
|
||||
defragged++, zs->zsl->header = newheader;
|
||||
if (dictSize(zs->pdict) > server.active_defrag_max_scan_fields)
|
||||
if (dictSize(zs->pdict) > cserver.active_defrag_max_scan_fields)
|
||||
defragLater(db, kde);
|
||||
else {
|
||||
dictIterator *di = dictGetIterator(zs->pdict);
|
||||
@@ -560,7 +575,7 @@ long defragHash(redisDb *db, dictEntry *kde) {
|
||||
dict *d, *newd;
|
||||
serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT);
|
||||
d = (dict*)ptrFromObj(ob);
|
||||
if (dictSize(d) > server.active_defrag_max_scan_fields)
|
||||
if (dictSize(d) > cserver.active_defrag_max_scan_fields)
|
||||
defragLater(db, kde);
|
||||
else
|
||||
defragged += activeDefragSdsDict(d, DEFRAG_SDS_DICT_VAL_IS_SDS);
|
||||
@@ -578,7 +593,7 @@ long defragSet(redisDb *db, dictEntry *kde) {
|
||||
dict *d, *newd;
|
||||
serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT);
|
||||
d = (dict*)ptrFromObj(ob);
|
||||
if (dictSize(d) > server.active_defrag_max_scan_fields)
|
||||
if (dictSize(d) > cserver.active_defrag_max_scan_fields)
|
||||
defragLater(db, kde);
|
||||
else
|
||||
defragged += activeDefragSdsDict(d, DEFRAG_SDS_DICT_NO_VAL);
|
||||
@@ -742,7 +757,7 @@ long defragStream(redisDb *db, dictEntry *kde) {
|
||||
if ((news = (stream*)activeDefragAlloc(s)))
|
||||
defragged++, ob->m_ptr = s = news;
|
||||
|
||||
if (raxSize(s->prax) > server.active_defrag_max_scan_fields) {
|
||||
if (raxSize(s->prax) > cserver.active_defrag_max_scan_fields) {
|
||||
rax *newrax = (rax*)activeDefragAlloc(s->prax);
|
||||
if (newrax)
|
||||
defragged++, s->prax = newrax;
|
||||
@@ -765,20 +780,22 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
long defragged = 0;
|
||||
sds newsds;
|
||||
|
||||
ob = (robj*)dictGetVal(de);
|
||||
|
||||
/* Try to defrag the key name. */
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
{
|
||||
defragged++, de->key = newsds;
|
||||
if (dictSize(db->expires)) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
uint64_t hash = dictGetHash(db->pdict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
if (!db->setexpire->empty()) {
|
||||
bool fReplaced = replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
|
||||
serverAssert(fReplaced == ob->FExpires());
|
||||
} else {
|
||||
serverAssert(!ob->FExpires());
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = (robj*)dictGetVal(de);
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
@@ -831,18 +848,19 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for the main db dictionary. */
|
||||
void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
long defragged = defragKey((redisDb*)privdata, (dictEntry*)de);
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
g_pserver->stat_active_defrag_hits += defragged;
|
||||
if(defragged)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
g_pserver->stat_active_defrag_key_hits++;
|
||||
else
|
||||
server.stat_active_defrag_key_misses++;
|
||||
server.stat_active_defrag_scanned++;
|
||||
g_pserver->stat_active_defrag_key_misses++;
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
@@ -887,8 +905,8 @@ long defragOtherGlobals() {
|
||||
/* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc.
|
||||
* but we assume most of these are short lived, we only need to defrag allocations
|
||||
* that remain static for a long time */
|
||||
defragged += activeDefragSdsDict(server.lua_scripts, DEFRAG_SDS_DICT_VAL_IS_STROB);
|
||||
defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL);
|
||||
defragged += activeDefragSdsDict(g_pserver->lua_scripts, DEFRAG_SDS_DICT_VAL_IS_STROB);
|
||||
defragged += activeDefragSdsListAndDict(g_pserver->repl_scriptcache_fifo, g_pserver->repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL);
|
||||
return defragged;
|
||||
}
|
||||
|
||||
@@ -898,16 +916,16 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = (robj*)dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
g_pserver->stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
g_pserver->stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
server.stat_active_defrag_hits += scanLaterZset(ob, cursor);
|
||||
g_pserver->stat_active_defrag_hits += scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
server.stat_active_defrag_hits += scanLaterHash(ob, cursor);
|
||||
g_pserver->stat_active_defrag_hits += scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStraemListpacks(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
return scanLaterStraemListpacks(ob, cursor, endtime, &g_pserver->stat_active_defrag_hits);
|
||||
} else {
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
}
|
||||
@@ -922,8 +940,8 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
static sds current_key = NULL;
|
||||
static unsigned long cursor = 0;
|
||||
unsigned int iterations = 0;
|
||||
unsigned long long prev_defragged = server.stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||
unsigned long long prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
long long key_defragged;
|
||||
|
||||
do {
|
||||
@@ -952,7 +970,7 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
|
||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||
dictEntry *de = dictFind(db->pdict, current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
key_defragged = g_pserver->stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &cursor, endtime))
|
||||
@@ -967,24 +985,24 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
* (if we have a lot of pointers in one hash bucket, or rehashing),
|
||||
* check if we reached the time limit. */
|
||||
if (quit || (++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
g_pserver->stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
g_pserver->stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
if (quit || ustime() > endtime) {
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
if(key_defragged != g_pserver->stat_active_defrag_hits)
|
||||
g_pserver->stat_active_defrag_key_hits++;
|
||||
else
|
||||
server.stat_active_defrag_key_misses++;
|
||||
g_pserver->stat_active_defrag_key_misses++;
|
||||
return 1;
|
||||
}
|
||||
iterations = 0;
|
||||
prev_defragged = server.stat_active_defrag_hits;
|
||||
prev_scanned = server.stat_active_defrag_scanned;
|
||||
prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
}
|
||||
} while(cursor);
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
if(key_defragged != g_pserver->stat_active_defrag_hits)
|
||||
g_pserver->stat_active_defrag_key_hits++;
|
||||
else
|
||||
server.stat_active_defrag_key_misses++;
|
||||
g_pserver->stat_active_defrag_key_misses++;
|
||||
} while(1);
|
||||
}
|
||||
|
||||
@@ -996,26 +1014,26 @@ void computeDefragCycles() {
|
||||
size_t frag_bytes;
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
/* If we're not already running, and below the threshold, exit. */
|
||||
if (!server.active_defrag_running) {
|
||||
if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
|
||||
if (!g_pserver->active_defrag_running) {
|
||||
if(frag_pct < cserver.active_defrag_threshold_lower || frag_bytes < cserver.active_defrag_ignore_bytes)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate the adaptive aggressiveness of the defrag */
|
||||
int cpu_pct = INTERPOLATE(frag_pct,
|
||||
server.active_defrag_threshold_lower,
|
||||
server.active_defrag_threshold_upper,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
cserver.active_defrag_threshold_lower,
|
||||
cserver.active_defrag_threshold_upper,
|
||||
cserver.active_defrag_cycle_min,
|
||||
cserver.active_defrag_cycle_max);
|
||||
cpu_pct = LIMIT(cpu_pct,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
cserver.active_defrag_cycle_min,
|
||||
cserver.active_defrag_cycle_max);
|
||||
/* We allow increasing the aggressiveness during a scan, but don't
|
||||
* reduce it. */
|
||||
if (!server.active_defrag_running ||
|
||||
cpu_pct > server.active_defrag_running)
|
||||
if (!g_pserver->active_defrag_running ||
|
||||
cpu_pct > g_pserver->active_defrag_running)
|
||||
{
|
||||
server.active_defrag_running = cpu_pct;
|
||||
g_pserver->active_defrag_running = cpu_pct;
|
||||
serverLog(LL_VERBOSE,
|
||||
"Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
|
||||
frag_pct, frag_bytes, cpu_pct);
|
||||
@@ -1031,13 +1049,13 @@ void activeDefragCycle(void) {
|
||||
static redisDb *db = NULL;
|
||||
static long long start_scan, start_stat;
|
||||
unsigned int iterations = 0;
|
||||
unsigned long long prev_defragged = server.stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||
unsigned long long prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
long long start, timelimit, endtime;
|
||||
mstime_t latency;
|
||||
int quit = 0;
|
||||
|
||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->rdb_child_pid!=-1)
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
@@ -1045,12 +1063,12 @@ void activeDefragCycle(void) {
|
||||
run_with_period(1000) {
|
||||
computeDefragCycles();
|
||||
}
|
||||
if (!server.active_defrag_running)
|
||||
if (!g_pserver->active_defrag_running)
|
||||
return;
|
||||
|
||||
/* See activeExpireCycle for how timelimit is handled. */
|
||||
start = ustime();
|
||||
timelimit = 1000000*server.active_defrag_running/server.hz/100;
|
||||
timelimit = 1000000*g_pserver->active_defrag_running/g_pserver->hz/100;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
endtime = start + timelimit;
|
||||
latencyStartMonitor(latency);
|
||||
@@ -1065,7 +1083,7 @@ void activeDefragCycle(void) {
|
||||
}
|
||||
|
||||
/* Move on to next database, and stop if we reached the last one. */
|
||||
if (++current_db >= server.dbnum) {
|
||||
if (++current_db >= cserver.dbnum) {
|
||||
/* defrag other items not part of the db / keys */
|
||||
defragOtherGlobals();
|
||||
|
||||
@@ -1074,26 +1092,26 @@ void activeDefragCycle(void) {
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
serverLog(LL_VERBOSE,
|
||||
"Active defrag done in %dms, reallocated=%d, frag=%.0f%%, frag_bytes=%zu",
|
||||
(int)((now - start_scan)/1000), (int)(server.stat_active_defrag_hits - start_stat), frag_pct, frag_bytes);
|
||||
(int)((now - start_scan)/1000), (int)(g_pserver->stat_active_defrag_hits - start_stat), frag_pct, frag_bytes);
|
||||
|
||||
start_scan = now;
|
||||
current_db = -1;
|
||||
cursor = 0;
|
||||
db = NULL;
|
||||
server.active_defrag_running = 0;
|
||||
g_pserver->active_defrag_running = 0;
|
||||
|
||||
computeDefragCycles(); /* if another scan is needed, start it right away */
|
||||
if (server.active_defrag_running != 0 && ustime() < endtime)
|
||||
if (g_pserver->active_defrag_running != 0 && ustime() < endtime)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
else if (current_db==0) {
|
||||
/* Start a scan from the first database. */
|
||||
start_scan = ustime();
|
||||
start_stat = server.stat_active_defrag_hits;
|
||||
start_stat = g_pserver->stat_active_defrag_hits;
|
||||
}
|
||||
|
||||
db = &server.db[current_db];
|
||||
db = &g_pserver->db[current_db];
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
@@ -1112,15 +1130,15 @@ void activeDefragCycle(void) {
|
||||
* But regardless, don't start a new db in this loop, this is because after
|
||||
* the last db we call defragOtherGlobals, which must be done in once cycle */
|
||||
if (!cursor || (++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
g_pserver->stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
g_pserver->stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
if (!cursor || ustime() > endtime) {
|
||||
quit = 1;
|
||||
break;
|
||||
}
|
||||
iterations = 0;
|
||||
prev_defragged = server.stat_active_defrag_hits;
|
||||
prev_scanned = server.stat_active_defrag_scanned;
|
||||
prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
}
|
||||
} while(cursor && !quit);
|
||||
} while(!quit);
|
||||
|
||||
+177
-150
@@ -77,8 +77,8 @@ unsigned int getLRUClock(void) {
|
||||
* precomputed value, otherwise we need to resort to a system call. */
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
if (1000/g_pserver->hz <= LRU_CLOCK_RESOLUTION) {
|
||||
lruclock = g_pserver->lruclock;
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
@@ -111,7 +111,7 @@ unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
* If all the bytes needed to return back under the limit were freed the
|
||||
* function returns C_OK, otherwise C_ERR is returned, and the caller
|
||||
* should block the execution of commands that will result in more memory
|
||||
* used by the server.
|
||||
* used by the g_pserver->
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
@@ -150,6 +150,84 @@ void evictionPoolAlloc(void) {
|
||||
EvictionPoolLRU = ep;
|
||||
}
|
||||
|
||||
void processEvictionCandidate(int dbid, sds key, robj *o, const expireEntry *e, struct evictionPoolEntry *pool)
|
||||
{
|
||||
unsigned long long idle;
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = (o != nullptr) ? estimateObjectIdleTime(o) : 0;
|
||||
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
/* When we use an LRU policy, we sort the keys by idle time
|
||||
* so that we expire keys starting from greater idle time.
|
||||
* However when the policy is an LFU one, we have a frequency
|
||||
* estimation, and we want to evict keys with lower frequency
|
||||
* first. So inside the pool we put objects using the inverted
|
||||
* frequency subtracting the actual frequency to the maximum
|
||||
* frequency of 255. */
|
||||
idle = 255-LFUDecrAndReturn(o);
|
||||
} else if (g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
|
||||
/* In this case the sooner the expire the better. */
|
||||
idle = ULLONG_MAX - e->when();
|
||||
} else {
|
||||
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
|
||||
}
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
int k = 0;
|
||||
while (k < EVPOOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
return;
|
||||
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[EVPOOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
|
||||
/* Save SDS before overwriting. */
|
||||
sds cached = pool[EVPOOL_SIZE-1].cached;
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
|
||||
pool[k].cached = cached;
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sds cached = pool[0].cached; /* Save SDS before overwriting. */
|
||||
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
pool[k].cached = cached;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
} else {
|
||||
memcpy(pool[k].cached,key,klen+1);
|
||||
sdssetlen(pool[k].cached,klen);
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
|
||||
/* This is an helper function for freeMemoryIfNeeded(), it is used in order
|
||||
* to populate the evictionPool with a few entries every time we want to
|
||||
* expire a key. Keys with idle time smaller than one of the current
|
||||
@@ -159,100 +237,36 @@ void evictionPoolAlloc(void) {
|
||||
* idle time are on the left, and keys with the higher idle time on the
|
||||
* right. */
|
||||
|
||||
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
|
||||
int j, k, count;
|
||||
dictEntry **samples = (dictEntry**)alloca(server.maxmemory_samples * sizeof(dictEntry*));
|
||||
struct visitFunctor
|
||||
{
|
||||
int dbid;
|
||||
dict *dbdict;
|
||||
struct evictionPoolEntry *pool;
|
||||
int count;
|
||||
|
||||
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
|
||||
for (j = 0; j < count; j++) {
|
||||
unsigned long long idle;
|
||||
sds key;
|
||||
robj *o;
|
||||
dictEntry *de;
|
||||
|
||||
de = samples[j];
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
/* If the dictionary we are sampling from is not the main
|
||||
* dictionary (but the expires one) we need to lookup the key
|
||||
* again in the key dictionary to obtain the value object. */
|
||||
if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
|
||||
if (sampledict != keydict) de = dictFind(keydict, key);
|
||||
o = (robj*)dictGetVal(de);
|
||||
bool operator()(const expireEntry &e)
|
||||
{
|
||||
dictEntry *de = dictFind(dbdict, e.key());
|
||||
processEvictionCandidate(dbid, (sds)dictGetKey(de), (robj*)dictGetVal(de), &e, pool);
|
||||
++count;
|
||||
return count < g_pserver->maxmemory_samples;
|
||||
}
|
||||
};
|
||||
void evictionPoolPopulate(int dbid, dict *dbdict, expireset *setexpire, struct evictionPoolEntry *pool)
|
||||
{
|
||||
if (setexpire != nullptr)
|
||||
{
|
||||
visitFunctor visitor { dbid, dbdict, pool, 0 };
|
||||
setexpire->random_visit(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
|
||||
int count = dictGetSomeKeys(dbdict,samples,g_pserver->maxmemory_samples);
|
||||
for (int j = 0; j < count; j++) {
|
||||
robj *o = (robj*)dictGetVal(samples[j]);
|
||||
processEvictionCandidate(dbid, (sds)dictGetKey(samples[j]), o, nullptr, pool);
|
||||
}
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = estimateObjectIdleTime(o);
|
||||
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
/* When we use an LRU policy, we sort the keys by idle time
|
||||
* so that we expire keys starting from greater idle time.
|
||||
* However when the policy is an LFU one, we have a frequency
|
||||
* estimation, and we want to evict keys with lower frequency
|
||||
* first. So inside the pool we put objects using the inverted
|
||||
* frequency subtracting the actual frequency to the maximum
|
||||
* frequency of 255. */
|
||||
idle = 255-LFUDecrAndReturn(o);
|
||||
} else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
|
||||
/* In this case the sooner the expire the better. */
|
||||
idle = ULLONG_MAX - (long)dictGetVal(de);
|
||||
} else {
|
||||
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
|
||||
}
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
k = 0;
|
||||
while (k < EVPOOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
continue;
|
||||
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[EVPOOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
|
||||
/* Save SDS before overwriting. */
|
||||
sds cached = pool[EVPOOL_SIZE-1].cached;
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
|
||||
pool[k].cached = cached;
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sds cached = pool[0].cached; /* Save SDS before overwriting. */
|
||||
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
pool[k].cached = cached;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
} else {
|
||||
memcpy(pool[k].cached,key,klen+1);
|
||||
sdssetlen(pool[k].cached,klen);
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +311,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
|
||||
* 16 bits. The returned time is suitable to be stored as LDT (last decrement
|
||||
* time) for the LFU implementation. */
|
||||
unsigned long LFUGetTimeInMinutes(void) {
|
||||
return (server.unixtime/60) & 65535;
|
||||
return (g_pserver->unixtime/60) & 65535;
|
||||
}
|
||||
|
||||
/* Given an object last access time, compute the minimum number of minutes
|
||||
@@ -317,7 +331,7 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
double r = (double)rand()/RAND_MAX;
|
||||
double baseval = counter - LFU_INIT_VAL;
|
||||
if (baseval < 0) baseval = 0;
|
||||
double p = 1.0/(baseval*server.lfu_log_factor+1);
|
||||
double p = 1.0/(baseval*g_pserver->lfu_log_factor+1);
|
||||
if (r < p) counter++;
|
||||
return counter;
|
||||
}
|
||||
@@ -326,7 +340,7 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
* do not update LFU fields of the object, we update the access time
|
||||
* and counter in an explicit way when the object is really accessed.
|
||||
* And we will times halve the counter according to the times of
|
||||
* elapsed time than server.lfu_decay_time.
|
||||
* elapsed time than g_pserver->lfu_decay_time.
|
||||
* Return the object frequency counter.
|
||||
*
|
||||
* This function is used in order to scan the dataset for the best object
|
||||
@@ -335,7 +349,7 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
unsigned long LFUDecrAndReturn(robj *o) {
|
||||
unsigned long ldt = o->lru >> 8;
|
||||
unsigned long counter = o->lru & 255;
|
||||
unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;
|
||||
unsigned long num_periods = g_pserver->lfu_decay_time ? LFUTimeElapsed(ldt) / g_pserver->lfu_decay_time : 0;
|
||||
if (num_periods)
|
||||
counter = (num_periods > counter) ? 0 : counter - num_periods;
|
||||
return counter;
|
||||
@@ -352,20 +366,20 @@ unsigned long LFUDecrAndReturn(robj *o) {
|
||||
size_t freeMemoryGetNotCountedMemory(void) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
size_t overhead = 0;
|
||||
int slaves = listLength(server.slaves);
|
||||
int slaves = listLength(g_pserver->slaves);
|
||||
|
||||
if (slaves) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(slave);
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(replica);
|
||||
}
|
||||
}
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
|
||||
if (g_pserver->aof_state != AOF_OFF) {
|
||||
overhead += sdsalloc(g_pserver->aof_buf)+aofRewriteBufferSize();
|
||||
}
|
||||
return overhead;
|
||||
}
|
||||
@@ -403,7 +417,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
if (total) *total = mem_reported;
|
||||
|
||||
/* We may return ASAP if there is no need to compute the level. */
|
||||
int return_ok_asap = !server.maxmemory || mem_reported <= server.maxmemory;
|
||||
int return_ok_asap = !g_pserver->maxmemory || mem_reported <= g_pserver->maxmemory;
|
||||
if (return_ok_asap && !level) return C_OK;
|
||||
|
||||
/* Remove the size of slaves output buffers and AOF buffer from the
|
||||
@@ -414,20 +428,20 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
|
||||
/* Compute the ratio of memory usage. */
|
||||
if (level) {
|
||||
if (!server.maxmemory) {
|
||||
if (!g_pserver->maxmemory) {
|
||||
*level = 0;
|
||||
} else {
|
||||
*level = (float)mem_used / (float)server.maxmemory;
|
||||
*level = (float)mem_used / (float)g_pserver->maxmemory;
|
||||
}
|
||||
}
|
||||
|
||||
if (return_ok_asap) return C_OK;
|
||||
|
||||
/* Check if we are still over the memory limit. */
|
||||
if (mem_used <= server.maxmemory) return C_OK;
|
||||
if (mem_used <= g_pserver->maxmemory) return C_OK;
|
||||
|
||||
/* Compute how much memory we need to free. */
|
||||
mem_tofree = mem_used - server.maxmemory;
|
||||
mem_tofree = mem_used - g_pserver->maxmemory;
|
||||
|
||||
if (logical) *logical = mem_used;
|
||||
if (tofree) *tofree = mem_tofree;
|
||||
@@ -448,12 +462,12 @@ int freeMemoryIfNeeded(void) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (listLength(server.masters) && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
int slaves = listLength(server.slaves);
|
||||
int slaves = listLength(g_pserver->slaves);
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
@@ -464,7 +478,7 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
mem_freed = 0;
|
||||
|
||||
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
|
||||
if (g_pserver->maxmemory_policy == MAXMEMORY_NO_EVICTION)
|
||||
goto cant_free; /* We need to free memory, but policy forbids. */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
@@ -474,11 +488,9 @@ int freeMemoryIfNeeded(void) {
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
redisDb *db;
|
||||
dict *dict;
|
||||
dictEntry *de;
|
||||
|
||||
if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
|
||||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
|
||||
if (g_pserver->maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
|
||||
g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
|
||||
{
|
||||
struct evictionPoolEntry *pool = EvictionPoolLRU;
|
||||
|
||||
@@ -488,12 +500,20 @@ int freeMemoryIfNeeded(void) {
|
||||
/* We don't want to make local-db choices when expiring keys,
|
||||
* so to start populate the eviction pool sampling keys from
|
||||
* every DB. */
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
db = server.db+i;
|
||||
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
|
||||
db->pdict : db->expires;
|
||||
if ((keys = dictSize(dict)) != 0) {
|
||||
evictionPoolPopulate(i, dict, db->pdict, pool);
|
||||
for (i = 0; i < cserver.dbnum; i++) {
|
||||
db = g_pserver->db+i;
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS)
|
||||
{
|
||||
if ((keys = dictSize(db->pdict)) != 0) {
|
||||
evictionPoolPopulate(i, db->pdict, nullptr, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
keys = db->setexpire->size();
|
||||
if (keys != 0)
|
||||
evictionPoolPopulate(i, db->pdict, db->setexpire, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
@@ -503,14 +523,11 @@ int freeMemoryIfNeeded(void) {
|
||||
for (k = EVPOOL_SIZE-1; k >= 0; k--) {
|
||||
if (pool[k].key == NULL) continue;
|
||||
bestdbid = pool[k].dbid;
|
||||
sds key = nullptr;
|
||||
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
|
||||
de = dictFind(server.db[pool[k].dbid].pdict,
|
||||
pool[k].key);
|
||||
} else {
|
||||
de = dictFind(server.db[pool[k].dbid].expires,
|
||||
pool[k].key);
|
||||
}
|
||||
dictEntry *de = dictFind(g_pserver->db[pool[k].dbid].pdict,pool[k].key);
|
||||
if (de != nullptr && (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS || ((robj*)dictGetVal(de))->FExpires()))
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
/* Remove the entry from the pool. */
|
||||
if (pool[k].key != pool[k].cached)
|
||||
@@ -520,8 +537,8 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
/* If the key exists, is our pick. Otherwise it is
|
||||
* a ghost and we need to try the next element. */
|
||||
if (de) {
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
if (key) {
|
||||
bestkey = key;
|
||||
break;
|
||||
} else {
|
||||
/* Ghost... Iterate again. */
|
||||
@@ -531,31 +548,41 @@ int freeMemoryIfNeeded(void) {
|
||||
}
|
||||
|
||||
/* volatile-random and allkeys-random policy */
|
||||
else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
|
||||
server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
|
||||
else if (g_pserver->maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
|
||||
g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
|
||||
{
|
||||
/* When evicting a random key, we try to evict a key for
|
||||
* each DB, so we use the static 'next_db' variable to
|
||||
* incrementally visit all DBs. */
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
j = (++next_db) % server.dbnum;
|
||||
db = server.db+j;
|
||||
dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
|
||||
db->pdict : db->expires;
|
||||
if (dictSize(dict) != 0) {
|
||||
de = dictGetRandomKey(dict);
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
for (i = 0; i < cserver.dbnum; i++) {
|
||||
j = (++next_db) % cserver.dbnum;
|
||||
db = g_pserver->db+j;
|
||||
if (g_pserver->maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM)
|
||||
{
|
||||
if (dictSize(db->pdict) != 0) {
|
||||
dictEntry *de = dictGetRandomKey(db->pdict);
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!db->setexpire->empty())
|
||||
{
|
||||
bestkey = (sds)db->setexpire->random_value().key();
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
db = server.db+bestdbid;
|
||||
db = g_pserver->db+bestdbid;
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_eviction);
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
@@ -566,7 +593,7 @@ int freeMemoryIfNeeded(void) {
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
if (server.lazyfree_lazy_eviction)
|
||||
if (g_pserver->lazyfree_lazy_eviction)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
@@ -575,7 +602,7 @@ int freeMemoryIfNeeded(void) {
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
server.stat_evictedkeys++;
|
||||
g_pserver->stat_evictedkeys++;
|
||||
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
|
||||
keyobj, db->id);
|
||||
decrRefCount(keyobj);
|
||||
@@ -594,7 +621,7 @@ int freeMemoryIfNeeded(void) {
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
if (g_pserver->lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
/* Let's satisfy our stop condition. */
|
||||
mem_freed = mem_tofree;
|
||||
@@ -632,6 +659,6 @@ cant_free:
|
||||
*
|
||||
*/
|
||||
int freeMemoryIfNeededAndSafe(void) {
|
||||
if (server.lua_timedout || server.loading) return C_OK;
|
||||
if (g_pserver->lua_timedout || g_pserver->loading) return C_OK;
|
||||
return freeMemoryIfNeeded();
|
||||
}
|
||||
+314
-120
@@ -31,6 +31,22 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_expire);
|
||||
if (g_pserver->lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
if (g_pserver->tracking_clients) trackingInvalidateKey(keyobj);
|
||||
decrRefCount(keyobj);
|
||||
g_pserver->stat_expiredkeys++;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Incremental collection of expired keys.
|
||||
@@ -47,31 +63,200 @@
|
||||
* If the key is found to be expired, it is removed from the database and
|
||||
* 1 is returned. Otherwise no operation is performed and 0 is returned.
|
||||
*
|
||||
* When a key is expired, server.stat_expiredkeys is incremented.
|
||||
* When a key is expired, g_pserver->stat_expiredkeys is incremented.
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
if (now > t) {
|
||||
sds key = (sds)dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
if (!e.FFat())
|
||||
{
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
|
||||
if (server.lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
expireEntryFat *pfat = e.pfatentry();
|
||||
dictEntry *de = dictFind(db->pdict, e.key());
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
int deleted = 0;
|
||||
while (!pfat->FEmpty())
|
||||
{
|
||||
if (pfat->nextExpireEntry().when > now)
|
||||
break;
|
||||
|
||||
// Is it the full key expiration?
|
||||
if (pfat->nextExpireEntry().spsubkey == nullptr)
|
||||
{
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
if (setTypeRemove(val,pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (setTypeSize(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_HASH:
|
||||
if (hashTypeDelete(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (hashTypeLength(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_ZSET:
|
||||
if (zsetDel(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (zsetLength(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_CRON:
|
||||
executeCronJobExpireHook(e.key(), val);
|
||||
return;
|
||||
|
||||
case OBJ_LIST:
|
||||
default:
|
||||
serverAssert(false);
|
||||
}
|
||||
pfat->popfrontExpireEntry();
|
||||
}
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
if (!pfat->FEmpty())
|
||||
{
|
||||
// We need to resort the expire entry since it may no longer be in the correct position
|
||||
auto itr = db->setexpire->find(e.key());
|
||||
expireEntry eT = std::move(e);
|
||||
db->setexpire->erase(itr);
|
||||
db->setexpire->insert(eT);
|
||||
}
|
||||
|
||||
robj objT;
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
initStaticStringObject(objT, (char*)e.key());
|
||||
signalModifiedKey(db,&objT);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objT,db->id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfat->FEmpty())
|
||||
{
|
||||
robj *keyobj = createStringObject(e.key(),sdslen(e.key()));
|
||||
removeExpire(db, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int parseUnitString(const char *sz)
|
||||
{
|
||||
if (strcasecmp(sz, "s") == 0)
|
||||
return UNIT_SECONDS;
|
||||
if (strcasecmp(sz, "ms") == 0)
|
||||
return UNIT_MILLISECONDS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, long long when, int unit)
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case UNIT_SECONDS:
|
||||
when *= 1000;
|
||||
case UNIT_MILLISECONDS:
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "Invalid unit arg");
|
||||
return;
|
||||
}
|
||||
|
||||
when += basetime;
|
||||
|
||||
/* No key, return zero. */
|
||||
robj *val = lookupKeyWriteOrReply(c, key, shared.czero);
|
||||
if (val == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
double dblT;
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
if (!setTypeIsMember(val, szFromObj(subkey))) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_HASH:
|
||||
if (!hashTypeExists(val, szFromObj(subkey))) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_ZSET:
|
||||
if (zsetScore(val, szFromObj(subkey), &dblT) == C_ERR) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "object type is unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
setExpire(c, c->db, key, subkey, when);
|
||||
|
||||
addReply(c, shared.cone);
|
||||
}
|
||||
|
||||
void expireMemberCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 5) {
|
||||
addReplyError(c, "Invalid number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int unit = UNIT_SECONDS;
|
||||
if (c->argc == 5) {
|
||||
unit = parseUnitString(szFromObj(c->argv[4]));
|
||||
}
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], mstime(), when, unit);
|
||||
}
|
||||
|
||||
void expireMemberAtCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
|
||||
}
|
||||
|
||||
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
* will use few CPU cycles if there are few expiring keys, otherwise
|
||||
* it will get more aggressive to avoid that too much memory is used by
|
||||
@@ -126,14 +311,14 @@ void activeExpireCycle(int type) {
|
||||
* 2) If last time we hit the time limit, we want to scan all DBs
|
||||
* in this iteration, as there is work to do in some DB and we don't want
|
||||
* expired keys to use memory for too much time. */
|
||||
if (dbs_per_call > server.dbnum || timelimit_exit)
|
||||
dbs_per_call = server.dbnum;
|
||||
if (dbs_per_call > cserver.dbnum || timelimit_exit)
|
||||
dbs_per_call = cserver.dbnum;
|
||||
|
||||
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
|
||||
* per iteration. Since this function gets called with a frequency of
|
||||
* server.hz times per second, the following is the max amount of
|
||||
* g_pserver->hz times per second, the following is the max amount of
|
||||
* microseconds we can spend in this function. */
|
||||
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
|
||||
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/g_pserver->hz/100;
|
||||
timelimit_exit = 0;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
@@ -147,86 +332,53 @@ void activeExpireCycle(int type) {
|
||||
long total_expired = 0;
|
||||
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
int expired;
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
redisDb *db = g_pserver->db+(current_db % cserver.dbnum);
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
* in the current DB we'll restart from the next. This allows to
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
do {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
int ttl_samples;
|
||||
iteration++;
|
||||
long long now;
|
||||
iteration++;
|
||||
now = mstime();
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = dictSize(db->expires)) == 0) {
|
||||
db->avg_ttl = 0;
|
||||
break;
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if (db->setexpire->empty())
|
||||
{
|
||||
db->avg_ttl = 0;
|
||||
db->last_expire_set = now;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t expired = 0;
|
||||
size_t tried = 0;
|
||||
long long check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; // assume a check is roughly 1us. It isn't but good enough
|
||||
db->expireitr = db->setexpire->enumerate(db->expireitr, now, [&](expireEntry &e) __attribute__((always_inline)) {
|
||||
if (e.when() < now)
|
||||
{
|
||||
activeExpireCycleExpire(db, e, now);
|
||||
++expired;
|
||||
}
|
||||
slots = dictSlots(db->expires);
|
||||
now = mstime();
|
||||
++tried;
|
||||
|
||||
/* When there are less than 1% filled slots getting random
|
||||
* keys is expensive, so stop here waiting for better times...
|
||||
* The dictionary will be resized asap. */
|
||||
if (num && slots > DICT_HT_INITIAL_SIZE &&
|
||||
(num*100/slots < 1)) break;
|
||||
|
||||
/* The main collection cycle. Sample random keys among keys
|
||||
* with an expire set, checking for expired ones. */
|
||||
expired = 0;
|
||||
ttl_sum = 0;
|
||||
ttl_samples = 0;
|
||||
|
||||
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
|
||||
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
|
||||
|
||||
while (num--) {
|
||||
dictEntry *de;
|
||||
long long ttl;
|
||||
|
||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
||||
ttl = dictGetSignedIntegerVal(de)-now;
|
||||
if (activeExpireCycleTryExpire(db,de,now)) expired++;
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet not expired. */
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
}
|
||||
total_sampled++;
|
||||
}
|
||||
total_expired += expired;
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
long long avg_ttl = ttl_sum/ttl_samples;
|
||||
|
||||
/* Do a simple running average with a few samples.
|
||||
* We just use the current estimate with a weight of 2%
|
||||
* and the previous estimate with a weight of 98%. */
|
||||
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
|
||||
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
|
||||
}
|
||||
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
if ((tried % ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) == 0)
|
||||
{
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
server.stat_expired_time_cap_reached_count++;
|
||||
break;
|
||||
g_pserver->stat_expired_time_cap_reached_count++;
|
||||
return false;
|
||||
}
|
||||
check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION;
|
||||
}
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
* found expired in the current DB. */
|
||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
||||
return true;
|
||||
}, &check);
|
||||
|
||||
total_expired += expired;
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
@@ -239,8 +391,8 @@ void activeExpireCycle(int type) {
|
||||
current_perc = (double)total_expired/total_sampled;
|
||||
} else
|
||||
current_perc = 0;
|
||||
server.stat_expired_stale_perc = (current_perc*0.05)+
|
||||
(server.stat_expired_stale_perc*0.95);
|
||||
g_pserver->stat_expired_stale_perc = (current_perc*0.05)+
|
||||
(g_pserver->stat_expired_stale_perc*0.95);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -248,17 +400,17 @@ void activeExpireCycle(int type) {
|
||||
*
|
||||
* Normally slaves do not process expires: they wait the masters to synthesize
|
||||
* DEL operations in order to retain consistency. However writable slaves are
|
||||
* an exception: if a key is created in the slave and an expire is assigned
|
||||
* an exception: if a key is created in the replica and an expire is assigned
|
||||
* to it, we need a way to expire such a key, since the master does not know
|
||||
* anything about such a key.
|
||||
*
|
||||
* In order to do so, we track keys created in the slave side with an expire
|
||||
* In order to do so, we track keys created in the replica side with an expire
|
||||
* set, and call the expireSlaveKeys() function from time to time in order to
|
||||
* reclaim the keys if they already expired.
|
||||
*
|
||||
* Note that the use case we are trying to cover here, is a popular one where
|
||||
* slaves are put in writable mode in order to compute slow operations in
|
||||
* the slave side that are mostly useful to actually read data in a more
|
||||
* the replica side that are mostly useful to actually read data in a more
|
||||
* processed way. Think at sets intersections in a tmp key, with an expire so
|
||||
* that it is also used as a cache to avoid intersecting every time.
|
||||
*
|
||||
@@ -267,7 +419,7 @@ void activeExpireCycle(int type) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* The dictionary where we remember key names and database ID of keys we may
|
||||
* want to expire from the slave. Since this function is not often used we
|
||||
* want to expire from the replica. Since this function is not often used we
|
||||
* don't even care to initialize the database at startup. We'll do it once
|
||||
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
|
||||
* is called.
|
||||
@@ -297,23 +449,30 @@ void expireSlaveKeys(void) {
|
||||
/* Check the key against every database corresponding to the
|
||||
* bits set in the value bitmap. */
|
||||
int dbid = 0;
|
||||
while(dbids && dbid < server.dbnum) {
|
||||
while(dbids && dbid < cserver.dbnum) {
|
||||
if ((dbids & 1) != 0) {
|
||||
redisDb *db = server.db+dbid;
|
||||
dictEntry *expire = dictFind(db->expires,keyname);
|
||||
redisDb *db = g_pserver->db+dbid;
|
||||
|
||||
// the expire is hashed based on the key pointer, so we need the point in the main db
|
||||
dictEntry *deMain = dictFind(db->pdict, keyname);
|
||||
auto itr = db->setexpire->end();
|
||||
if (deMain != nullptr)
|
||||
itr = db->setexpire->find((sds)dictGetKey(deMain));
|
||||
int expired = 0;
|
||||
|
||||
if (expire &&
|
||||
activeExpireCycleTryExpire(server.db+dbid,expire,start))
|
||||
if (itr != db->setexpire->end())
|
||||
{
|
||||
expired = 1;
|
||||
if (itr->when() < start) {
|
||||
activeExpireCycleExpire(g_pserver->db+dbid,*itr,start);
|
||||
expired = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the key was not expired in this DB, we need to set the
|
||||
* corresponding bit in the new bitmap we set as value.
|
||||
* At the end of the loop if the bitmap is zero, it means we
|
||||
* no longer need to keep track of this key. */
|
||||
if (expire && !expired) {
|
||||
if (itr != db->setexpire->end() && !expired) {
|
||||
noexpire++;
|
||||
new_dbids |= (uint64_t)1 << dbid;
|
||||
}
|
||||
@@ -323,7 +482,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Set the new bitmap as value of the key, in the dictionary
|
||||
* of keys with an expire set directly in the writable slave. Otherwise
|
||||
* of keys with an expire set directly in the writable replica. Otherwise
|
||||
* if the bitmap is zero, we no longer need to keep track of it. */
|
||||
if (new_dbids)
|
||||
dictSetUnsignedIntegerVal(de,new_dbids);
|
||||
@@ -340,7 +499,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Track keys that received an EXPIRE or similar command in the context
|
||||
* of a writable slave. */
|
||||
* of a writable replica. */
|
||||
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
|
||||
if (slaveKeysWithExpire == NULL) {
|
||||
static dictType dt = {
|
||||
@@ -377,12 +536,12 @@ size_t getSlaveKeyWithExpireCount(void) {
|
||||
}
|
||||
|
||||
/* Remove the keys in the hash table. We need to do that when data is
|
||||
* flushed from the server. We may receive new keys from the master with
|
||||
* flushed from the g_pserver-> We may receive new keys from the master with
|
||||
* the same name/db and it is no longer a good idea to expire them.
|
||||
*
|
||||
* Note: technically we should handle the case of a single DB being flushed
|
||||
* but it is not worth it since anyway race conditions using the same set
|
||||
* of key names in a wriatable slave and in its master will lead to
|
||||
* of key names in a wriatable replica and in its master will lead to
|
||||
* inconsistencies. This is just a best-effort thing we do. */
|
||||
void flushSlaveKeysWithExpireList(void) {
|
||||
if (slaveKeysWithExpire) {
|
||||
@@ -420,31 +579,31 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
* of a replica instance.
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
if (when <= mstime() && !server.loading && !listLength(server.masters)) {
|
||||
if (when <= mstime() && !g_pserver->loading && (!listLength(g_pserver->masters) || g_pserver->fActiveReplica)) {
|
||||
robj *aux;
|
||||
|
||||
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
|
||||
int deleted = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
|
||||
dbSyncDelete(c->db,key);
|
||||
serverAssertWithInfo(c,key,deleted);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* Replicate/AOF this as an explicit DEL or UNLINK. */
|
||||
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
|
||||
aux = g_pserver->lazyfree_lazy_expire ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c,2,aux,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c,c->db,key,when);
|
||||
setExpire(c,c->db,key,nullptr,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -471,16 +630,40 @@ void pexpireatCommand(client *c) {
|
||||
|
||||
/* Implements TTL and PTTL */
|
||||
void ttlGenericCommand(client *c, int output_ms) {
|
||||
long long expire, ttl = -1;
|
||||
long long expire = -1, ttl = -1;
|
||||
|
||||
/* If the key does not exist at all, return -2 */
|
||||
if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
|
||||
if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == nullptr) {
|
||||
addReplyLongLong(c,-2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The key exists. Return -1 if it has no expire, or the actual
|
||||
* TTL value otherwise. */
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
* TTL value otherwise. */
|
||||
expireEntry *pexpire = getExpire(c->db,c->argv[1]);
|
||||
|
||||
if (c->argc == 2) {
|
||||
// primary expire
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
} else if (c->argc == 3) {
|
||||
// We want a subkey expire
|
||||
if (pexpire && pexpire->FFat()) {
|
||||
for (auto itr : *pexpire) {
|
||||
if (itr.subkey() == nullptr)
|
||||
continue;
|
||||
if (sdscmp((sds)itr.subkey(), szFromObj(c->argv[2])) == 0) {
|
||||
expire = itr.when();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "Invalid arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (expire != -1) {
|
||||
ttl = expire-mstime();
|
||||
if (ttl < 0) ttl = 0;
|
||||
@@ -505,11 +688,22 @@ void pttlCommand(client *c) {
|
||||
/* PERSIST key */
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
if (c->argc == 2) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
addReplyError(c, "Invalid arguments");
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
@@ -520,7 +714,7 @@ void persistCommand(client *c) {
|
||||
void touchCommand(client *c) {
|
||||
int touched = 0;
|
||||
for (int j = 1; j < c->argc; j++)
|
||||
if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++;
|
||||
if (lookupKeyRead(c->db,c->argv[j]) != nullptr) touched++;
|
||||
addReplyLongLong(c,touched);
|
||||
}
|
||||
|
||||
+316
-28
@@ -27,6 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "fastlock.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -36,6 +37,14 @@
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <limits.h>
|
||||
#include <map>
|
||||
#ifdef __linux__
|
||||
#include <linux/futex.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "config.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
@@ -49,6 +58,17 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef UNUSED
|
||||
#define UNUSED(x) ((void)x)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <ucontext.h>
|
||||
__attribute__((weak)) void logStackTrace(ucontext_t *) {}
|
||||
#endif
|
||||
|
||||
extern int g_fInCrash;
|
||||
|
||||
/****************************************************
|
||||
*
|
||||
* Implementation of a fair spinlock. To promote fairness we
|
||||
@@ -56,14 +76,85 @@
|
||||
*
|
||||
****************************************************/
|
||||
|
||||
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
|
||||
uint64_t g_longwaits = 0;
|
||||
|
||||
uint64_t fastlock_getlongwaitcount()
|
||||
#if !defined(__has_feature)
|
||||
#define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex);
|
||||
#endif
|
||||
|
||||
#if __has_feature(thread_sanitizer)
|
||||
|
||||
/* Report that a lock has been created at address "lock". */
|
||||
#define ANNOTATE_RWLOCK_CREATE(lock) \
|
||||
AnnotateRWLockCreate(__FILE__, __LINE__, lock)
|
||||
|
||||
/* Report that the lock at address "lock" is about to be destroyed. */
|
||||
#define ANNOTATE_RWLOCK_DESTROY(lock) \
|
||||
AnnotateRWLockDestroy(__FILE__, __LINE__, lock)
|
||||
|
||||
/* Report that the lock at address "lock" has been acquired.
|
||||
is_w=1 for writer lock, is_w=0 for reader lock. */
|
||||
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \
|
||||
AnnotateRWLockAcquired(__FILE__, __LINE__, lock, is_w)
|
||||
|
||||
/* Report that the lock at address "lock" is about to be released. */
|
||||
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \
|
||||
AnnotateRWLockReleased(__FILE__, __LINE__, lock, is_w)
|
||||
|
||||
#if defined(DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK)
|
||||
#if defined(__GNUC__)
|
||||
#define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak))
|
||||
#else
|
||||
/* TODO(glider): for Windows support we may want to change this macro in order
|
||||
to prepend __declspec(selectany) to the annotations' declarations. */
|
||||
#error weak annotations are not supported for your compiler
|
||||
#endif
|
||||
#else
|
||||
#define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
void AnnotateRWLockCreate(
|
||||
const char *file, int line,
|
||||
const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
|
||||
void AnnotateRWLockDestroy(
|
||||
const char *file, int line,
|
||||
const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
|
||||
void AnnotateRWLockAcquired(
|
||||
const char *file, int line,
|
||||
const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
|
||||
void AnnotateRWLockReleased(
|
||||
const char *file, int line,
|
||||
const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define ANNOTATE_RWLOCK_CREATE(lock)
|
||||
#define ANNOTATE_RWLOCK_DESTROY(lock)
|
||||
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w)
|
||||
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w)
|
||||
|
||||
#endif
|
||||
|
||||
#pragma weak _serverPanic
|
||||
extern "C" __attribute__((weak)) void _serverPanic(const char * /*file*/, int /*line*/, const char * /*msg*/, ...)
|
||||
{
|
||||
return g_longwaits;
|
||||
*((char*)-1) = 'x';
|
||||
}
|
||||
|
||||
#pragma weak serverLog
|
||||
__attribute__((weak)) void serverLog(int , const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
extern "C" pid_t gettid()
|
||||
{
|
||||
@@ -82,64 +173,215 @@ extern "C" pid_t gettid()
|
||||
return pidCache;
|
||||
}
|
||||
|
||||
extern "C" void fastlock_init(struct fastlock *lock)
|
||||
void printTrace()
|
||||
{
|
||||
#ifdef HAVE_BACKTRACE
|
||||
serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid());
|
||||
ucontext_t ctxt;
|
||||
getcontext(&ctxt);
|
||||
logStackTrace(&ctxt);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
const struct timespec *timeout, int val3)
|
||||
{
|
||||
return syscall(SYS_futex, uaddr, futex_op, val,
|
||||
timeout, uaddr, val3);
|
||||
}
|
||||
#endif
|
||||
|
||||
class DeadlockDetector
|
||||
{
|
||||
std::map<pid_t, fastlock *> m_mapwait;
|
||||
fastlock m_lock { "deadlock detector" };
|
||||
public:
|
||||
void registerwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
static volatile bool fInDeadlock = false;
|
||||
|
||||
if (lock == &m_lock || g_fInCrash)
|
||||
return;
|
||||
fastlock_lock(&m_lock);
|
||||
|
||||
if (fInDeadlock)
|
||||
{
|
||||
printTrace();
|
||||
fastlock_unlock(&m_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
m_mapwait.insert(std::make_pair(thispid, lock));
|
||||
|
||||
// Detect cycles
|
||||
pid_t pidCheck = thispid;
|
||||
size_t cchecks = 0;
|
||||
for (;;)
|
||||
{
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
if (itr == m_mapwait.end())
|
||||
break;
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
if (pidCheck == thispid)
|
||||
{
|
||||
// Deadlock detected, printout some debugging info and crash
|
||||
serverLog(3 /*LL_WARNING*/, "\n\n");
|
||||
serverLog(3 /*LL_WARNING*/, "!!! ERROR: Deadlock detected !!!");
|
||||
pidCheck = thispid;
|
||||
for (;;)
|
||||
{
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
if (pidCheck == thispid)
|
||||
break;
|
||||
}
|
||||
// Wake All sleeping threads so they can print their callstacks
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#ifdef __linux__
|
||||
int mask = -1;
|
||||
fInDeadlock = true;
|
||||
fastlock_unlock(&m_lock);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
|
||||
futex(&itr->second->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
|
||||
sleep(2);
|
||||
fastlock_lock(&m_lock);
|
||||
printTrace();
|
||||
#endif
|
||||
#endif
|
||||
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
|
||||
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
|
||||
}
|
||||
|
||||
if (cchecks > m_mapwait.size())
|
||||
break; // There is a cycle but we're not in it
|
||||
++cchecks;
|
||||
}
|
||||
fastlock_unlock(&m_lock);
|
||||
}
|
||||
|
||||
void clearwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
if (lock == &m_lock || g_fInCrash)
|
||||
return;
|
||||
fastlock_lock(&m_lock);
|
||||
m_mapwait.erase(thispid);
|
||||
fastlock_unlock(&m_lock);
|
||||
}
|
||||
};
|
||||
|
||||
DeadlockDetector g_dlock;
|
||||
|
||||
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
|
||||
uint64_t g_longwaits = 0;
|
||||
|
||||
extern "C" void fastlock_panic(struct fastlock *lock)
|
||||
{
|
||||
_serverPanic(__FILE__, __LINE__, "fastlock lock/unlock mismatch for: %s", lock->szName);
|
||||
}
|
||||
|
||||
uint64_t fastlock_getlongwaitcount()
|
||||
{
|
||||
uint64_t rval;
|
||||
__atomic_load(&g_longwaits, &rval, __ATOMIC_RELAXED);
|
||||
return rval;
|
||||
}
|
||||
|
||||
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
|
||||
{
|
||||
#ifdef __linux__
|
||||
g_dlock.registerwait(lock, pid);
|
||||
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
|
||||
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
|
||||
g_dlock.clearwait(lock, pid);
|
||||
#endif
|
||||
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
extern "C" void fastlock_init(struct fastlock *lock, const char *name)
|
||||
{
|
||||
lock->m_ticket.m_active = 0;
|
||||
lock->m_ticket.m_avail = 0;
|
||||
lock->m_depth = 0;
|
||||
lock->m_pidOwner = -1;
|
||||
lock->futex = 0;
|
||||
int cch = strlen(name);
|
||||
cch = std::min<int>(cch, sizeof(lock->szName)-1);
|
||||
memcpy(lock->szName, name, cch);
|
||||
lock->szName[cch] = '\0';
|
||||
ANNOTATE_RWLOCK_CREATE(lock);
|
||||
}
|
||||
|
||||
#ifndef ASM_SPINLOCK
|
||||
extern "C" void fastlock_lock(struct fastlock *lock)
|
||||
{
|
||||
if ((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_ACQUIRE) == gettid())
|
||||
int pidOwner;
|
||||
__atomic_load(&lock->m_pidOwner, &pidOwner, __ATOMIC_ACQUIRE);
|
||||
if (pidOwner == gettid())
|
||||
{
|
||||
++lock->m_depth;
|
||||
return;
|
||||
}
|
||||
|
||||
int tid = gettid();
|
||||
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
|
||||
unsigned mask = (1U << (myticket % 32));
|
||||
unsigned cloops = 0;
|
||||
ticket ticketT;
|
||||
|
||||
int cloops = 0;
|
||||
while (__atomic_load_2(&lock->m_ticket.m_active, __ATOMIC_ACQUIRE) != myticket)
|
||||
for (;;)
|
||||
{
|
||||
if ((++cloops % 1024*1024) == 0)
|
||||
{
|
||||
sched_yield();
|
||||
++g_longwaits;
|
||||
}
|
||||
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
|
||||
if ((ticketT.u & 0xffff) == myticket)
|
||||
break;
|
||||
|
||||
#if defined(__i386__) || defined(__amd64__)
|
||||
__asm__ ("pause");
|
||||
__asm__ __volatile__ ("pause");
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ __volatile__ ("yield");
|
||||
#endif
|
||||
if ((++cloops % 0x100000) == 0)
|
||||
{
|
||||
fastlock_sleep(lock, tid, ticketT.u, mask);
|
||||
}
|
||||
}
|
||||
|
||||
lock->m_depth = 1;
|
||||
__atomic_store_4(&lock->m_pidOwner, gettid(), __ATOMIC_RELEASE);
|
||||
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
|
||||
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
extern "C" int fastlock_trylock(struct fastlock *lock)
|
||||
extern "C" int fastlock_trylock(struct fastlock *lock, int fWeak)
|
||||
{
|
||||
if ((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_ACQUIRE) == gettid())
|
||||
int tid;
|
||||
__atomic_load(&lock->m_pidOwner, &tid, __ATOMIC_ACQUIRE);
|
||||
if (tid == gettid())
|
||||
{
|
||||
++lock->m_depth;
|
||||
return true;
|
||||
}
|
||||
|
||||
// cheap test
|
||||
if (lock->m_ticket.m_active != lock->m_ticket.m_avail)
|
||||
struct ticket ticketT;
|
||||
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
|
||||
if (ticketT.m_active != ticketT.m_avail)
|
||||
return false;
|
||||
|
||||
uint16_t active = __atomic_load_2(&lock->m_ticket.m_active, __ATOMIC_RELAXED);
|
||||
uint16_t active = ticketT.m_active;
|
||||
uint16_t next = active + 1;
|
||||
|
||||
struct ticket ticket_expect { active, active };
|
||||
struct ticket ticket_setiflocked { active, next };
|
||||
if (__atomic_compare_exchange(&lock->m_ticket, &ticket_expect, &ticket_setiflocked, false /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
|
||||
struct ticket ticket_expect { { { active, active } } };
|
||||
struct ticket ticket_setiflocked { { { active, next } } };
|
||||
if (__atomic_compare_exchange(&lock->m_ticket.u, &ticket_expect.u, &ticket_setiflocked.u, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
{
|
||||
lock->m_depth = 1;
|
||||
__atomic_store_4(&lock->m_pidOwner, gettid(), __ATOMIC_RELEASE);
|
||||
tid = gettid();
|
||||
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
|
||||
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -150,10 +392,39 @@ extern "C" void fastlock_unlock(struct fastlock *lock)
|
||||
--lock->m_depth;
|
||||
if (lock->m_depth == 0)
|
||||
{
|
||||
assert((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_RELAXED) >= 0); // unlock after free
|
||||
lock->m_pidOwner = -1;
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
__atomic_fetch_add(&lock->m_ticket.m_active, 1, __ATOMIC_ACQ_REL); // on x86 the atomic is not required here, but ASM handles that case
|
||||
int pidT;
|
||||
__atomic_load(&lock->m_pidOwner, &pidT, __ATOMIC_RELAXED);
|
||||
assert(pidT >= 0); // unlock after free
|
||||
int t = -1;
|
||||
__atomic_store(&lock->m_pidOwner, &t, __ATOMIC_RELEASE);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
ANNOTATE_RWLOCK_RELEASED(lock, true);
|
||||
uint16_t activeNew = __atomic_add_fetch(&lock->m_ticket.m_active, 1, __ATOMIC_RELEASE); // on x86 the atomic is not required here, but ASM handles that case
|
||||
#ifdef __linux__
|
||||
unlock_futex(lock, activeNew);
|
||||
#else
|
||||
UNUSED(activeNew);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#define ROL32(v, shift) ((v << shift) | (v >> (32-shift)))
|
||||
extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex)
|
||||
{
|
||||
unsigned mask = (1U << (ifutex % 32));
|
||||
unsigned futexT;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
__atomic_load(&lock->futex, &futexT, __ATOMIC_ACQUIRE);
|
||||
futexT &= mask;
|
||||
if (!futexT)
|
||||
break;
|
||||
|
||||
if (futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask) == 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -164,10 +435,27 @@ extern "C" void fastlock_free(struct fastlock *lock)
|
||||
assert((lock->m_ticket.m_active == lock->m_ticket.m_avail) // Asser the lock is unlocked
|
||||
|| (lock->m_pidOwner == gettid() && (lock->m_ticket.m_active == lock->m_ticket.m_avail-1))); // OR we own the lock and nobody else is waiting
|
||||
lock->m_pidOwner = -2; // sentinal value indicating free
|
||||
ANNOTATE_RWLOCK_DESTROY(lock);
|
||||
}
|
||||
|
||||
|
||||
bool fastlock::fOwnLock()
|
||||
{
|
||||
return gettid() == m_pidOwner;
|
||||
int tid;
|
||||
__atomic_load(&m_pidOwner, &tid, __ATOMIC_RELAXED);
|
||||
return gettid() == tid;
|
||||
}
|
||||
|
||||
int fastlock_unlock_recursive(struct fastlock *lock)
|
||||
{
|
||||
int rval = lock->m_depth;
|
||||
lock->m_depth = 1;
|
||||
fastlock_unlock(lock);
|
||||
return rval;
|
||||
}
|
||||
|
||||
void fastlock_lock_recursive(struct fastlock *lock, int nesting)
|
||||
{
|
||||
fastlock_lock(lock);
|
||||
lock->m_depth = nesting;
|
||||
}
|
||||
+41
-12
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -7,11 +8,13 @@ extern "C" {
|
||||
|
||||
/* Begin C API */
|
||||
struct fastlock;
|
||||
void fastlock_init(struct fastlock *lock);
|
||||
void fastlock_init(struct fastlock *lock, const char *name);
|
||||
void fastlock_lock(struct fastlock *lock);
|
||||
int fastlock_trylock(struct fastlock *lock);
|
||||
int fastlock_trylock(struct fastlock *lock, int fWeak);
|
||||
void fastlock_unlock(struct fastlock *lock);
|
||||
void fastlock_free(struct fastlock *lock);
|
||||
int fastlock_unlock_recursive(struct fastlock *lock);
|
||||
void fastlock_lock_recursive(struct fastlock *lock, int nesting);
|
||||
|
||||
uint64_t fastlock_getlongwaitcount(); // this is a global value
|
||||
|
||||
@@ -20,39 +23,65 @@ uint64_t fastlock_getlongwaitcount(); // this is a global value
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
struct ticket
|
||||
{
|
||||
uint16_t m_active;
|
||||
uint16_t m_avail;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint16_t m_active;
|
||||
uint16_t m_avail;
|
||||
};
|
||||
unsigned u;
|
||||
};
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
struct fastlock
|
||||
{
|
||||
volatile struct ticket m_ticket;
|
||||
|
||||
volatile int m_pidOwner;
|
||||
volatile int m_depth;
|
||||
char szName[56];
|
||||
/* Volatile data on seperate cache line */
|
||||
volatile struct ticket m_ticket;
|
||||
unsigned futex;
|
||||
char padding[56]; // ensure ticket and futex are on their own independent cache line
|
||||
|
||||
#ifdef __cplusplus
|
||||
fastlock()
|
||||
fastlock(const char *name)
|
||||
{
|
||||
fastlock_init(this);
|
||||
fastlock_init(this, name);
|
||||
}
|
||||
|
||||
void lock()
|
||||
inline void lock()
|
||||
{
|
||||
fastlock_lock(this);
|
||||
}
|
||||
|
||||
bool try_lock()
|
||||
inline bool try_lock(bool fWeak = false)
|
||||
{
|
||||
return !!fastlock_trylock(this);
|
||||
return !!fastlock_trylock(this, fWeak);
|
||||
}
|
||||
|
||||
void unlock()
|
||||
inline void unlock()
|
||||
{
|
||||
fastlock_unlock(this);
|
||||
}
|
||||
|
||||
int unlock_recursive()
|
||||
{
|
||||
return fastlock_unlock_recursive(this);
|
||||
}
|
||||
|
||||
void lock_recursive(int nesting)
|
||||
{
|
||||
fastlock_lock_recursive(this, nesting);
|
||||
}
|
||||
|
||||
bool fOwnLock(); // true if this thread owns the lock, NOTE: not 100% reliable, use for debugging only
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
|
||||
+119
-89
@@ -1,122 +1,152 @@
|
||||
section .text
|
||||
.intel_syntax noprefix
|
||||
.text
|
||||
|
||||
extern gettid
|
||||
extern sched_yield
|
||||
extern g_longwaits
|
||||
.extern gettid
|
||||
.extern fastlock_sleep
|
||||
|
||||
; This is the first use of assembly in this codebase, a valid question is WHY?
|
||||
; The spinlock we implement here is performance critical, and simply put GCC
|
||||
; emits awful code. The original C code is left in fastlock.cpp for reference
|
||||
; and x-plat.
|
||||
# This is the first use of assembly in this codebase, a valid question is WHY?
|
||||
# The spinlock we implement here is performance critical, and simply put GCC
|
||||
# emits awful code. The original C code is left in fastlock.cpp for reference
|
||||
# and x-plat.
|
||||
|
||||
ALIGN 16
|
||||
global fastlock_lock
|
||||
.ALIGN 16
|
||||
.global fastlock_lock
|
||||
.type fastlock_lock,@function
|
||||
fastlock_lock:
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
.cfi_startproc
|
||||
.cfi_def_cfa rsp, 8
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
|
||||
; First get our TID and put it in ecx
|
||||
push rdi ; we need our struct pointer (also balance the stack for the call)
|
||||
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax ; back it up in esi
|
||||
mov rdi, [rsp] ; get our pointer back
|
||||
# First get our TID and put it in ecx
|
||||
push rdi # we need our struct pointer (also balance the stack for the call)
|
||||
.cfi_adjust_cfa_offset 8
|
||||
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax # back it up in esi
|
||||
pop rdi # get our pointer back
|
||||
.cfi_adjust_cfa_offset -8
|
||||
|
||||
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
||||
je .LLocked ; Don't spin in that case
|
||||
cmp [rdi], esi # Is the TID we got back the owner of the lock?
|
||||
je .LLocked # Don't spin in that case
|
||||
|
||||
xor eax, eax ; eliminate partial register dependency
|
||||
inc eax ; we want to add one
|
||||
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
|
||||
; eax now contains the ticket
|
||||
xor eax, eax # eliminate partial register dependency
|
||||
inc eax # we want to add one
|
||||
lock xadd [rdi+66], ax # do the xadd, ax contains the value before the addition
|
||||
# ax now contains the ticket
|
||||
# OK Start the wait loop
|
||||
xor ecx, ecx
|
||||
ALIGN 16
|
||||
.ALIGN 16
|
||||
.LLoop:
|
||||
cmp [rdi], ax ; is our ticket up?
|
||||
je .LLocked ; leave the loop
|
||||
mov edx, [rdi+64]
|
||||
cmp dx, ax # is our ticket up?
|
||||
je .LLocked # leave the loop
|
||||
pause
|
||||
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
|
||||
; 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
jnc .LLoop ; If so, give up our timeslice to someone who's doing real work
|
||||
; Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
||||
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
||||
; taking a long time to be released anyways. We optimize for the common case of short
|
||||
; lock intervals. That's why we're using a spinlock in the first place
|
||||
add ecx, 0x1000 # Have we been waiting a long time? (oflow if we have)
|
||||
# 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
jnc .LLoop # If so, give up our timeslice to someone who's doing real work
|
||||
# Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
||||
# But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
||||
# taking a long time to be released anyways. We optimize for the common case of short
|
||||
# lock intervals. That's why we're using a spinlock in the first place
|
||||
# If we get here we're going to sleep in the kernel with a futex
|
||||
push rdi
|
||||
push rsi
|
||||
push rax
|
||||
mov rax, 24 ; sys_sched_yield
|
||||
syscall ; give up our timeslice we'll be here a while
|
||||
.cfi_adjust_cfa_offset 24
|
||||
# Setup the syscall args
|
||||
|
||||
# rdi ARG1 futex (already in rdi)
|
||||
# rsi ARG2 tid (already in esi)
|
||||
# rdx ARG3 ticketT.u (already in edx)
|
||||
bts ecx, eax # rcx ARG4 mask
|
||||
call fastlock_sleep
|
||||
# cleanup and continue
|
||||
pop rax
|
||||
pop rsi
|
||||
mov rcx, g_longwaits
|
||||
inc qword [rcx] ; increment our long wait counter
|
||||
mov rdi, [rsp] ; our struct pointer is on the stack already
|
||||
xor ecx, ecx ; Reset our loop counter
|
||||
jmp .LLoop ; Get back in the game
|
||||
ALIGN 16
|
||||
pop rdi
|
||||
.cfi_adjust_cfa_offset -24
|
||||
xor ecx, ecx # Reset our loop counter
|
||||
jmp .LLoop # Get back in the game
|
||||
.ALIGN 16
|
||||
.LLocked:
|
||||
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
||||
inc dword [rdi+8] ; lock->m_depth++
|
||||
add rsp, 8 ; fix stack
|
||||
mov [rdi], esi # lock->m_pidOwner = gettid()
|
||||
inc dword ptr [rdi+4] # lock->m_depth++
|
||||
ret
|
||||
.cfi_endproc
|
||||
|
||||
ALIGN 16
|
||||
global fastlock_trylock
|
||||
.ALIGN 16
|
||||
.global fastlock_trylock
|
||||
.type fastlock_trylock,@function
|
||||
fastlock_trylock:
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
|
||||
; First get our TID and put it in ecx
|
||||
push rdi ; we need our struct pointer (also balance the stack for the call)
|
||||
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax ; back it up in esi
|
||||
pop rdi ; get our pointer back
|
||||
# First get our TID and put it in ecx
|
||||
push rdi # we need our struct pointer (also balance the stack for the call)
|
||||
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax # back it up in esi
|
||||
pop rdi # get our pointer back
|
||||
|
||||
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
||||
je .LRecursive ; Don't spin in that case
|
||||
cmp [rdi], esi # Is the TID we got back the owner of the lock?
|
||||
je .LRecursive # Don't spin in that case
|
||||
|
||||
mov eax, [rdi] ; get both active and avail counters
|
||||
mov ecx, eax ; duplicate in ecx
|
||||
ror ecx, 16 ; swap upper and lower 16-bits
|
||||
cmp eax, ecx ; are the upper and lower 16-bits the same?
|
||||
jnz .LAlreadyLocked ; If not return failure
|
||||
mov eax, [rdi+64] # get both active and avail counters
|
||||
mov ecx, eax # duplicate in ecx
|
||||
ror ecx, 16 # swap upper and lower 16-bits
|
||||
cmp eax, ecx # are the upper and lower 16-bits the same?
|
||||
jnz .LAlreadyLocked # If not return failure
|
||||
|
||||
; at this point we know eax+ecx have [avail][active] and they are both the same
|
||||
add ecx, 10000h ; increment avail, ecx is now our wanted value
|
||||
lock cmpxchg [rdi], ecx ; If rdi still contains the value in eax, put in ecx (inc avail)
|
||||
jnz .LAlreadyLocked ; If Z is not set then someone locked it while we were preparing
|
||||
# at this point we know eax+ecx have [avail][active] and they are both the same
|
||||
add ecx, 0x10000 # increment avail, ecx is now our wanted value
|
||||
lock cmpxchg [rdi+64], ecx # If rdi still contains the value in eax, put in ecx (inc avail)
|
||||
jnz .LAlreadyLocked # If Z is not set then someone locked it while we were preparing
|
||||
xor eax, eax
|
||||
inc eax ; return SUCCESS! (eax=1)
|
||||
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
||||
mov dword [rdi+8], eax ; lock->m_depth = 1
|
||||
inc eax # return SUCCESS! (eax=1)
|
||||
mov [rdi], esi # lock->m_pidOwner = gettid()
|
||||
mov dword ptr [rdi+4], eax # lock->m_depth = 1
|
||||
ret
|
||||
ALIGN 16
|
||||
.ALIGN 16
|
||||
.LRecursive:
|
||||
xor eax, eax
|
||||
inc eax ; return SUCCESS! (eax=1)
|
||||
inc dword [rdi+8] ; lock->m_depth++
|
||||
inc eax # return SUCCESS! (eax=1)
|
||||
inc dword ptr [rdi+4] # lock->m_depth++
|
||||
ret
|
||||
ALIGN 16
|
||||
.ALIGN 16
|
||||
.LAlreadyLocked:
|
||||
xor eax, eax ; return 0;
|
||||
xor eax, eax # return 0
|
||||
ret
|
||||
|
||||
ALIGN 16
|
||||
global fastlock_unlock
|
||||
.ALIGN 16
|
||||
.global fastlock_unlock
|
||||
.type fastlock_unlock,@function
|
||||
fastlock_unlock:
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
|
||||
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
|
||||
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
|
||||
inc word [rdi] ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
sub dword ptr [rdi+4], 1 # decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
|
||||
jnz .LDone # if depth is non-zero this is a recursive unlock, and we still hold it
|
||||
mov dword ptr [rdi], -1 # pidOwner = -1 (we don't own it anymore)
|
||||
mov esi, [rdi+64] # get current active (this one)
|
||||
inc esi # bump it to the next thread
|
||||
mov word ptr [rdi+64], si # give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
|
||||
mfence # sync other threads
|
||||
# At this point the lock is removed, however we must wake up any pending futexs
|
||||
mov edx, [rdi+64+4] # load the futex mask
|
||||
bt edx, esi # is the next thread waiting on a futex?
|
||||
jc unlock_futex # unlock the futex if necessary
|
||||
ret # if not we're done.
|
||||
.ALIGN 16
|
||||
.LDone:
|
||||
js fastlock_panic # panic if we made m_depth negative
|
||||
ret
|
||||
+12
-12
@@ -110,7 +110,7 @@ int extractLongLatOrReply(client *c, robj **argv, double *xy) {
|
||||
/* Input Argument Helper */
|
||||
/* Decode lat/long from a zset member's score.
|
||||
* Returns C_OK on successful decoding, otherwise C_ERR is returned. */
|
||||
int longLatFromMember(robj *zobj, robj *member, double *xy) {
|
||||
int longLatFromMember(robj_roptr zobj, robj *member, double *xy) {
|
||||
double score = 0;
|
||||
|
||||
if (zsetScore(zobj, szFromObj(member), &score) == C_ERR) return C_ERR;
|
||||
@@ -222,7 +222,7 @@ int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius,
|
||||
* using multiple queries to the sorted set, that we later need to sort
|
||||
* via qsort. Similarly we need to be able to reject points outside the search
|
||||
* radius area ASAP in order to allocate and process more points than needed. */
|
||||
int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
|
||||
int geoGetPointsInRange(robj_roptr zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
|
||||
/* minex 0 = include min in range; maxex 1 = exclude max in range */
|
||||
/* That's: min <= val < max */
|
||||
zrangespec range = { min, max, 0, 1 };
|
||||
@@ -315,7 +315,7 @@ void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bit
|
||||
/* Obtain all members between the min/max of this geohash bounding box.
|
||||
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
|
||||
* Return the number of points added to the array. */
|
||||
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
|
||||
int membersOfGeoHashBox(robj_roptr zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
|
||||
GeoHashFix52Bits min, max;
|
||||
|
||||
scoresOfGeoHashBox(hash,&min,&max);
|
||||
@@ -323,7 +323,7 @@ int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon,
|
||||
}
|
||||
|
||||
/* Search all eight neighbors + self geohash box */
|
||||
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
|
||||
int membersOfAllNeighbors(robj_roptr zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
|
||||
GeoHashBits neighbors[9];
|
||||
unsigned int i, count = 0, last_processed = 0;
|
||||
int debugmsg = 0;
|
||||
@@ -465,8 +465,8 @@ void georadiusGeneric(client *c, int flags) {
|
||||
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL ||
|
||||
robj_roptr zobj;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == nullptr ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
@@ -661,11 +661,11 @@ void georadiusGeneric(client *c, int flags) {
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
g_pserver->dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReplyLongLong(c, returned_items);
|
||||
}
|
||||
@@ -701,7 +701,7 @@ void geohashCommand(client *c) {
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
robj_roptr zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
@@ -754,7 +754,7 @@ void geoposCommand(client *c) {
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
robj_roptr zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
@@ -796,9 +796,9 @@ void geodistCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
robj_roptr zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
== nullptr || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
double score1, score2, xyxy[4];
|
||||
|
||||
+19
-7
@@ -343,6 +343,12 @@ struct commandHelp {
|
||||
"Set the expiration for a key as a UNIX timestamp",
|
||||
0,
|
||||
"1.2.0" },
|
||||
{ "EXPIREMEMBER",
|
||||
"key subkey delay [Unit: s,ms]",
|
||||
"set a subkey's time to live in seconds (or milliseconds)"},
|
||||
{ "EXPIREMEMBERAT",
|
||||
"key subkey timestamp",
|
||||
"Set the expiration for a subkey as a UNIX timestamp"},
|
||||
{ "FLUSHALL",
|
||||
"[ASYNC]",
|
||||
"Remove all keys from all databases",
|
||||
@@ -629,8 +635,8 @@ struct commandHelp {
|
||||
0,
|
||||
"2.2.3" },
|
||||
{ "PERSIST",
|
||||
"key",
|
||||
"Remove the expiration from a key",
|
||||
"key [subkey]",
|
||||
"Remove the expiration from a key or subkey",
|
||||
0,
|
||||
"2.2.0" },
|
||||
{ "PEXPIRE",
|
||||
@@ -674,8 +680,8 @@ struct commandHelp {
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PTTL",
|
||||
"key",
|
||||
"Get the time to live for a key in milliseconds",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PUBLISH",
|
||||
@@ -949,8 +955,8 @@ struct commandHelp {
|
||||
0,
|
||||
"3.2.1" },
|
||||
{ "TTL",
|
||||
"key",
|
||||
"Get the time to live for a key",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "TYPE",
|
||||
@@ -1162,7 +1168,13 @@ struct commandHelp {
|
||||
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Add multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"2.0.0" }
|
||||
"2.0.0" },
|
||||
{ "KEYDB.CRON",
|
||||
"name [single/repeat] [optional: start] delay script numkeys [key N] [arg N]",
|
||||
"Run a specified script after start + delay, optionally repeating every delay interval. The job may be cancelled by deleting the key associated with the job (name parameter)",
|
||||
10,
|
||||
"6.5.2"
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
+17
-16
@@ -176,7 +176,7 @@
|
||||
* involved in updating the sparse representation is not justified by the
|
||||
* memory savings. The exact maximum length of the sparse representation
|
||||
* when this implementation switches to the dense representation is
|
||||
* configured via the define server.hll_sparse_max_bytes.
|
||||
* configured via the define g_pserver->hll_sparse_max_bytes.
|
||||
*/
|
||||
|
||||
struct hllhdr {
|
||||
@@ -652,7 +652,7 @@ int hllSparseToDense(robj *o) {
|
||||
* As a side effect the function may promote the HLL representation from
|
||||
* sparse to dense: this happens when a register requires to be set to a value
|
||||
* not representable with the sparse representation, or when the resulting
|
||||
* size would be greater than server.hll_sparse_max_bytes. */
|
||||
* size would be greater than g_pserver->hll_sparse_max_bytes. */
|
||||
int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
struct hllhdr *hdr;
|
||||
uint8_t oldcount, *sparse, *end, *p, *prev, *next;
|
||||
@@ -710,6 +710,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
first += span;
|
||||
}
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
if (p >= end) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@@ -837,7 +838,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
deltalen = seqlen-oldlen;
|
||||
|
||||
if (deltalen > 0 &&
|
||||
sdslen(szFromObj(o))+deltalen > server.hll_sparse_max_bytes) goto promote;
|
||||
sdslen(szFromObj(o))+deltalen > g_pserver->hll_sparse_max_bytes) goto promote;
|
||||
if (deltalen && next) memmove(next+deltalen,next,end-next);
|
||||
sdsIncrLen(szFromObj(o),deltalen);
|
||||
memcpy(p,seq,seqlen);
|
||||
@@ -1073,7 +1074,7 @@ int hllAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
*
|
||||
* If the HyperLogLog is sparse and is found to be invalid, C_ERR
|
||||
* is returned, otherwise the function always succeeds. */
|
||||
int hllMerge(uint8_t *max, size_t cmax, robj *hll) {
|
||||
int hllMerge(uint8_t *max, size_t cmax, robj_roptr hll) {
|
||||
struct hllhdr *hdr = (hllhdr*)ptrFromObj(hll);
|
||||
int i;
|
||||
|
||||
@@ -1085,7 +1086,7 @@ int hllMerge(uint8_t *max, size_t cmax, robj *hll) {
|
||||
if (val > max[i]) max[i] = val;
|
||||
}
|
||||
} else {
|
||||
uint8_t *p = (uint8_t*)ptrFromObj(hll), *end = p + sdslen(szFromObj(hll));
|
||||
const uint8_t *p = (const uint8_t*)ptrFromObj(hll), *end = p + sdslen(szFromObj(hll));
|
||||
long runlen, regval;
|
||||
|
||||
p += HLL_HDR_SIZE;
|
||||
@@ -1156,7 +1157,7 @@ robj *createHLLObject(void) {
|
||||
/* Check if the object is a String with a valid HLL representation.
|
||||
* Return C_OK if this is true, otherwise reply to the client
|
||||
* with an error and return C_ERR. */
|
||||
int isHLLObjectOrReply(client *c, robj *o) {
|
||||
int isHLLObjectOrReply(client *c, robj_roptr o) {
|
||||
struct hllhdr *hdr;
|
||||
|
||||
/* Key exists, check type */
|
||||
@@ -1221,7 +1222,7 @@ void pfaddCommand(client *c) {
|
||||
if (updated) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
}
|
||||
addReply(c, updated ? shared.cone : shared.czero);
|
||||
@@ -1248,8 +1249,8 @@ void pfcountCommand(client *c) {
|
||||
registers = max + HLL_HDR_SIZE;
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
/* Check type and size. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == nullptr) continue; /* Assume empty HLL for non existing var.*/
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
@@ -1311,7 +1312,7 @@ void pfcountCommand(client *c) {
|
||||
* may be modified and given that the HLL is a Redis string
|
||||
* we need to propagate the change. */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReplyLongLong(c,card);
|
||||
}
|
||||
@@ -1330,8 +1331,8 @@ void pfmergeCommand(client *c) {
|
||||
memset(max,0,sizeof(max));
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
/* Check type and size. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var. */
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == nullptr) continue; /* Assume empty HLL for non existing var. */
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* If at least one involved HLL is dense, use the dense representation
|
||||
@@ -1387,7 +1388,7 @@ void pfmergeCommand(client *c) {
|
||||
/* We generate a PFADD event for PFMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
@@ -1457,7 +1458,7 @@ void pfselftestCommand(client *c) {
|
||||
|
||||
/* Make sure that for small cardinalities we use sparse
|
||||
* encoding. */
|
||||
if (j == checkpoint && j < server.hll_sparse_max_bytes/2) {
|
||||
if (j == checkpoint && j < g_pserver->hll_sparse_max_bytes/2) {
|
||||
hdr2 = (hllhdr*)ptrFromObj(o);
|
||||
if (hdr2->encoding != HLL_SPARSE) {
|
||||
addReplyError(c, "TESTFAILED sparse encoding not used");
|
||||
@@ -1528,7 +1529,7 @@ void pfdebugCommand(client *c) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
return;
|
||||
}
|
||||
server.dirty++; /* Force propagation on encoding change. */
|
||||
g_pserver->dirty++; /* Force propagation on encoding change. */
|
||||
}
|
||||
|
||||
hdr = (hllhdr*)ptrFromObj(o);
|
||||
@@ -1593,7 +1594,7 @@ void pfdebugCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
conv = 1;
|
||||
server.dirty++; /* Force propagation on encoding change. */
|
||||
g_pserver->dirty++; /* Force propagation on encoding change. */
|
||||
}
|
||||
addReply(c,conv ? shared.cone : shared.czero);
|
||||
} else {
|
||||
|
||||
+25
-25
@@ -88,15 +88,15 @@ int THPGetAnonHugePagesSize(void) {
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
g_pserver->latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
}
|
||||
|
||||
/* Add the specified sample to the specified time series "event".
|
||||
* This function is usually called via latencyAddSampleIfNeeded(), that
|
||||
* is a macro that only adds the sample if the latency is higher than
|
||||
* server.latency_monitor_threshold. */
|
||||
* g_pserver->latency_monitor_threshold. */
|
||||
void latencyAddSample(const char *event, mstime_t latency) {
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictFetchValue(server.latency_events,event);
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictFetchValue(g_pserver->latency_events,event);
|
||||
time_t now = time(NULL);
|
||||
int prev;
|
||||
|
||||
@@ -106,7 +106,7 @@ void latencyAddSample(const char *event, mstime_t latency) {
|
||||
ts->idx = 0;
|
||||
ts->max = 0;
|
||||
memset(ts->samples,0,sizeof(ts->samples));
|
||||
dictAdd(server.latency_events,zstrdup(event),ts);
|
||||
dictAdd(g_pserver->latency_events,zstrdup(event),ts);
|
||||
}
|
||||
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
@@ -137,12 +137,12 @@ int latencyResetEvent(char *event_to_reset) {
|
||||
dictEntry *de;
|
||||
int resets = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
di = dictGetSafeIterator(g_pserver->latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = (char*)dictGetKey(de);
|
||||
|
||||
if (event_to_reset == NULL || strcasecmp(event,event_to_reset) == 0) {
|
||||
dictDelete(server.latency_events, event);
|
||||
dictDelete(g_pserver->latency_events, event);
|
||||
resets++;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ int latencyResetEvent(char *event_to_reset) {
|
||||
* If the specified event has no elements the structure is populate with
|
||||
* zero values. */
|
||||
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictFetchValue(server.latency_events,event);
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictFetchValue(g_pserver->latency_events,event);
|
||||
int j;
|
||||
uint64_t sum;
|
||||
|
||||
@@ -236,8 +236,8 @@ sds createLatencyReport(void) {
|
||||
|
||||
/* Return ASAP if the latency engine is disabled and it looks like it
|
||||
* was never enabled so far. */
|
||||
if (dictSize(server.latency_events) == 0 &&
|
||||
server.latency_monitor_threshold == 0)
|
||||
if (dictSize(g_pserver->latency_events) == 0 &&
|
||||
g_pserver->latency_monitor_threshold == 0)
|
||||
{
|
||||
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n");
|
||||
return report;
|
||||
@@ -249,7 +249,7 @@ sds createLatencyReport(void) {
|
||||
dictEntry *de;
|
||||
int eventnum = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
di = dictGetSafeIterator(g_pserver->latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = (char*)dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictGetVal(de);
|
||||
@@ -274,31 +274,31 @@ sds createLatencyReport(void) {
|
||||
/* Fork */
|
||||
if (!strcasecmp(event,"fork")) {
|
||||
const char *fork_quality;
|
||||
if (server.stat_fork_rate < 10) {
|
||||
if (g_pserver->stat_fork_rate < 10) {
|
||||
fork_quality = "terrible";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 25) {
|
||||
} else if (g_pserver->stat_fork_rate < 25) {
|
||||
fork_quality = "poor";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 100) {
|
||||
} else if (g_pserver->stat_fork_rate < 100) {
|
||||
fork_quality = "good";
|
||||
} else {
|
||||
fork_quality = "excellent";
|
||||
}
|
||||
report = sdscatprintf(report,
|
||||
" Fork rate is %.2f GB/sec (%s).", server.stat_fork_rate,
|
||||
" Fork rate is %.2f GB/sec (%s).", g_pserver->stat_fork_rate,
|
||||
fork_quality);
|
||||
}
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than < 0) {
|
||||
if (g_pserver->slowlog_log_slower_than < 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
server.latency_monitor_threshold)
|
||||
} else if (g_pserver->slowlog_log_slower_than/1000 >
|
||||
g_pserver->latency_monitor_threshold)
|
||||
{
|
||||
advise_slowlog_tuning = 1;
|
||||
advices++;
|
||||
@@ -401,11 +401,11 @@ sds createLatencyReport(void) {
|
||||
|
||||
/* Slow log. */
|
||||
if (advise_slowlog_enabled) {
|
||||
report = sdscatprintf(report,"- There are latency issues with potentially slow commands you are using. Try to enable the Slow Log Redis feature using the command 'CONFIG SET slowlog-log-slower-than %llu'. If the Slow log is disabled Redis is not able to log slow commands execution for you.\n", (unsigned long long)server.latency_monitor_threshold*1000);
|
||||
report = sdscatprintf(report,"- There are latency issues with potentially slow commands you are using. Try to enable the Slow Log Redis feature using the command 'CONFIG SET slowlog-log-slower-than %llu'. If the Slow log is disabled Redis is not able to log slow commands execution for you.\n", (unsigned long long)g_pserver->latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_tuning) {
|
||||
report = sdscatprintf(report,"- Your current Slow Log configuration only logs events that are slower than your configured latency monitor threshold. Please use 'CONFIG SET slowlog-log-slower-than %llu'.\n", (unsigned long long)server.latency_monitor_threshold*1000);
|
||||
report = sdscatprintf(report,"- Your current Slow Log configuration only logs events that are slower than your configured latency monitor threshold. Please use 'CONFIG SET slowlog-log-slower-than %llu'.\n", (unsigned long long)g_pserver->latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_inspect) {
|
||||
@@ -443,7 +443,7 @@ sds createLatencyReport(void) {
|
||||
report = sdscat(report,"- Assuming from the point of view of data safety this is viable in your environment, you could try to enable the 'no-appendfsync-on-rewrite' option, so that fsync will not be performed while there is a child rewriting the AOF file or producing an RDB file (the moment where there is high disk contention).\n");
|
||||
}
|
||||
|
||||
if (advise_relax_fsync_policy && server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
if (advise_relax_fsync_policy && g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
report = sdscat(report,"- Your fsync policy is set to 'always'. It is very hard to get good performances with such a setup, if possible try to relax the fsync policy to 'onesec'.\n");
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ sds createLatencyReport(void) {
|
||||
report = sdscat(report,"- Latency during the AOF atomic rename operation or when the final difference is flushed to the AOF file at the end of the rewrite, sometimes is caused by very high write load, causing the AOF buffer to get very large. If possible try to send less commands to accomplish the same work, or use Lua scripts to group multiple operations into a single EVALSHA call.\n");
|
||||
}
|
||||
|
||||
if (advise_hz && server.hz < 100) {
|
||||
if (advise_hz && g_pserver->hz < 100) {
|
||||
report = sdscat(report,"- In order to make the Redis keys expiring process more incremental, try to set the 'hz' configuration parameter to 100 using 'CONFIG SET hz 100'.\n");
|
||||
}
|
||||
|
||||
@@ -497,8 +497,8 @@ void latencyCommandReplyWithLatestEvents(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyArrayLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
addReplyArrayLen(c,dictSize(g_pserver->latency_events));
|
||||
di = dictGetIterator(g_pserver->latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = (char*)dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = (latencyTimeSeries*)dictGetVal(de);
|
||||
@@ -581,7 +581,7 @@ NULL
|
||||
|
||||
if (!strcasecmp(szFromObj(c->argv[1]),"history") && c->argc == 3) {
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = (latencyTimeSeries*)dictFetchValue(server.latency_events,ptrFromObj(c->argv[2]));
|
||||
ts = (latencyTimeSeries*)dictFetchValue(g_pserver->latency_events,ptrFromObj(c->argv[2]));
|
||||
if (ts == NULL) {
|
||||
addReplyArrayLen(c,0);
|
||||
} else {
|
||||
@@ -593,7 +593,7 @@ NULL
|
||||
dictEntry *de;
|
||||
char *event;
|
||||
|
||||
de = dictFind(server.latency_events,ptrFromObj(c->argv[2]));
|
||||
de = dictFind(g_pserver->latency_events,ptrFromObj(c->argv[2]));
|
||||
if (de == NULL) goto nodataerr;
|
||||
ts = (latencyTimeSeries*)dictGetVal(de);
|
||||
event = (char*)dictGetKey(de);
|
||||
|
||||
+4
-4
@@ -72,7 +72,7 @@ int THPIsEnabled(void);
|
||||
/* Latency monitoring macros. */
|
||||
|
||||
/* Start monitoring an event. We just set the current time. */
|
||||
#define latencyStartMonitor(var) if (server.latency_monitor_threshold) { \
|
||||
#define latencyStartMonitor(var) if (g_pserver->latency_monitor_threshold) { \
|
||||
var = mstime(); \
|
||||
} else { \
|
||||
var = 0; \
|
||||
@@ -80,14 +80,14 @@ int THPIsEnabled(void);
|
||||
|
||||
/* End monitoring an event, compute the difference with the current time
|
||||
* to check the amount of time elapsed. */
|
||||
#define latencyEndMonitor(var) if (server.latency_monitor_threshold) { \
|
||||
#define latencyEndMonitor(var) if (g_pserver->latency_monitor_threshold) { \
|
||||
var = mstime() - var; \
|
||||
}
|
||||
|
||||
/* Add the sample only if the elapsed time is >= to the configured threshold. */
|
||||
#define latencyAddSampleIfNeeded(event,var) \
|
||||
if (server.latency_monitor_threshold && \
|
||||
(var) >= server.latency_monitor_threshold) \
|
||||
if (g_pserver->latency_monitor_threshold && \
|
||||
(var) >= g_pserver->latency_monitor_threshold) \
|
||||
latencyAddSample((event),(var));
|
||||
|
||||
/* Remove time from a nested event. */
|
||||
|
||||
+21
-16
@@ -52,16 +52,19 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
* will be reclaimed in a different bio.c thread. */
|
||||
#define LAZYFREE_THRESHOLD 64
|
||||
int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,ptrFromObj(key));
|
||||
|
||||
/* If the value is composed of a few allocations, to free in a lazy way
|
||||
* is actually just slower... So under a certain limit we just free
|
||||
* the object synchronously. */
|
||||
dictEntry *de = dictUnlink(db->pdict,ptrFromObj(key));
|
||||
if (de) {
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
if (val->FExpires())
|
||||
{
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
removeExpireCore(db,key,de);
|
||||
}
|
||||
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
|
||||
/* If releasing the object is too much work, do it in the background
|
||||
@@ -72,7 +75,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* objects, and then call dbDelete(). In this case we'll fall
|
||||
* through and reach the dictFreeUnlinkedEntry() call, that will be
|
||||
* equivalent to just calling decrRefCount(). */
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->getrefcount(std::memory_order_relaxed) == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->pdict,de,NULL);
|
||||
@@ -83,7 +86,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->pdict,de);
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -93,7 +96,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
/* Free an object, if the object is huge enough, free it in async way. */
|
||||
void freeObjAsync(robj *o) {
|
||||
size_t free_effort = lazyfreeGetFreeEffort(o);
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->refcount == 1) {
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->getrefcount(std::memory_order_relaxed) == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
|
||||
} else {
|
||||
@@ -105,21 +108,23 @@ void freeObjAsync(robj *o) {
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
void emptyDbAsync(redisDb *db) {
|
||||
dict *oldht1 = db->pdict, *oldht2 = db->expires;
|
||||
dict *oldht1 = db->pdict;
|
||||
auto *set = db->setexpire;
|
||||
db->setexpire = new (MALLOC_LOCAL) expireset();
|
||||
db->expireitr = db->setexpire->end();
|
||||
db->pdict = dictCreate(&dbDictType,NULL);
|
||||
db->expires = dictCreate(&keyptrDictType,NULL);
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,set);
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one
|
||||
* and scheduiling the old for lazy freeing. */
|
||||
void slotToKeyFlushAsync(void) {
|
||||
rax *old = server.cluster->slots_to_keys;
|
||||
rax *old = g_pserver->cluster->slots_to_keys;
|
||||
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
g_pserver->cluster->slots_to_keys = raxNew();
|
||||
memset(g_pserver->cluster->slots_keys_count,0,
|
||||
sizeof(g_pserver->cluster->slots_keys_count));
|
||||
atomicIncr(lazyfree_objects,old->numele);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,old);
|
||||
}
|
||||
@@ -136,10 +141,10 @@ void lazyfreeFreeObjectFromBioThread(robj *o) {
|
||||
* when the database was logically deleted. 'sl' is a skiplist used by
|
||||
* Redis Cluster in order to take the hash slots -> keys mapping. This
|
||||
* may be NULL if Redis Cluster is disabled. */
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, expireset *set) {
|
||||
size_t numkeys = dictSize(ht1);
|
||||
dictRelease(ht1);
|
||||
dictRelease(ht2);
|
||||
delete set;
|
||||
atomicDecr(lazyfree_objects,numkeys);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
#define LP_AFTER 1
|
||||
#define LP_REPLACE 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
unsigned char *lpNew(void);
|
||||
void lpFree(unsigned char *lp);
|
||||
unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, unsigned char *p, int where, unsigned char **newp);
|
||||
@@ -58,4 +62,8 @@ unsigned char *lpPrev(unsigned char *lp, unsigned char *p);
|
||||
uint32_t lpBytes(unsigned char *lp);
|
||||
unsigned char *lpSeek(unsigned char *lp, long index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+158
-98
@@ -30,6 +30,9 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include <dlfcn.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
#include "redismodule.h"
|
||||
@@ -55,7 +58,7 @@ struct RedisModule {
|
||||
typedef struct RedisModule RedisModule;
|
||||
|
||||
/* This represents a shared API. Shared APIs will be used to populate
|
||||
* the server.sharedapi dictionary, mapping names of APIs exported by
|
||||
* the g_pserver->sharedapi dictionary, mapping names of APIs exported by
|
||||
* modules for other modules to use, to their structure specifying the
|
||||
* function pointer that can be called. */
|
||||
struct RedisModuleSharedAPI {
|
||||
@@ -235,7 +238,6 @@ static list *moduleUnblockedClients;
|
||||
|
||||
/* We need a mutex that is unlocked / relocked in beforeSleep() in order to
|
||||
* allow thread safe contexts to execute commands at a safe moment. */
|
||||
static pthread_rwlock_t moduleGIL = PTHREAD_RWLOCK_INITIALIZER;
|
||||
int fModuleGILWlocked = FALSE;
|
||||
|
||||
/* Function pointer type for keyspace event notification subscriptions from modules. */
|
||||
@@ -293,6 +295,12 @@ typedef struct RedisModuleCommandFilter {
|
||||
/* Registered filters */
|
||||
static list *moduleCommandFilters;
|
||||
|
||||
/* Module GIL Variables */
|
||||
static int s_cAcquisitionsServer = 0;
|
||||
static int s_cAcquisitionsModule = 0;
|
||||
static std::mutex s_mutex;
|
||||
static std::condition_variable s_cv;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -427,8 +435,8 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) {
|
||||
switch(type) {
|
||||
case REDISMODULE_KEYTYPE_LIST:
|
||||
obj = createQuicklistObject();
|
||||
quicklistSetOptions((quicklist*)obj->m_ptr, server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
quicklistSetOptions((quicklist*)obj->m_ptr, g_pserver->list_max_ziplist_size,
|
||||
g_pserver->list_compress_depth);
|
||||
break;
|
||||
case REDISMODULE_KEYTYPE_ZSET:
|
||||
obj = createZsetZiplistObject();
|
||||
@@ -492,7 +500,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
|
||||
* This function is not meant to be used by modules developer, it is only
|
||||
* used implicitly by including redismodule.h. */
|
||||
int RM_GetApi(const char *funcname, void **targetPtrPtr) {
|
||||
dictEntry *he = dictFind(server.moduleapi, funcname);
|
||||
dictEntry *he = dictFind(g_pserver->moduleapi, funcname);
|
||||
if (!he) return REDISMODULE_ERR;
|
||||
*targetPtrPtr = dictGetVal(he);
|
||||
return REDISMODULE_OK;
|
||||
@@ -528,7 +536,7 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
alsoPropagate(cserver.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
@@ -558,7 +566,7 @@ void RedisModuleCommandDispatcher(client *c) {
|
||||
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)
|
||||
if (c->argv[i]->getrefcount(std::memory_order_relaxed) > 1)
|
||||
trimStringObjectIfNeeded(c->argv[i]);
|
||||
}
|
||||
}
|
||||
@@ -700,7 +708,7 @@ int commandFlagsFromString(char *s) {
|
||||
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
||||
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
if (flags == -1) return REDISMODULE_ERR;
|
||||
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
|
||||
if ((flags & CMD_MODULE_NO_CLUSTER) && g_pserver->cluster_enabled)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
struct redisCommand *rediscmd;
|
||||
@@ -734,8 +742,8 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
cp->rediscmd->keystep = keystep;
|
||||
cp->rediscmd->microseconds = 0;
|
||||
cp->rediscmd->calls = 0;
|
||||
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
|
||||
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
|
||||
dictAdd(g_pserver->commands,sdsdup(cmdname),cp->rediscmd);
|
||||
dictAdd(g_pserver->orig_commands,sdsdup(cmdname),cp->rediscmd);
|
||||
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -756,6 +764,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
module->usedby = listCreate();
|
||||
module->usingMods = listCreate();
|
||||
module->filters = listCreate();
|
||||
module->in_call = 0;
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
@@ -1028,7 +1037,7 @@ int RM_StringCompare(RedisModuleString *a, RedisModuleString *b) {
|
||||
/* Return the (possibly modified in encoding) input 'str' object if
|
||||
* the string is unshared, otherwise NULL is returned. */
|
||||
RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) {
|
||||
if (str->refcount != 1) {
|
||||
if (str->getrefcount(std::memory_order_relaxed) != 1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Module attempted to use an in-place string modify operation "
|
||||
"with a string referenced multiple times. Please check the code "
|
||||
@@ -1244,6 +1253,17 @@ 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. */
|
||||
@@ -1355,7 +1375,7 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
||||
/* Release the argv. */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
zfree(argv);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1374,7 +1394,7 @@ int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
|
||||
alsoPropagate(ctx->client->cmd,ctx->client->db->id,
|
||||
ctx->client->argv,ctx->client->argc,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1419,7 +1439,7 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a replica
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
|
||||
*
|
||||
@@ -1454,29 +1474,32 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
|
||||
}
|
||||
|
||||
if (server.cluster_enabled)
|
||||
if (g_pserver->cluster_enabled)
|
||||
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
|
||||
|
||||
if (g_pserver->loading)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LOADING;
|
||||
|
||||
/* Maxmemory and eviction policy */
|
||||
if (server.maxmemory > 0) {
|
||||
if (g_pserver->maxmemory > 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
|
||||
|
||||
if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION)
|
||||
if (g_pserver->maxmemory_policy != MAXMEMORY_NO_EVICTION)
|
||||
flags |= REDISMODULE_CTX_FLAGS_EVICT;
|
||||
}
|
||||
|
||||
/* Persistence flags */
|
||||
if (server.aof_state != AOF_OFF)
|
||||
if (g_pserver->aof_state != AOF_OFF)
|
||||
flags |= REDISMODULE_CTX_FLAGS_AOF;
|
||||
if (server.saveparamslen > 0)
|
||||
if (g_pserver->saveparamslen > 0)
|
||||
flags |= REDISMODULE_CTX_FLAGS_RDB;
|
||||
|
||||
/* Replication flags */
|
||||
if (listLength(server.masters) == 0) {
|
||||
if (listLength(g_pserver->masters) == 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MASTER;
|
||||
} else {
|
||||
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
|
||||
if (server.repl_slave_ro)
|
||||
if (g_pserver->repl_slave_ro)
|
||||
flags |= REDISMODULE_CTX_FLAGS_READONLY;
|
||||
}
|
||||
|
||||
@@ -1525,7 +1548,7 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
|
||||
if (mode & REDISMODULE_WRITE) {
|
||||
value = lookupKeyWrite(ctx->client->db,keyname);
|
||||
} else {
|
||||
value = lookupKeyRead(ctx->client->db,keyname);
|
||||
value = lookupKeyRead(ctx->client->db,keyname).unsafe_robjcast();
|
||||
if (value == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -1621,7 +1644,11 @@ int RM_UnlinkKey(RedisModuleKey *key) {
|
||||
* If no TTL is associated with the key or if the key is empty,
|
||||
* REDISMODULE_NO_EXPIRE is returned. */
|
||||
mstime_t RM_GetExpire(RedisModuleKey *key) {
|
||||
mstime_t expire = getExpire(key->db,key->key);
|
||||
expireEntry *pexpire = getExpire(key->db,key->key);
|
||||
mstime_t expire = -1;
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
|
||||
if (expire == -1 || key->value == NULL) return -1;
|
||||
expire -= mstime();
|
||||
return expire >= 0 ? expire : 0;
|
||||
@@ -1641,7 +1668,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
|
||||
return REDISMODULE_ERR;
|
||||
if (expire != REDISMODULE_NO_EXPIRE) {
|
||||
expire += mstime();
|
||||
setExpire(key->ctx->client,key->db,key->key,expire);
|
||||
setExpire(key->ctx->client,key->db,key->key,nullptr,expire);
|
||||
} else {
|
||||
removeExpire(key->db,key->key);
|
||||
}
|
||||
@@ -2750,7 +2777,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
int replicate = 0; /* Replicate this command? */
|
||||
int call_flags;
|
||||
sds proto = nullptr;
|
||||
|
||||
|
||||
/* Create the client and dispatch the command. */
|
||||
va_start(ap, fmt);
|
||||
c = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
@@ -2792,12 +2819,12 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
/* If this is a Redis Cluster node, we need to make sure the module is not
|
||||
* trying to access non-local keys, with the exception of commands
|
||||
* received from our master. */
|
||||
if (server.cluster_enabled && !(ctx->client->flags & CLIENT_MASTER)) {
|
||||
if (g_pserver->cluster_enabled && !(ctx->client->flags & CLIENT_MASTER)) {
|
||||
/* Duplicate relevant flags in the module client. */
|
||||
c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
|
||||
c->flags |= ctx->client->flags & (CLIENT_READONLY|CLIENT_ASKING);
|
||||
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
|
||||
server.cluster->myself)
|
||||
g_pserver->cluster->myself)
|
||||
{
|
||||
errno = EPERM;
|
||||
goto cleanup;
|
||||
@@ -3507,7 +3534,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;
|
||||
if (level < cserver.verbosity) return;
|
||||
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
|
||||
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
|
||||
@@ -3663,9 +3690,22 @@ int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
bc->privdata = privdata;
|
||||
listAddNodeTail(moduleUnblockedClients,bc);
|
||||
if (write(server.module_blocked_pipe[1],"A",1) != 1) {
|
||||
/* Ignore the error, this is best-effort. */
|
||||
if (bc->client != nullptr)
|
||||
{
|
||||
if (write(g_pserver->rgthreadvar[bc->client->iel].module_blocked_pipe[1],"A",1) != 1) {
|
||||
/* Ignore the error, this is best-effort. */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
{
|
||||
if (write(g_pserver->rgthreadvar[iel].module_blocked_pipe[1],"A",1) != 1) {
|
||||
/* Ignore the error, this is best-effort. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -3706,8 +3746,7 @@ void RM_SetDisconnectCallback(RedisModuleBlockedClient *bc, RedisModuleDisconnec
|
||||
* blocked client, it was terminated by Redis (for timeout or other reasons).
|
||||
* When this happens the RedisModuleBlockedClient structure in the queue
|
||||
* will have the 'client' field set to NULL. */
|
||||
void moduleHandleBlockedClients(void) {
|
||||
listNode *ln;
|
||||
void moduleHandleBlockedClients(int iel) {
|
||||
RedisModuleBlockedClient *bc;
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
@@ -3715,12 +3754,16 @@ void moduleHandleBlockedClients(void) {
|
||||
/* Here we unblock all the pending clients blocked in modules operations
|
||||
* so we can read every pending "awake byte" in the pipe. */
|
||||
char buf[1];
|
||||
while (read(server.module_blocked_pipe[0],buf,1) == 1);
|
||||
while (listLength(moduleUnblockedClients)) {
|
||||
ln = listFirst(moduleUnblockedClients);
|
||||
while (read(serverTL->module_blocked_pipe[0],buf,1) == 1);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleUnblockedClients, &li);
|
||||
while ((ln = listNext(&li))) {
|
||||
bc = (RedisModuleBlockedClient*)ln->value;
|
||||
client *c = bc->client;
|
||||
serverAssert(c->iel == IDX_EVENT_LOOP_MAIN);
|
||||
if ((c != nullptr) && (iel != c->iel))
|
||||
continue;
|
||||
|
||||
listDelNode(moduleUnblockedClients,ln);
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
|
||||
@@ -3763,14 +3806,7 @@ 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) {
|
||||
if (bc->reply_client->bufpos)
|
||||
addReplyProto(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;
|
||||
}
|
||||
if (c) AddReplyFromClient(c, bc->reply_client);
|
||||
freeClient(bc->reply_client);
|
||||
|
||||
if (c != NULL) {
|
||||
@@ -3788,9 +3824,9 @@ void moduleHandleBlockedClients(void) {
|
||||
c->flags |= CLIENT_PENDING_WRITE;
|
||||
AssertCorrectThread(c);
|
||||
|
||||
fastlock_lock(&server.rgthreadvar[c->iel].lockPendingWrite);
|
||||
listAddNodeHead(server.rgthreadvar[c->iel].clients_pending_write,c);
|
||||
fastlock_unlock(&server.rgthreadvar[c->iel].lockPendingWrite);
|
||||
fastlock_lock(&g_pserver->rgthreadvar[c->iel].lockPendingWrite);
|
||||
g_pserver->rgthreadvar[c->iel].clients_pending_write.push_back(c);
|
||||
fastlock_unlock(&g_pserver->rgthreadvar[c->iel].lockPendingWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3894,13 +3930,18 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
|
||||
* in order to keep things like the currently selected database and similar
|
||||
* things. */
|
||||
ctx->client = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
if (bc) selectDb(ctx->client,bc->dbid);
|
||||
if (bc) {
|
||||
selectDb(ctx->client,bc->dbid);
|
||||
ctx->client->id = bc->client->id;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/* Release a thread safe context. */
|
||||
void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
|
||||
moduleAcquireGIL(false /*fServerThread*/);
|
||||
moduleFreeContext(ctx);
|
||||
moduleReleaseGIL(false /*fServerThread*/);
|
||||
zfree(ctx);
|
||||
}
|
||||
|
||||
@@ -3910,6 +3951,8 @@ void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
|
||||
void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) {
|
||||
UNUSED(ctx);
|
||||
moduleAcquireGIL(FALSE /*fServerThread*/);
|
||||
if (serverTL == nullptr)
|
||||
serverTL = &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN]; // arbitrary module threads get the main thread context
|
||||
}
|
||||
|
||||
/* Release the server lock after a thread safe API call was executed. */
|
||||
@@ -3919,23 +3962,36 @@ void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) {
|
||||
}
|
||||
|
||||
void moduleAcquireGIL(int fServerThread) {
|
||||
std::unique_lock<std::mutex> lock(s_mutex);
|
||||
int *pcheck = fServerThread ? &s_cAcquisitionsModule : &s_cAcquisitionsServer;
|
||||
|
||||
while (*pcheck > 0)
|
||||
s_cv.wait(lock);
|
||||
|
||||
if (fServerThread)
|
||||
{
|
||||
pthread_rwlock_rdlock(&moduleGIL);
|
||||
++s_cAcquisitionsServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_rwlock_wrlock(&moduleGIL);
|
||||
fModuleGILWlocked = TRUE;
|
||||
++s_cAcquisitionsModule;
|
||||
fModuleGILWlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
void moduleReleaseGIL(int fServerThread) {
|
||||
pthread_rwlock_unlock(&moduleGIL);
|
||||
if (!fServerThread)
|
||||
std::unique_lock<std::mutex> lock(s_mutex);
|
||||
|
||||
if (fServerThread)
|
||||
{
|
||||
fModuleGILWlocked = FALSE;
|
||||
--s_cAcquisitionsServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
--s_cAcquisitionsModule;
|
||||
fModuleGILWlocked--;
|
||||
}
|
||||
s_cv.notify_all();
|
||||
}
|
||||
|
||||
int moduleGILAcquiredByModule(void) {
|
||||
@@ -3987,7 +4043,7 @@ int moduleGILAcquiredByModule(void) {
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occurred as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable notifications in redis.conf for
|
||||
* Notice that it is not necessary to enable notifications in keydb.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
@@ -4107,7 +4163,7 @@ void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8
|
||||
* is already a callback for this function, the callback is unregistered
|
||||
* (so this API call is also used in order to delete the receiver). */
|
||||
void RM_RegisterClusterMessageReceiver(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) {
|
||||
if (!server.cluster_enabled) return;
|
||||
if (!g_pserver->cluster_enabled) return;
|
||||
|
||||
uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0);
|
||||
moduleClusterReceiver *r = clusterReceivers[type], *prev = NULL;
|
||||
@@ -4151,7 +4207,7 @@ void RM_RegisterClusterMessageReceiver(RedisModuleCtx *ctx, uint8_t type, RedisM
|
||||
* otherwise if the node is not connected or such node ID does not map to any
|
||||
* known cluster node, REDISMODULE_ERR is returned. */
|
||||
int RM_SendClusterMessage(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) {
|
||||
if (!server.cluster_enabled) return REDISMODULE_ERR;
|
||||
if (!g_pserver->cluster_enabled) return REDISMODULE_ERR;
|
||||
uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0);
|
||||
if (clusterSendModuleMessageToTarget(target_id,module_id,type,msg,len) == C_OK)
|
||||
return REDISMODULE_OK;
|
||||
@@ -4184,10 +4240,10 @@ int RM_SendClusterMessage(RedisModuleCtx *ctx, char *target_id, uint8_t type, un
|
||||
char **RM_GetClusterNodesList(RedisModuleCtx *ctx, size_t *numnodes) {
|
||||
UNUSED(ctx);
|
||||
|
||||
if (!server.cluster_enabled) return NULL;
|
||||
size_t count = dictSize(server.cluster->nodes);
|
||||
if (!g_pserver->cluster_enabled) return NULL;
|
||||
size_t count = dictSize(g_pserver->cluster->nodes);
|
||||
char **ids = (char**)zmalloc((count+1)*REDISMODULE_NODE_ID_LEN, MALLOC_LOCAL);
|
||||
dictIterator *di = dictGetIterator(server.cluster->nodes);
|
||||
dictIterator *di = dictGetIterator(g_pserver->cluster->nodes);
|
||||
dictEntry *de;
|
||||
int j = 0;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
@@ -4214,8 +4270,8 @@ void RM_FreeClusterNodesList(char **ids) {
|
||||
/* Return this node ID (REDISMODULE_CLUSTER_ID_LEN bytes) or NULL if the cluster
|
||||
* is disabled. */
|
||||
const char *RM_GetMyClusterID(void) {
|
||||
if (!server.cluster_enabled) return NULL;
|
||||
return server.cluster->myself->name;
|
||||
if (!g_pserver->cluster_enabled) return NULL;
|
||||
return g_pserver->cluster->myself->name;
|
||||
}
|
||||
|
||||
/* Return the number of nodes in the cluster, regardless of their state
|
||||
@@ -4223,8 +4279,8 @@ const char *RM_GetMyClusterID(void) {
|
||||
* be smaller, but not greater than this number. If the instance is not in
|
||||
* cluster mode, zero is returned. */
|
||||
size_t RM_GetClusterSize(void) {
|
||||
if (!server.cluster_enabled) return 0;
|
||||
return dictSize(server.cluster->nodes);
|
||||
if (!g_pserver->cluster_enabled) return 0;
|
||||
return dictSize(g_pserver->cluster->nodes);
|
||||
}
|
||||
|
||||
/* Populate the specified info for the node having as ID the specified 'id',
|
||||
@@ -4233,7 +4289,7 @@ size_t RM_GetClusterSize(void) {
|
||||
*
|
||||
* The arguments ip, master_id, port and flags can be NULL in case we don't
|
||||
* need to populate back certain info. If an ip and master_id (only populated
|
||||
* if the instance is a slave) are specified, they point to buffers holding
|
||||
* if the instance is a replica) are specified, they point to buffers holding
|
||||
* at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as ip
|
||||
* and master_id are not null terminated.
|
||||
*
|
||||
@@ -4244,7 +4300,7 @@ size_t RM_GetClusterSize(void) {
|
||||
* * REDISMODULE_NODE_SLAVE The node is a replica
|
||||
* * REDISMODULE_NODE_PFAIL We see the node as failing
|
||||
* * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
|
||||
* * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
|
||||
* * REDISMODULE_NODE_NOFAILOVER The replica is configured to never failover
|
||||
*/
|
||||
|
||||
clusterNode *clusterLookupNode(const char *name); /* We need access to internals */
|
||||
@@ -4304,9 +4360,9 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
void RM_SetClusterFlags(RedisModuleCtx *ctx, uint64_t flags) {
|
||||
UNUSED(ctx);
|
||||
if (flags & REDISMODULE_CLUSTER_FLAG_NO_FAILOVER)
|
||||
server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_FAILOVER;
|
||||
g_pserver->cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_FAILOVER;
|
||||
if (flags & REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION)
|
||||
server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_REDIRECTION;
|
||||
g_pserver->cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_REDIRECTION;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
@@ -4415,7 +4471,7 @@ RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisMod
|
||||
if (memcmp(ri.key,&key,sizeof(key)) == 0) {
|
||||
/* This is the first key, we need to re-install the timer according
|
||||
* to the just added event. */
|
||||
aeDeleteTimeEvent(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,aeTimer);
|
||||
aeDeleteTimeEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,aeTimer);
|
||||
aeTimer = -1;
|
||||
}
|
||||
raxStop(&ri);
|
||||
@@ -4424,7 +4480,7 @@ RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisMod
|
||||
/* If we have no main timer (the old one was invalidated, or this is the
|
||||
* first module timer we have), install one. */
|
||||
if (aeTimer == -1)
|
||||
aeTimer = aeCreateTimeEvent(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,period,moduleTimerHandler,NULL,NULL);
|
||||
aeTimer = aeCreateTimeEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,period,moduleTimerHandler,NULL,NULL);
|
||||
|
||||
return key;
|
||||
}
|
||||
@@ -4752,7 +4808,7 @@ int RM_ExportSharedAPI(RedisModuleCtx *ctx, const char *apiname, void *func) {
|
||||
RedisModuleSharedAPI *sapi = (RedisModuleSharedAPI*)zmalloc(sizeof(*sapi), MALLOC_LOCAL);
|
||||
sapi->module = ctx->module;
|
||||
sapi->func = func;
|
||||
if (dictAdd(server.sharedapi, (char*)apiname, sapi) != DICT_OK) {
|
||||
if (dictAdd(g_pserver->sharedapi, (char*)apiname, sapi) != DICT_OK) {
|
||||
zfree(sapi);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
@@ -4793,7 +4849,7 @@ int RM_ExportSharedAPI(RedisModuleCtx *ctx, const char *apiname, void *func) {
|
||||
* }
|
||||
*/
|
||||
void *RM_GetSharedAPI(RedisModuleCtx *ctx, const char *apiname) {
|
||||
dictEntry *de = dictFind(server.sharedapi, apiname);
|
||||
dictEntry *de = dictFind(g_pserver->sharedapi, apiname);
|
||||
if (de == NULL) return NULL;
|
||||
RedisModuleSharedAPI *sapi = (RedisModuleSharedAPI*)dictGetVal(de);
|
||||
if (listSearchKey(sapi->module->usedby,ctx->module) == NULL) {
|
||||
@@ -4811,13 +4867,13 @@ void *RM_GetSharedAPI(RedisModuleCtx *ctx, const char *apiname) {
|
||||
* The number of unregistered APIs is returned. */
|
||||
int moduleUnregisterSharedAPI(RedisModule *module) {
|
||||
int count = 0;
|
||||
dictIterator *di = dictGetSafeIterator(server.sharedapi);
|
||||
dictIterator *di = dictGetSafeIterator(g_pserver->sharedapi);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
const char *apiname = (const char*)dictGetKey(de);
|
||||
RedisModuleSharedAPI *sapi = (RedisModuleSharedAPI*)dictGetVal(de);
|
||||
if (sapi->module == module) {
|
||||
dictDelete(server.sharedapi,apiname);
|
||||
dictDelete(g_pserver->sharedapi,apiname);
|
||||
zfree(sapi);
|
||||
count++;
|
||||
}
|
||||
@@ -4955,6 +5011,12 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int moduleHasCommandFilters()
|
||||
{
|
||||
// Note: called outside the global lock
|
||||
return listLength(moduleCommandFilters);
|
||||
}
|
||||
|
||||
void moduleCallCommandFilters(client *c) {
|
||||
if (listLength(moduleCommandFilters) == 0) return;
|
||||
|
||||
@@ -5056,7 +5118,7 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* server.moduleapi dictionary type. Only uses plain C strings since
|
||||
/* g_pserver->moduleapi dictionary type. Only uses plain C strings since
|
||||
* this gets queries from modules. */
|
||||
|
||||
uint64_t dictCStringKeyHash(const void *key) {
|
||||
@@ -5077,8 +5139,8 @@ dictType moduleAPIDictType = {
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
int moduleRegisterApi(const char *funcname, void *funcptr) {
|
||||
return dictAdd(server.moduleapi, (char*)funcname, funcptr);
|
||||
extern "C" int moduleRegisterApi(const char *funcname, void *funcptr) {
|
||||
return dictAdd(g_pserver->moduleapi, (char*)funcname, funcptr);
|
||||
}
|
||||
|
||||
#define REGISTER_API(name) \
|
||||
@@ -5089,7 +5151,7 @@ void moduleRegisterCoreAPI(void);
|
||||
|
||||
void moduleInitModulesSystem(void) {
|
||||
moduleUnblockedClients = listCreate();
|
||||
server.loadmodule_queue = listCreate();
|
||||
g_pserver->loadmodule_queue = listCreate();
|
||||
modules = dictCreate(&modulesDictType,NULL);
|
||||
|
||||
/* Set up the keyspace notification susbscriber list and static client */
|
||||
@@ -5102,27 +5164,16 @@ void moduleInitModulesSystem(void) {
|
||||
moduleCommandFilters = listCreate();
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't create the pipe for module blocking commands: %s",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
/* Make the pipe non blocking. This is just a best effort aware mechanism
|
||||
* and we do not want to block not in the read nor in the write half. */
|
||||
anetNonBlock(NULL,server.module_blocked_pipe[0]);
|
||||
anetNonBlock(NULL,server.module_blocked_pipe[1]);
|
||||
|
||||
/* Create the timers radix tree. */
|
||||
Timers = raxNew();
|
||||
|
||||
/* Our thread-safe contexts GIL must start with already locked:
|
||||
* it is just unlocked when it's safe. */
|
||||
pthread_rwlock_init(&moduleGIL, NULL);
|
||||
pthread_rwlock_rdlock(&moduleGIL);
|
||||
moduleAcquireGIL(true);
|
||||
}
|
||||
|
||||
/* Load all the modules in the server.loadmodule_queue list, which is
|
||||
/* Load all the modules in the g_pserver->loadmodule_queue list, which is
|
||||
* populated by `loadmodule` directives in the configuration file.
|
||||
* We can't load modules directly when processing the configuration file
|
||||
* because the server must be fully initialized before loading modules.
|
||||
@@ -5130,12 +5181,12 @@ void moduleInitModulesSystem(void) {
|
||||
* The function aborts the server on errors, since to start with missing
|
||||
* modules is not considered sane: clients may rely on the existence of
|
||||
* given commands, loading AOF also may need some modules to exist, and
|
||||
* if this instance is a slave, it must understand commands from master. */
|
||||
* if this instance is a replica, it must understand commands from master. */
|
||||
void moduleLoadFromQueue(void) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.loadmodule_queue,&li);
|
||||
listRewind(g_pserver->loadmodule_queue,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
struct moduleLoadQueueEntry *loadmod = (moduleLoadQueueEntry*)ln->value;
|
||||
if (moduleLoad(loadmod->path,(void **)loadmod->argv,loadmod->argc)
|
||||
@@ -5158,7 +5209,7 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
|
||||
void moduleUnregisterCommands(struct RedisModule *module) {
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictIterator *di = dictGetSafeIterator(g_pserver->commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = (redisCommand*)dictGetVal(de);
|
||||
@@ -5167,8 +5218,8 @@ void moduleUnregisterCommands(struct RedisModule *module) {
|
||||
(RedisModuleCommandProxy*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = (sds)cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
dictDelete(g_pserver->commands,cmdname);
|
||||
dictDelete(g_pserver->orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
@@ -5184,6 +5235,15 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
int (*onload)(void *, void **, int);
|
||||
void *handle;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0)
|
||||
{ // this check is best effort
|
||||
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
|
||||
serverLog(LL_WARNING, "Module %s failed to load: It does not have execute permissions.", path);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
handle = dlopen(path,RTLD_NOW|RTLD_LOCAL);
|
||||
if (handle == NULL) {
|
||||
@@ -5353,8 +5413,8 @@ size_t moduleCount(void) {
|
||||
/* Register all the APIs we export. Keep this function at the end of the
|
||||
* file so that's easy to seek it to add new entries. */
|
||||
void moduleRegisterCoreAPI(void) {
|
||||
server.moduleapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
server.sharedapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
g_pserver->moduleapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
g_pserver->sharedapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
REGISTER_API(Alloc);
|
||||
REGISTER_API(Calloc);
|
||||
REGISTER_API(Realloc);
|
||||
|
||||
@@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellofilter.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -47,11 +47,6 @@ hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
|
||||
hellofilter.xo: ../redismodule.h
|
||||
|
||||
hellofilter.so: hellofilter.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/* Timer callback. */
|
||||
void timerHandler(RedisModuleCtx *ctx, void *data) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
printf("Fired %s!\n", data);
|
||||
printf("Fired %s!\n", (const char *) data);
|
||||
RedisModule_Free(data);
|
||||
}
|
||||
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
#include "sds.h"
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "motd.h"
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Message of the day
|
||||
*--------------------------------------------------------------------------- */
|
||||
#ifdef MOTD
|
||||
#include <curl/curl.h>
|
||||
|
||||
static const char *szMotdCachePath()
|
||||
{
|
||||
static sds sdsMotdCachePath = NULL;
|
||||
if (sdsMotdCachePath != NULL)
|
||||
return sdsMotdCachePath;
|
||||
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw == NULL)
|
||||
return "";
|
||||
const char *homedir = pw->pw_dir;
|
||||
sdsMotdCachePath = sdsnew(homedir);
|
||||
sdsMotdCachePath = sdscat(sdsMotdCachePath, motd_cache_file);
|
||||
return sdsMotdCachePath;
|
||||
}
|
||||
static size_t motd_write_callback(void *ptr, size_t size, size_t nmemb, sds *str)
|
||||
{
|
||||
*str = sdscatlen(*str, ptr, size*nmemb);
|
||||
return (size*nmemb);
|
||||
}
|
||||
|
||||
static char *fetchMOTDFromCache()
|
||||
{
|
||||
struct stat attrib;
|
||||
if (stat(szMotdCachePath(), &attrib) != 0)
|
||||
return NULL;
|
||||
time_t t = attrib.st_mtim.tv_sec;
|
||||
time_t now = time(NULL);
|
||||
if ((now - t) < 14400)
|
||||
{
|
||||
// If our cache was updated no more than 4 hours ago use it instead of fetching the MOTD
|
||||
FILE *pf = fopen(szMotdCachePath(), "rb");
|
||||
if (pf == NULL)
|
||||
return NULL;
|
||||
fseek(pf, 0L, SEEK_END);
|
||||
long cb = ftell(pf);
|
||||
fseek(pf, 0L, SEEK_SET); // rewind
|
||||
sds str = sdsnewlen(NULL, cb);
|
||||
size_t cbRead = fread(str, 1, cb, pf);
|
||||
fclose(pf);
|
||||
if ((long)cbRead != cb)
|
||||
{
|
||||
sdsfree(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void setMOTDCache(const char *sz)
|
||||
{
|
||||
FILE *pf = fopen(szMotdCachePath(), "wb");
|
||||
if (pf == NULL)
|
||||
return;
|
||||
size_t celem = fwrite(sz, strlen(sz), 1, pf);
|
||||
(void)celem; // best effort
|
||||
fclose(pf);
|
||||
}
|
||||
|
||||
extern "C" char *fetchMOTD(int cache)
|
||||
{
|
||||
sds str;
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
|
||||
/* First try and get the string from the cache */
|
||||
if (cache) {
|
||||
str = fetchMOTDFromCache();
|
||||
if (str != NULL)
|
||||
return str;
|
||||
}
|
||||
|
||||
str = sdsnew("");
|
||||
curl = curl_easy_init();
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, motd_url);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // follow redirects
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2); // take no more than two seconds
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, motd_write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
|
||||
|
||||
/* Perform the request, res will get the return code */
|
||||
res = curl_easy_perform(curl);
|
||||
/* Check for errors */
|
||||
if(res != CURLE_OK)
|
||||
{
|
||||
sdsfree(str);
|
||||
str = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
long response_code;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
if ((response_code / 100) != 2)
|
||||
{
|
||||
// An error code not in the 200s implies an error
|
||||
sdsfree(str);
|
||||
str = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* always cleanup */
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (str != NULL && cache)
|
||||
setMOTDCache(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
extern "C" char *fetchMOTD(int /* cache */)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
extern const char *motd_url;
|
||||
extern const char *motd_cache_file;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char *fetchMOTD(int fCache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+17
-14
@@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
bool FInReplicaReplay();
|
||||
|
||||
/* ================================ MULTI/EXEC ============================== */
|
||||
|
||||
@@ -82,7 +83,6 @@ void discardTransaction(client *c) {
|
||||
/* Flag the transacation as DIRTY_EXEC so that EXEC will fail.
|
||||
* Should be called every time there is an error while queueing a command. */
|
||||
void flagTransaction(client *c) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (c->flags & CLIENT_MULTI)
|
||||
c->flags |= CLIENT_DIRTY_EXEC;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ void discardCommand(client *c) {
|
||||
void execCommandPropagateMulti(client *c) {
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
|
||||
propagate(server.multiCommand,c->db->id,&multistring,1,
|
||||
propagate(cserver.multiCommand,c->db->id,&multistring,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ void execCommand(client *c) {
|
||||
int orig_argc;
|
||||
struct redisCommand *orig_cmd;
|
||||
int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
|
||||
int was_master = listLength(server.masters) == 0;
|
||||
int was_master = listLength(g_pserver->masters) == 0;
|
||||
|
||||
if (!(c->flags & CLIENT_MULTI)) {
|
||||
addReplyError(c,"EXEC without MULTI");
|
||||
@@ -143,11 +143,11 @@ void execCommand(client *c) {
|
||||
}
|
||||
|
||||
/* If there are write commands inside the transaction, and this is a read
|
||||
* only slave, we want to send an error. This happens when the transaction
|
||||
* only replica, we want to send an error. This happens when the transaction
|
||||
* was initiated when the instance was a master or a writable replica and
|
||||
* then the configuration changed (for example instance was turned into
|
||||
* a replica). */
|
||||
if (!server.loading && listLength(server.masters) && server.repl_slave_ro &&
|
||||
if (!g_pserver->loading && listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
|
||||
{
|
||||
addReplyError(c,
|
||||
@@ -173,12 +173,15 @@ void execCommand(client *c) {
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)) && !(FInReplicaReplay())) {
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
int flags = g_pserver->loading ? CMD_CALL_NONE : CMD_CALL_FULL;
|
||||
if (FInReplicaReplay())
|
||||
flags &= ~CMD_CALL_PROPAGATE;
|
||||
call(c,flags);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
@@ -193,14 +196,14 @@ void execCommand(client *c) {
|
||||
/* Make sure the EXEC command will be propagated as well if MULTI
|
||||
* was already propagated. */
|
||||
if (must_propagate) {
|
||||
int is_master = listLength(server.masters) == 0;
|
||||
server.dirty++;
|
||||
int is_master = listLength(g_pserver->masters) == 0;
|
||||
g_pserver->dirty++;
|
||||
/* If inside the MULTI/EXEC block this instance was suddenly
|
||||
* switched from master to slave (using the SLAVEOF command), the
|
||||
* switched from master to replica (using the SLAVEOF command), the
|
||||
* initial MULTI was propagated into the replication backlog, but the
|
||||
* rest was not. We need to make sure to at least terminate the
|
||||
* backlog with the final EXEC. */
|
||||
if (server.repl_backlog && was_master && !is_master) {
|
||||
if (g_pserver->repl_backlog && was_master && !is_master) {
|
||||
const char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
|
||||
feedReplicationBacklog(execcmd,strlen(execcmd));
|
||||
}
|
||||
@@ -212,8 +215,8 @@ handle_monitor:
|
||||
* MUTLI, EXEC, ... commands inside transaction ...
|
||||
* Instead EXEC is flagged as CMD_SKIP_MONITOR in the command
|
||||
* table, and we do it here with correct ordering. */
|
||||
if (listLength(server.monitors) && !server.loading)
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
if (listLength(g_pserver->monitors) && !g_pserver->loading)
|
||||
replicationFeedMonitors(c,g_pserver->monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
/* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
|
||||
@@ -323,7 +326,7 @@ void touchWatchedKeysOnFlush(int dbid) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
/* For every client, check all the waited keys */
|
||||
listRewind(server.clients,&li1);
|
||||
listRewind(g_pserver->clients,&li1);
|
||||
while((ln = listNext(&li1))) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
listRewind(c->watched_keys,&li2);
|
||||
|
||||
+577
-322
File diff suppressed because it is too large.
Load diff
+34
@@ -0,0 +1,34 @@
|
||||
#include <cstddef> // std::size_t
|
||||
#include "server.h"
|
||||
#include "new.h"
|
||||
|
||||
#ifdef SANITIZE
|
||||
void *operator new(size_t size, enum MALLOC_CLASS mclass)
|
||||
{
|
||||
(void)mclass;
|
||||
return ::operator new(size);
|
||||
}
|
||||
|
||||
#else
|
||||
[[deprecated]]
|
||||
void *operator new(size_t size)
|
||||
{
|
||||
return zmalloc(size, MALLOC_LOCAL);
|
||||
}
|
||||
|
||||
void *operator new(size_t size, enum MALLOC_CLASS mclass)
|
||||
{
|
||||
return zmalloc(size, mclass);
|
||||
}
|
||||
|
||||
void operator delete(void * p) noexcept
|
||||
{
|
||||
zfree(p);
|
||||
}
|
||||
|
||||
void operator delete(void *p, std::size_t) noexcept
|
||||
{
|
||||
zfree(p);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
void *operator new(size_t size, enum MALLOC_CLASS mclass);
|
||||
|
||||
#ifndef SANITIZE
|
||||
[[deprecated]]
|
||||
void *operator new(size_t size);
|
||||
|
||||
void operator delete(void * p) noexcept;
|
||||
void operator delete(void *p, std::size_t) noexcept;
|
||||
#endif
|
||||
+3
-3
@@ -109,12 +109,12 @@ void notifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
if (!(g_pserver->notify_keyspace_events & type)) return;
|
||||
|
||||
eventobj = createStringObject(event,strlen(event));
|
||||
|
||||
/* __keyspace@<db>__:<key> <event> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYSPACE) {
|
||||
if (g_pserver->notify_keyspace_events & NOTIFY_KEYSPACE) {
|
||||
chan = sdsnewlen("__keyspace@",11);
|
||||
len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
@@ -126,7 +126,7 @@ void notifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
|
||||
}
|
||||
|
||||
/* __keyevent@<db>__:<event> <key> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
|
||||
if (g_pserver->notify_keyspace_events & NOTIFY_KEYEVENT) {
|
||||
chan = sdsnewlen("__keyevent@",11);
|
||||
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
|
||||
+136
-78
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -39,15 +40,16 @@
|
||||
/* ===================== Creation and parsing of objects ==================== */
|
||||
|
||||
robj *createObject(int type, void *ptr) {
|
||||
robj *o = (robj*)zmalloc(sizeof(*o), MALLOC_SHARED);
|
||||
robj *o = (robj*)zcalloc(sizeof(*o), MALLOC_SHARED);
|
||||
o->type = type;
|
||||
o->encoding = OBJ_ENCODING_RAW;
|
||||
o->m_ptr = ptr;
|
||||
o->refcount = 1;
|
||||
o->setrefcount(1);
|
||||
o->mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
|
||||
/* Set the LRU to the current lruclock (minutes resolution), or
|
||||
* alternatively the LFU counter. */
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||
} else {
|
||||
o->lru = LRU_CLOCK();
|
||||
@@ -67,8 +69,9 @@ robj *createObject(int type, void *ptr) {
|
||||
*
|
||||
*/
|
||||
robj *makeObjectShared(robj *o) {
|
||||
serverAssert(o->refcount == 1);
|
||||
o->refcount = OBJ_SHARED_REFCOUNT;
|
||||
serverAssert(o->getrefcount(std::memory_order_relaxed) == 1);
|
||||
serverAssert(!o->FExpires());
|
||||
o->setrefcount(OBJ_SHARED_REFCOUNT);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -85,13 +88,15 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
|
||||
size_t allocsize = sizeof(struct sdshdr8)+len+1;
|
||||
if (allocsize < sizeof(void*))
|
||||
allocsize = sizeof(void*);
|
||||
robj *o = (robj*)zmalloc(sizeof(robj)+allocsize-sizeof(o->m_ptr), MALLOC_SHARED);
|
||||
robj *o = (robj*)zcalloc(sizeof(robj)+allocsize-sizeof(o->m_ptr), MALLOC_SHARED);
|
||||
struct sdshdr8 *sh = (sdshdr8*)(&o->m_ptr);
|
||||
|
||||
o->type = OBJ_STRING;
|
||||
o->encoding = OBJ_ENCODING_EMBSTR;
|
||||
o->refcount = 1;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
o->setrefcount(1);
|
||||
o->mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||
} else {
|
||||
o->lru = LRU_CLOCK();
|
||||
@@ -135,8 +140,8 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
|
||||
robj *o;
|
||||
|
||||
if (server.maxmemory == 0 ||
|
||||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
|
||||
if (g_pserver->maxmemory == 0 ||
|
||||
!(g_pserver->maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
|
||||
{
|
||||
/* If the maxmemory policy permits, we can still return shared integers
|
||||
* even if valueobj is true. */
|
||||
@@ -280,13 +285,13 @@ robj *createModuleObject(moduleType *mt, void *value) {
|
||||
return createObject(OBJ_MODULE,mv);
|
||||
}
|
||||
|
||||
void freeStringObject(robj *o) {
|
||||
void freeStringObject(robj_roptr o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW) {
|
||||
sdsfree(szFromObj(o));
|
||||
}
|
||||
}
|
||||
|
||||
void freeListObject(robj *o) {
|
||||
void freeListObject(robj_roptr o) {
|
||||
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
quicklistRelease((quicklist*)ptrFromObj(o));
|
||||
} else {
|
||||
@@ -294,7 +299,7 @@ void freeListObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
void freeSetObject(robj *o) {
|
||||
void freeSetObject(robj_roptr o) {
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_HT:
|
||||
dictRelease((dict*) ptrFromObj(o));
|
||||
@@ -307,7 +312,7 @@ void freeSetObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
void freeZsetObject(robj *o) {
|
||||
void freeZsetObject(robj_roptr o) {
|
||||
zset *zs;
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_SKIPLIST:
|
||||
@@ -324,7 +329,7 @@ void freeZsetObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
void freeHashObject(robj *o) {
|
||||
void freeHashObject(robj_roptr o) {
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_HT:
|
||||
dictRelease((dict*) ptrFromObj(o));
|
||||
@@ -338,22 +343,25 @@ void freeHashObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
void freeModuleObject(robj *o) {
|
||||
void freeModuleObject(robj_roptr o) {
|
||||
moduleValue *mv = (moduleValue*)ptrFromObj(o);
|
||||
mv->type->free(mv->value);
|
||||
zfree(mv);
|
||||
}
|
||||
|
||||
void freeStreamObject(robj *o) {
|
||||
void freeStreamObject(robj_roptr o) {
|
||||
freeStream((stream*)ptrFromObj(o));
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
|
||||
void incrRefCount(robj_roptr o) {
|
||||
if (o->getrefcount(std::memory_order_relaxed) != OBJ_SHARED_REFCOUNT) o->addref();
|
||||
}
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
if (o->refcount == 1) {
|
||||
void decrRefCount(robj_roptr o) {
|
||||
if (o->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
return;
|
||||
unsigned prev = o->release();
|
||||
if (prev == 1) {
|
||||
switch(o->type) {
|
||||
case OBJ_STRING: freeStringObject(o); break;
|
||||
case OBJ_LIST: freeListObject(o); break;
|
||||
@@ -362,19 +370,19 @@ void decrRefCount(robj *o) {
|
||||
case OBJ_HASH: freeHashObject(o); break;
|
||||
case OBJ_MODULE: freeModuleObject(o); break;
|
||||
case OBJ_STREAM: freeStreamObject(o); break;
|
||||
case OBJ_CRON: freeCronObject(o); break;
|
||||
default: serverPanic("Unknown object type"); break;
|
||||
}
|
||||
zfree(o);
|
||||
zfree(o.unsafe_robjcast());
|
||||
} else {
|
||||
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
|
||||
if (prev <= 0) serverPanic("decrRefCount against refcount <= 0");
|
||||
}
|
||||
}
|
||||
|
||||
/* This variant of decrRefCount() gets its argument as void, and is useful
|
||||
* as free method in data structures that expect a 'void free_object(void*)'
|
||||
* prototype for the free method. */
|
||||
void decrRefCountVoid(void *o) {
|
||||
void decrRefCountVoid(const void *o) {
|
||||
decrRefCount((robj*)o);
|
||||
}
|
||||
|
||||
@@ -391,11 +399,11 @@ void decrRefCountVoid(void *o) {
|
||||
* decrRefCount(obj);
|
||||
*/
|
||||
robj *resetRefCount(robj *obj) {
|
||||
obj->refcount = 0;
|
||||
obj->setrefcount(0);
|
||||
return obj;
|
||||
}
|
||||
|
||||
int checkType(client *c, robj *o, int type) {
|
||||
int checkType(client *c, robj_roptr o, int type) {
|
||||
if (o->type != type) {
|
||||
addReplyAsync(c,shared.wrongtypeerr);
|
||||
return 1;
|
||||
@@ -403,7 +411,7 @@ int checkType(client *c, robj *o, int type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isSdsRepresentableAsLongLong(sds s, long long *llval) {
|
||||
int isSdsRepresentableAsLongLong(const char *s, long long *llval) {
|
||||
return string2ll(s,sdslen(s),llval) ? C_OK : C_ERR;
|
||||
}
|
||||
|
||||
@@ -449,7 +457,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
/* It's not safe to encode shared objects: shared objects can be shared
|
||||
* everywhere in the "object space" of Redis and may end in places where
|
||||
* they are not handled. We handle them only as values in the keyspace. */
|
||||
if (o->refcount > 1) return o;
|
||||
if (o->getrefcount(std::memory_order_relaxed) > 1) return o;
|
||||
|
||||
/* Check if we can represent this string as a long integer.
|
||||
* Note that we are sure that a string larger than 20 chars is not
|
||||
@@ -460,8 +468,8 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* Note that we avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if ((server.maxmemory == 0 ||
|
||||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
|
||||
if ((g_pserver->maxmemory == 0 ||
|
||||
!(g_pserver->maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
|
||||
value >= 0 &&
|
||||
value < OBJ_SHARED_INTEGERS)
|
||||
{
|
||||
@@ -524,6 +532,10 @@ robj *getDecodedObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
robj_roptr getDecodedObject(robj_roptr o) {
|
||||
return getDecodedObject(o.unsafe_robjcast());
|
||||
}
|
||||
|
||||
/* Compare two string objects via strcmp() or strcoll() depending on flags.
|
||||
* Note that the objects may be integer-encoded. In such a case we
|
||||
* use ll2string() to get a string representation of the numbers on the stack
|
||||
@@ -592,7 +604,7 @@ int equalStringObjects(robj *a, robj *b) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t stringObjectLen(robj *o) {
|
||||
size_t stringObjectLen(robj_roptr o) {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
return sdslen(szFromObj(o));
|
||||
@@ -617,7 +629,7 @@ int getDoubleFromObject(const robj *o, double *target) {
|
||||
(size_t)(eptr-(char*)szFromObj(o)) != sdslen(szFromObj(o)) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
std::isnan(value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)ptrFromObj(o);
|
||||
@@ -659,7 +671,7 @@ int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
(size_t)(eptr-(char*)szFromObj(o)) != sdslen(szFromObj(o)) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
std::isnan(value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)szFromObj(o);
|
||||
@@ -704,6 +716,34 @@ int getLongLongFromObject(robj *o, long long *target) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int getUnsignedLongLongFromObject(robj *o, uint64_t *target) {
|
||||
uint64_t value;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
char *pchEnd = nullptr;
|
||||
errno = 0;
|
||||
value = strtoull(szFromObj(o), &pchEnd, 10);
|
||||
if (value == 0) {
|
||||
// potential error
|
||||
if (errno != 0)
|
||||
return C_ERR;
|
||||
if (pchEnd == szFromObj(o))
|
||||
return C_ERR;
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)ptrFromObj(o);
|
||||
} else {
|
||||
serverPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
if (target) *target = value;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg) {
|
||||
long long value;
|
||||
if (getLongLongFromObject(o, &value) != C_OK) {
|
||||
@@ -959,39 +999,39 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
struct redisMemOverhead *mh = (redisMemOverhead*)zcalloc(sizeof(*mh), MALLOC_LOCAL);
|
||||
|
||||
mh->total_allocated = zmalloc_used;
|
||||
mh->startup_allocated = server.initial_memory_usage;
|
||||
mh->peak_allocated = server.stat_peak_memory;
|
||||
mh->startup_allocated = g_pserver->initial_memory_usage;
|
||||
mh->peak_allocated = g_pserver->stat_peak_memory;
|
||||
mh->total_frag =
|
||||
(float)server.cron_malloc_stats.process_rss / server.cron_malloc_stats.zmalloc_used;
|
||||
(float)g_pserver->cron_malloc_stats.process_rss / g_pserver->cron_malloc_stats.zmalloc_used;
|
||||
mh->total_frag_bytes =
|
||||
server.cron_malloc_stats.process_rss - server.cron_malloc_stats.zmalloc_used;
|
||||
g_pserver->cron_malloc_stats.process_rss - g_pserver->cron_malloc_stats.zmalloc_used;
|
||||
mh->allocator_frag =
|
||||
(float)server.cron_malloc_stats.allocator_active / server.cron_malloc_stats.allocator_allocated;
|
||||
(float)g_pserver->cron_malloc_stats.allocator_active / g_pserver->cron_malloc_stats.allocator_allocated;
|
||||
mh->allocator_frag_bytes =
|
||||
server.cron_malloc_stats.allocator_active - server.cron_malloc_stats.allocator_allocated;
|
||||
g_pserver->cron_malloc_stats.allocator_active - g_pserver->cron_malloc_stats.allocator_allocated;
|
||||
mh->allocator_rss =
|
||||
(float)server.cron_malloc_stats.allocator_resident / server.cron_malloc_stats.allocator_active;
|
||||
(float)g_pserver->cron_malloc_stats.allocator_resident / g_pserver->cron_malloc_stats.allocator_active;
|
||||
mh->allocator_rss_bytes =
|
||||
server.cron_malloc_stats.allocator_resident - server.cron_malloc_stats.allocator_active;
|
||||
g_pserver->cron_malloc_stats.allocator_resident - g_pserver->cron_malloc_stats.allocator_active;
|
||||
mh->rss_extra =
|
||||
(float)server.cron_malloc_stats.process_rss / server.cron_malloc_stats.allocator_resident;
|
||||
(float)g_pserver->cron_malloc_stats.process_rss / g_pserver->cron_malloc_stats.allocator_resident;
|
||||
mh->rss_extra_bytes =
|
||||
server.cron_malloc_stats.process_rss - server.cron_malloc_stats.allocator_resident;
|
||||
g_pserver->cron_malloc_stats.process_rss - g_pserver->cron_malloc_stats.allocator_resident;
|
||||
|
||||
mem_total += server.initial_memory_usage;
|
||||
mem_total += g_pserver->initial_memory_usage;
|
||||
|
||||
mem = 0;
|
||||
if (server.repl_backlog)
|
||||
mem += zmalloc_size(server.repl_backlog);
|
||||
if (g_pserver->repl_backlog)
|
||||
mem += zmalloc_size(g_pserver->repl_backlog);
|
||||
mh->repl_backlog = mem;
|
||||
mem_total += mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.slaves)) {
|
||||
if (listLength(g_pserver->slaves)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
@@ -1005,11 +1045,11 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.clients)) {
|
||||
if (listLength(g_pserver->clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
|
||||
@@ -1023,27 +1063,27 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
mem += sdsalloc(server.aof_buf);
|
||||
if (g_pserver->aof_state != AOF_OFF) {
|
||||
mem += sdsalloc(g_pserver->aof_buf);
|
||||
mem += aofRewriteBufferSize();
|
||||
}
|
||||
mh->aof_buffer = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = server.lua_scripts_mem;
|
||||
mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
|
||||
dictSlots(server.lua_scripts) * sizeof(dictEntry*);
|
||||
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
|
||||
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
|
||||
if (listLength(server.repl_scriptcache_fifo) > 0) {
|
||||
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
|
||||
sdsZmallocSize((sds)listNodeValue(listFirst(server.repl_scriptcache_fifo))));
|
||||
mem = g_pserver->lua_scripts_mem;
|
||||
mem += dictSize(g_pserver->lua_scripts) * sizeof(dictEntry) +
|
||||
dictSlots(g_pserver->lua_scripts) * sizeof(dictEntry*);
|
||||
mem += dictSize(g_pserver->repl_scriptcache_dict) * sizeof(dictEntry) +
|
||||
dictSlots(g_pserver->repl_scriptcache_dict) * sizeof(dictEntry*);
|
||||
if (listLength(g_pserver->repl_scriptcache_fifo) > 0) {
|
||||
mem += listLength(g_pserver->repl_scriptcache_fifo) * (sizeof(listNode) +
|
||||
sdsZmallocSize((sds)listNodeValue(listFirst(g_pserver->repl_scriptcache_fifo))));
|
||||
}
|
||||
mh->lua_caches = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
long long keyscount = dictSize(db->pdict);
|
||||
if (keyscount==0) continue;
|
||||
|
||||
@@ -1057,8 +1097,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->db[mh->num_dbs].overhead_ht_main = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = dictSize(db->expires) * sizeof(dictEntry) +
|
||||
dictSlots(db->expires) * sizeof(dictEntry*);
|
||||
mem = db->setexpire->bytes_used();
|
||||
mh->db[mh->num_dbs].overhead_ht_expires = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
@@ -1139,8 +1178,8 @@ sds getMemoryDoctorReport(void) {
|
||||
}
|
||||
|
||||
/* Clients using more than 200k each average? */
|
||||
long numslaves = listLength(server.slaves);
|
||||
long numclients = listLength(server.clients)-numslaves;
|
||||
long numslaves = listLength(g_pserver->slaves);
|
||||
long numclients = listLength(g_pserver->clients)-numslaves;
|
||||
if ((numclients > 0) && mh->clients_normal / numclients > (1024*200)) {
|
||||
big_client_buf = 1;
|
||||
num_reports++;
|
||||
@@ -1153,7 +1192,7 @@ sds getMemoryDoctorReport(void) {
|
||||
}
|
||||
|
||||
/* Too many scripts are cached? */
|
||||
if (dictSize(server.lua_scripts) > 1000) {
|
||||
if (dictSize(g_pserver->lua_scripts) > 1000) {
|
||||
many_scripts = 1;
|
||||
num_reports++;
|
||||
}
|
||||
@@ -1203,14 +1242,14 @@ sds getMemoryDoctorReport(void) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
/* Set the object LRU/LFU depending on g_pserver->maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock) {
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (lfu_freq >= 0) {
|
||||
serverAssert(lfu_freq <= 255);
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
@@ -1268,7 +1307,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
addReplyLongLong(c,o->getrefcount(std::memory_order_relaxed));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
@@ -1276,7 +1315,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
}
|
||||
@@ -1284,7 +1323,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
if (!(g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
}
|
||||
@@ -1293,6 +1332,10 @@ NULL
|
||||
* because we update the access time only
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "lastmodified") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c, (g_pserver->mstime - (o->mvcc_tstamp >> MVCC_MS_SHIFT)) / 1000);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
@@ -1308,7 +1351,7 @@ void memoryCommand(client *c) {
|
||||
"DOCTOR - Return memory problems reports.",
|
||||
"MALLOC-STATS -- Return internal statistics report from the memory allocator.",
|
||||
"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.",
|
||||
"STATS -- Return information about the memory usage of the server.",
|
||||
"STATS -- Return information about the memory usage of the g_pserver->",
|
||||
"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).",
|
||||
NULL
|
||||
};
|
||||
@@ -1334,7 +1377,7 @@ NULL
|
||||
}
|
||||
}
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
addReplyNull(c);
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize((robj*)dictGetVal(de),samples);
|
||||
@@ -1402,13 +1445,13 @@ NULL
|
||||
addReplyDouble(c,mh->peak_perc);
|
||||
|
||||
addReplyBulkCString(c,"allocator.allocated");
|
||||
addReplyLongLong(c,server.cron_malloc_stats.allocator_allocated);
|
||||
addReplyLongLong(c,g_pserver->cron_malloc_stats.allocator_allocated);
|
||||
|
||||
addReplyBulkCString(c,"allocator.active");
|
||||
addReplyLongLong(c,server.cron_malloc_stats.allocator_active);
|
||||
addReplyLongLong(c,g_pserver->cron_malloc_stats.allocator_active);
|
||||
|
||||
addReplyBulkCString(c,"allocator.resident");
|
||||
addReplyLongLong(c,server.cron_malloc_stats.allocator_resident);
|
||||
addReplyLongLong(c,g_pserver->cron_malloc_stats.allocator_resident);
|
||||
|
||||
addReplyBulkCString(c,"allocator-fragmentation.ratio");
|
||||
addReplyDouble(c,mh->allocator_frag);
|
||||
@@ -1467,3 +1510,18 @@ NULL
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)ptrFromObj(c->argv[1]));
|
||||
}
|
||||
}
|
||||
|
||||
void redisObject::SetFExpires(bool fExpire)
|
||||
{
|
||||
serverAssert(this->refcount != OBJ_SHARED_REFCOUNT);
|
||||
if (fExpire)
|
||||
this->refcount.fetch_or(1U << 31, std::memory_order_relaxed);
|
||||
else
|
||||
this->refcount.fetch_and(~(1U << 31), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void redisObject::setrefcount(unsigned ref)
|
||||
{
|
||||
serverAssert(!FExpires());
|
||||
refcount.store(ref, std::memory_order_relaxed);
|
||||
}
|
||||
+35
-21
@@ -84,7 +84,7 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
if (channel)
|
||||
addReplyBulk(c,channel);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
if (pattern)
|
||||
addReplyBulk(c,pattern);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
* Pubsub low level API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void freePubsubPattern(void *p) {
|
||||
void freePubsubPattern(const void *p) {
|
||||
pubsubPattern *pat = (pubsubPattern *)p;
|
||||
|
||||
decrRefCount(pat->pattern);
|
||||
@@ -143,6 +143,8 @@ int clientSubscriptionsCount(client *c) {
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(c->lock.fOwnLock());
|
||||
dictEntry *de;
|
||||
list *clients = NULL;
|
||||
int retval = 0;
|
||||
@@ -152,10 +154,10 @@ int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
retval = 1;
|
||||
incrRefCount(channel);
|
||||
/* Add the client to the channel -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
de = dictFind(g_pserver->pubsub_channels,channel);
|
||||
if (de == NULL) {
|
||||
clients = listCreate();
|
||||
dictAdd(server.pubsub_channels,channel,clients);
|
||||
dictAdd(g_pserver->pubsub_channels,channel,clients);
|
||||
incrRefCount(channel);
|
||||
} else {
|
||||
clients = (list*)dictGetVal(de);
|
||||
@@ -181,7 +183,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
|
||||
retval = 1;
|
||||
/* Remove the client from the channel -> clients list hash table */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
de = dictFind(g_pserver->pubsub_channels,channel);
|
||||
serverAssertWithInfo(c,NULL,de != NULL);
|
||||
clients = (list*)dictGetVal(de);
|
||||
ln = listSearchKey(clients,c);
|
||||
@@ -191,7 +193,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
/* Free the list and associated hash entry at all if this was
|
||||
* the latest client, so that it will be possible to abuse
|
||||
* Redis PUBSUB creating millions of channels. */
|
||||
dictDelete(server.pubsub_channels,channel);
|
||||
dictDelete(g_pserver->pubsub_channels,channel);
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
@@ -202,6 +204,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
@@ -212,7 +215,7 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
pat = (pubsubPattern*)zmalloc(sizeof(*pat), MALLOC_LOCAL);
|
||||
pat->pattern = getDecodedObject(pattern);
|
||||
pat->pclient = c;
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
listAddNodeTail(g_pserver->pubsub_patterns,pat);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
@@ -232,8 +235,8 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
listDelNode(c->pubsub_patterns,ln);
|
||||
pat.pclient = c;
|
||||
pat.pattern = pattern;
|
||||
ln = listSearchKey(server.pubsub_patterns,&pat);
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
ln = listSearchKey(g_pserver->pubsub_patterns,&pat);
|
||||
listDelNode(g_pserver->pubsub_patterns,ln);
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
@@ -244,6 +247,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed to. */
|
||||
int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
int count = 0;
|
||||
@@ -262,6 +266,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
/* Unsubscribe from all the patterns. Return the number of patterns the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int count = 0;
|
||||
@@ -278,13 +283,14 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
|
||||
/* Publish a message */
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
/* Send to clients listening for that channel */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
de = dictFind(g_pserver->pubsub_channels,channel);
|
||||
if (de) {
|
||||
list *list = reinterpret_cast<::list*>(dictGetVal(de));
|
||||
listNode *ln;
|
||||
@@ -293,15 +299,19 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = reinterpret_cast<client*>(ln->value);
|
||||
fastlock_lock(&c->lock);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
|
||||
continue;
|
||||
if (FCorrectThread(c))
|
||||
fastlock_lock(&c->lock);
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
fastlock_unlock(&c->lock);
|
||||
if (FCorrectThread(c))
|
||||
fastlock_unlock(&c->lock);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
/* Send to clients listening to matching channels */
|
||||
if (listLength(server.pubsub_patterns)) {
|
||||
listRewind(server.pubsub_patterns,&li);
|
||||
if (listLength(g_pserver->pubsub_patterns)) {
|
||||
listRewind(g_pserver->pubsub_patterns,&li);
|
||||
channel = getDecodedObject(channel);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
pubsubPattern *pat = (pubsubPattern*)ln->value;
|
||||
@@ -311,10 +321,14 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
(char*)ptrFromObj(channel),
|
||||
sdslen(szFromObj(channel)),0))
|
||||
{
|
||||
fastlock_lock(&pat->pclient->lock);
|
||||
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
|
||||
continue;
|
||||
if (FCorrectThread(pat->pclient))
|
||||
fastlock_lock(&pat->pclient->lock);
|
||||
addReplyPubsubPatMessage(pat->pclient,
|
||||
pat->pattern,channel,message);
|
||||
fastlock_unlock(&pat->pclient->lock);
|
||||
if (FCorrectThread(pat->pclient))
|
||||
fastlock_unlock(&pat->pclient->lock);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@@ -371,7 +385,7 @@ void punsubscribeCommand(client *c) {
|
||||
|
||||
void publishCommand(client *c) {
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
if (server.cluster_enabled)
|
||||
if (g_pserver->cluster_enabled)
|
||||
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||
else
|
||||
forceCommandPropagation(c,PROPAGATE_REPL);
|
||||
@@ -393,7 +407,7 @@ NULL
|
||||
{
|
||||
/* PUBSUB CHANNELS [<pattern>] */
|
||||
sds pat = (c->argc == 2) ? NULL : szFromObj(c->argv[2]);
|
||||
dictIterator *di = dictGetIterator(server.pubsub_channels);
|
||||
dictIterator *di = dictGetIterator(g_pserver->pubsub_channels);
|
||||
dictEntry *de;
|
||||
long mblen = 0;
|
||||
void *replylen;
|
||||
@@ -418,14 +432,14 @@ NULL
|
||||
|
||||
addReplyArrayLen(c,(c->argc-2)*2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
list *l = (list*)dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
list *l = (list*)dictFetchValue(g_pserver->pubsub_channels,c->argv[j]);
|
||||
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyLongLong(c,l ? listLength(l) : 0);
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"numpat") && c->argc == 2) {
|
||||
/* PUBSUB NUMPAT */
|
||||
addReplyLongLong(c,listLength(server.pubsub_patterns));
|
||||
addReplyLongLong(c,listLength(g_pserver->pubsub_patterns));
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
|
||||
+12
-5
@@ -6,7 +6,7 @@ extern "C" {
|
||||
#include <sys/wait.h>
|
||||
|
||||
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
|
||||
extern "C" int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
{
|
||||
int status = EXIT_FAILURE;
|
||||
int fd[2];
|
||||
@@ -33,12 +33,19 @@ extern "C" int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
else
|
||||
{
|
||||
close(fd[0]);
|
||||
if (rdbSaveFd(fd[1], rsi) != C_OK)
|
||||
FILE *fp = fdopen(fd[1], "w");
|
||||
if (fp == NULL)
|
||||
{
|
||||
close(fd[1]);
|
||||
close (fd[1]);
|
||||
return C_ERR;
|
||||
}
|
||||
close(fd[1]);
|
||||
|
||||
if (rdbSaveFp(fp, rsi) != C_OK)
|
||||
{
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
}
|
||||
fclose(fp);
|
||||
waitpid(pid, &status, 0);
|
||||
}
|
||||
|
||||
@@ -59,7 +66,7 @@ int rdbLoadS3Core(int fd, rdbSaveInfo *rsi)
|
||||
|
||||
if ((fp = fdopen(fd, "rb")) == NULL) return C_ERR;
|
||||
startLoading(fp);
|
||||
rioInitWithFile(&rdb,fileno(fp));
|
||||
rioInitWithFile(&rdb,fp);
|
||||
retval = rdbLoadRio(&rdb,rsi,0);
|
||||
fclose(fp);
|
||||
stopLoading();
|
||||
|
||||
+372
-244
File diff suppressed because it is too large.
Load diff
@@ -91,10 +91,13 @@
|
||||
#define RDB_TYPE_HASH_ZIPLIST 13
|
||||
#define RDB_TYPE_LIST_QUICKLIST 14
|
||||
#define RDB_TYPE_STREAM_LISTPACKS 15
|
||||
|
||||
/* KeyDB Specific Object Types */
|
||||
#define RDB_TYPE_CRON 64
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15))
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15) || (t == RDB_TYPE_CRON))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
|
||||
@@ -133,24 +136,25 @@ int rdbSaveMillisecondTime(rio *rdb, long long t);
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
|
||||
uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||
int rdbSaveObjectType(rio *rdb, robj_roptr o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(rdbSaveInfo *rsi);
|
||||
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveBackground(rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(rdbSaveInfo *rsi);
|
||||
int rdbSaveFile(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveFd(int fd, rdbSaveInfo *rsi);
|
||||
int rdbSaveFp(FILE *pf, rdbSaveInfo *rsi);
|
||||
int rdbSaveS3(char *path, rdbSaveInfo *rsi);
|
||||
int rdbLoadS3(char *path, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key, uint64_t mvcc_tstamp);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj_roptr obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, const unsigned char *s, size_t len);
|
||||
void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr);
|
||||
int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
||||
|
||||
+24
-5
@@ -152,7 +152,7 @@ typedef struct clusterNode {
|
||||
int port;
|
||||
sds name;
|
||||
int flags;
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
int *slots;
|
||||
int slots_count;
|
||||
int current_slot_index;
|
||||
@@ -173,6 +173,8 @@ typedef struct redisConfig {
|
||||
sds appendonly;
|
||||
} redisConfig;
|
||||
|
||||
int g_fInCrash = false;
|
||||
|
||||
/* Prototypes */
|
||||
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
static void createMissingClients(client c);
|
||||
@@ -258,6 +260,19 @@ static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
else fprintf(stderr,"%s: %s\n",hostsocket,err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(config.auth){
|
||||
void *authReply = NULL;
|
||||
redisAppendCommand(c, "AUTH %s", config.auth);
|
||||
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
|
||||
if (reply) freeReplyObject(reply);
|
||||
reply = ((redisReply *) authReply);
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "ERROR: %s\n", reply->str);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
redisAppendCommand(c, "CONFIG GET %s", "save");
|
||||
redisAppendCommand(c, "CONFIG GET %s", "appendonly");
|
||||
|
||||
@@ -1196,7 +1211,7 @@ static int fetchClusterSlotsConfiguration(client c) {
|
||||
assert(reply->type == REDIS_REPLY_ARRAY);
|
||||
for (i = 0; i < reply->elements; i++) {
|
||||
redisReply *r = reply->element[i];
|
||||
assert(r->type = REDIS_REPLY_ARRAY);
|
||||
assert(r->type == REDIS_REPLY_ARRAY);
|
||||
assert(r->elements >= 3);
|
||||
int from, to, slot;
|
||||
from = r->element[0]->integer;
|
||||
@@ -1298,7 +1313,7 @@ int parseOptions(int argc, const char **argv) {
|
||||
if (*p < '0' || *p > '9') goto invalid;
|
||||
}
|
||||
config.randomkeys = 1;
|
||||
config.randomkeys_keyspacelen = atoi(argv[++i]);
|
||||
config.randomkeys_keyspacelen = atoi(next);
|
||||
if (config.randomkeys_keyspacelen < 0)
|
||||
config.randomkeys_keyspacelen = 0;
|
||||
} else if (!strcmp(argv[i],"-q")) {
|
||||
@@ -1533,7 +1548,10 @@ int main(int argc, const char **argv) {
|
||||
if (node->name) printf("%s ", node->name);
|
||||
printf("%s:%d\n", node->ip, node->port);
|
||||
node->redis_config = getRedisConfig(node->ip, node->port, NULL);
|
||||
if (node->redis_config == NULL) exit(1);
|
||||
if (node->redis_config == NULL) {
|
||||
fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n",
|
||||
node->ip, node->port);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
/* Automatically set thread number to node count if not specified
|
||||
@@ -1543,7 +1561,8 @@ int main(int argc, const char **argv) {
|
||||
} else {
|
||||
config.redis_config =
|
||||
getRedisConfig(config.hostip, config.hostport, config.hostsocket);
|
||||
if (config.redis_config == NULL) exit(1);
|
||||
if (config.redis_config == NULL)
|
||||
fprintf(stderr, "WARN: could not fetch server CONFIG\n");
|
||||
}
|
||||
|
||||
if (config.num_threads > 0) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1024];
|
||||
static char error[1044];
|
||||
static off_t epos;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
|
||||
@@ -186,7 +186,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
|
||||
int closefile = (fp == NULL);
|
||||
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
|
||||
|
||||
rioInitWithFile(&rdb,fileno(fp));
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdbstate.rio = &rdb;
|
||||
rdb.update_cksum = rdbLoadProgressCallback;
|
||||
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
|
||||
@@ -285,7 +285,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key,OBJ_MVCC_INVALID)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
@@ -297,7 +297,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
|
||||
expiretime = -1;
|
||||
}
|
||||
/* Verify the checksum if RDB version is >= 5 */
|
||||
if (rdbver >= 5 && server.rdb_checksum) {
|
||||
if (rdbver >= 5 && g_pserver->rdb_checksum) {
|
||||
uint64_t cksum, expected = rdb.cksum;
|
||||
|
||||
rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM;
|
||||
@@ -349,7 +349,7 @@ int redis_check_rdb_main(int argc, const char **argv, FILE *fp) {
|
||||
* an already initialized Redis instance, check if we really need to. */
|
||||
if (shared.integers[0] == NULL)
|
||||
createSharedObjects();
|
||||
server.loading_process_events_interval_bytes = 0;
|
||||
g_pserver->loading_process_events_interval_bytes = 0;
|
||||
rdbCheckMode = 1;
|
||||
rdbCheckInfo("Checking RDB file %s", argv[1]);
|
||||
rdbCheckSetupSignals();
|
||||
|
||||
@@ -124,8 +124,8 @@ extern "C" void freeClusterManager(void) {
|
||||
*
|
||||
* The score is calculated as follows:
|
||||
*
|
||||
* SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
|
||||
* SAME_AS_MASTER = 10000 * each replica in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each replica having the same IP as another replica
|
||||
of the same master.
|
||||
* FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
|
||||
*
|
||||
|
||||
+19
-1
@@ -45,6 +45,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
||||
@@ -55,6 +57,7 @@
|
||||
#include "anet.h"
|
||||
#include "ae.h"
|
||||
#include "storage.h"
|
||||
#include "motd.h"
|
||||
|
||||
#include "redis-cli.h"
|
||||
|
||||
@@ -88,6 +91,11 @@ int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253
|
||||
int *spectrum_palette;
|
||||
int spectrum_palette_size;
|
||||
|
||||
int g_fInCrash = 0;
|
||||
|
||||
const char *motd_url = "http://api.keydb.dev/motd/motd_cli.txt";
|
||||
const char *motd_cache_file = "/.keydb-cli-motd";
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -282,7 +290,7 @@ static int helpEntriesLen;
|
||||
|
||||
static sds cliVersion(void) {
|
||||
sds version;
|
||||
version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
|
||||
version = sdscatprintf(sdsempty(), "%s", KEYDB_REAL_VERSION);
|
||||
|
||||
/* Add git commit and working tree status when available */
|
||||
if (strtoll(redisGitSHA1(),NULL,16)) {
|
||||
@@ -6700,6 +6708,15 @@ int main(int argc, char **argv) {
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Show the message of the day if we are interactive */
|
||||
if (config.output == OUTPUT_STANDARD) {
|
||||
char *szMotd = fetchMOTD(1 /* cache */);
|
||||
if (szMotd != NULL) {
|
||||
printf("Message of the day:\n %s\n", szMotd);
|
||||
sdsfree(szMotd);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ignore SIGPIPE in interactive mode to force a reconnect */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -6711,6 +6728,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
/* Otherwise, we have some arguments to execute */
|
||||
if (cliConnect(0) != REDIS_OK) exit(1);
|
||||
|
||||
if (config.eval) {
|
||||
return evalMode(argc,argv);
|
||||
} else {
|
||||
|
||||
+3
-2
@@ -187,7 +187,7 @@ typedef struct clusterManagerNode {
|
||||
time_t ping_recv;
|
||||
int flags;
|
||||
list *flags_str; /* Flags string representations */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
int dirty; /* Node has changes that can be flushed */
|
||||
uint8_t slots[CLUSTER_MANAGER_SLOTS];
|
||||
int slots_count;
|
||||
@@ -276,4 +276,5 @@ redisReply *sendScan(unsigned long long *it);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+15
-2
@@ -5,6 +5,10 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---------------- Defines common between core and modules --------------- */
|
||||
|
||||
/* Error status return values. */
|
||||
@@ -67,9 +71,9 @@
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI (1<<1)
|
||||
/* The instance is a master */
|
||||
#define REDISMODULE_CTX_FLAGS_MASTER (1<<2)
|
||||
/* The instance is a slave */
|
||||
/* The instance is a replica */
|
||||
#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3)
|
||||
/* The instance is read-only (usually meaning it's a slave as well) */
|
||||
/* The instance is read-only (usually meaning it's a replica as well) */
|
||||
#define REDISMODULE_CTX_FLAGS_READONLY (1<<4)
|
||||
/* The instance is running in cluster mode */
|
||||
#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5)
|
||||
@@ -87,6 +91,8 @@
|
||||
#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)
|
||||
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
@@ -226,6 +232,7 @@ 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);
|
||||
@@ -376,6 +383,7 @@ 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);
|
||||
@@ -538,4 +546,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
#define RedisModuleString robj
|
||||
|
||||
#endif /* REDISMODULE_CORE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* REDISMOUDLE_H */
|
||||
+1
-1
@@ -46,7 +46,7 @@ char *redisGitDirty(void) {
|
||||
}
|
||||
|
||||
uint64_t redisBuildId(void) {
|
||||
char *buildid = REDIS_VERSION REDIS_BUILD_ID REDIS_GIT_DIRTY REDIS_GIT_SHA1;
|
||||
char *buildid = KEYDB_REAL_VERSION REDIS_BUILD_ID REDIS_GIT_DIRTY REDIS_GIT_SHA1;
|
||||
|
||||
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
||||
}
|
||||
+926
-555
File diff suppressed because it is too large.
Load diff
+8
-7
@@ -109,13 +109,14 @@ void rioInitWithBuffer(rio *r, sds s) {
|
||||
static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
size_t retval;
|
||||
|
||||
retval = write(r->io.file.fd,buf,len);
|
||||
retval = fwrite(buf,len,1,r->io.file.fp);
|
||||
r->io.file.buffered += len;
|
||||
|
||||
if (r->io.file.autosync &&
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
redis_fsync(r->io.file.fd);
|
||||
fflush(r->io.file.fp);
|
||||
redis_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
return retval;
|
||||
@@ -123,18 +124,18 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFileRead(rio *r, void *buf, size_t len) {
|
||||
return read(r->io.file.fd,buf,len);
|
||||
return fread(buf,len,1,r->io.file.fp);
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFileTell(rio *r) {
|
||||
return lseek(r->io.file.fd, 0, SEEK_CUR);
|
||||
return ftello(r->io.file.fp);
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFileFlush(rio *r) {
|
||||
return (fsync(r->io.file.fd) == 0) ? 1 : 0;
|
||||
return (fflush(r->io.file.fp) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static const rio rioFileIO = {
|
||||
@@ -149,9 +150,9 @@ static const rio rioFileIO = {
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithFile(rio *r, int fd) {
|
||||
void rioInitWithFile(rio *r, FILE *fp) {
|
||||
*r = rioFileIO;
|
||||
r->io.file.fd = fd;
|
||||
r->io.file.fp = fp;
|
||||
r->io.file.buffered = 0;
|
||||
r->io.file.autosync = 0;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ struct _rio {
|
||||
} buffer;
|
||||
/* Stdio file pointer target. */
|
||||
struct {
|
||||
int fd;
|
||||
FILE *fp;
|
||||
off_t buffered; /* Bytes written since last fsync. */
|
||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||
} file;
|
||||
@@ -128,7 +128,7 @@ static inline int rioFlush(rio *r) {
|
||||
return r->flush(r);
|
||||
}
|
||||
|
||||
void rioInitWithFile(rio *r, int fd);
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
|
||||
+98
-104
@@ -191,7 +191,7 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
int j = 0;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||
if (server.lua_caller->resp == 2 || atype == '*') {
|
||||
if (g_pserver->lua_caller->resp == 2 || atype == '*') {
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
@@ -203,7 +203,7 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
} else if (server.lua_caller->resp == 3) {
|
||||
} else if (g_pserver->lua_caller->resp == 3) {
|
||||
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
|
||||
* follow the above RESP2 code path. */
|
||||
p += 2;
|
||||
@@ -371,14 +371,14 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
int acl_retval = 0;
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||
struct redisCommand *cmd;
|
||||
client *c = server.lua_client;
|
||||
client *c = serverTL->lua_client;
|
||||
sds reply;
|
||||
|
||||
// Ensure our client is on the right thread
|
||||
serverAssert(!(c->flags & CLIENT_PENDING_WRITE));
|
||||
serverAssert(!(c->flags & CLIENT_UNBLOCKED));
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
c->iel = serverTL - server.rgthreadvar;
|
||||
c->iel = serverTL - g_pserver->rgthreadvar;
|
||||
|
||||
/* Cached across calls. */
|
||||
static robj **argv = NULL;
|
||||
@@ -388,7 +388,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* Reflect MULTI state */
|
||||
if (server.lua_multi_emitted || (server.lua_caller->flags & CLIENT_MULTI)) {
|
||||
if (g_pserver->lua_multi_emitted || (g_pserver->lua_caller->flags & CLIENT_MULTI)) {
|
||||
c->flags |= CLIENT_MULTI;
|
||||
} else {
|
||||
c->flags &= ~CLIENT_MULTI;
|
||||
@@ -472,7 +472,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Setup our fake client for command execution */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->puser = server.lua_caller->puser;
|
||||
c->puser = g_pserver->lua_caller->puser;
|
||||
|
||||
/* Process module hooks */
|
||||
moduleCallCommandFilters(c);
|
||||
@@ -533,13 +533,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* of this script. */
|
||||
if (cmd->flags & CMD_WRITE) {
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (server.lua_random_dirty && !server.lua_replicate_commands) {
|
||||
if (g_pserver->lua_random_dirty && !g_pserver->lua_replicate_commands) {
|
||||
luaPushError(lua,
|
||||
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
|
||||
goto cleanup;
|
||||
} else if (listLength(server.masters) && server.repl_slave_ro &&
|
||||
!server.loading &&
|
||||
!(server.lua_caller->flags & CLIENT_MASTER))
|
||||
} else if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
|
||||
!g_pserver->loading &&
|
||||
!(g_pserver->lua_caller->flags & CLIENT_MASTER))
|
||||
{
|
||||
luaPushError(lua, (char*)ptrFromObj(shared.roslaveerr));
|
||||
goto cleanup;
|
||||
@@ -549,7 +549,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
} else {
|
||||
sds aof_write_err = sdscatfmt(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno));
|
||||
strerror(g_pserver->aof_last_write_errno));
|
||||
luaPushError(lua, aof_write_err);
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
@@ -561,10 +561,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* could enlarge the memory usage are not allowed, but only if this is the
|
||||
* first write in the context of this script, otherwise we can't stop
|
||||
* in the middle. */
|
||||
if (server.maxmemory && /* Maxmemory is actually enabled. */
|
||||
!server.loading && /* Don't care about mem if loading. */
|
||||
!listLength(server.masters) && /* Slave must execute the script. */
|
||||
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
if (g_pserver->maxmemory && /* Maxmemory is actually enabled. */
|
||||
!g_pserver->loading && /* Don't care about mem if loading. */
|
||||
!listLength(g_pserver->masters) && /* Slave must execute the script. */
|
||||
g_pserver->lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
@@ -573,20 +573,20 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
|
||||
if (cmd->flags & CMD_WRITE) server.lua_write_dirty = 1;
|
||||
if (cmd->flags & CMD_RANDOM) g_pserver->lua_random_dirty = 1;
|
||||
if (cmd->flags & CMD_WRITE) g_pserver->lua_write_dirty = 1;
|
||||
|
||||
/* If this is a Redis Cluster node, we need to make sure Lua is not
|
||||
* trying to access non-local keys, with the exception of commands
|
||||
* received from our master or when loading the AOF back in memory. */
|
||||
if (server.cluster_enabled && !server.loading &&
|
||||
!(server.lua_caller->flags & CLIENT_MASTER))
|
||||
if (g_pserver->cluster_enabled && !g_pserver->loading &&
|
||||
!(g_pserver->lua_caller->flags & CLIENT_MASTER))
|
||||
{
|
||||
/* Duplicate relevant flags in the lua client. */
|
||||
c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
|
||||
c->flags |= server.lua_caller->flags & (CLIENT_READONLY|CLIENT_ASKING);
|
||||
c->flags |= g_pserver->lua_caller->flags & (CLIENT_READONLY|CLIENT_ASKING);
|
||||
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
|
||||
server.cluster->myself)
|
||||
g_pserver->cluster->myself)
|
||||
{
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to access a non local key in a "
|
||||
@@ -598,22 +598,22 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* If we are using single commands replication, we need to wrap what
|
||||
* we propagate into a MULTI/EXEC block, so that it will be atomic like
|
||||
* a Lua script in the context of AOF and slaves. */
|
||||
if (server.lua_replicate_commands &&
|
||||
!server.lua_multi_emitted &&
|
||||
!(server.lua_caller->flags & CLIENT_MULTI) &&
|
||||
server.lua_write_dirty &&
|
||||
server.lua_repl != PROPAGATE_NONE)
|
||||
if (g_pserver->lua_replicate_commands &&
|
||||
!g_pserver->lua_multi_emitted &&
|
||||
!(g_pserver->lua_caller->flags & CLIENT_MULTI) &&
|
||||
g_pserver->lua_write_dirty &&
|
||||
g_pserver->lua_repl != PROPAGATE_NONE)
|
||||
{
|
||||
execCommandPropagateMulti(server.lua_caller);
|
||||
server.lua_multi_emitted = 1;
|
||||
execCommandPropagateMulti(g_pserver->lua_caller);
|
||||
g_pserver->lua_multi_emitted = 1;
|
||||
}
|
||||
|
||||
/* Run the command */
|
||||
if (server.lua_replicate_commands) {
|
||||
if (g_pserver->lua_replicate_commands) {
|
||||
/* Set flags according to redis.set_repl() settings. */
|
||||
if (server.lua_repl & PROPAGATE_AOF)
|
||||
if (g_pserver->lua_repl & PROPAGATE_AOF)
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
if (server.lua_repl & PROPAGATE_REPL)
|
||||
if (g_pserver->lua_repl & PROPAGATE_REPL)
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
}
|
||||
call(c,call_flags);
|
||||
@@ -648,7 +648,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Sort the output array if needed, assuming it is a non-null multi bulk
|
||||
* reply as expected. */
|
||||
if ((cmd->flags & CMD_SORT_FOR_SCRIPT) &&
|
||||
(server.lua_replicate_commands == 0) &&
|
||||
(g_pserver->lua_replicate_commands == 0) &&
|
||||
(reply[0] == '*' && reply[1] != '-')) {
|
||||
luaSortArray(lua);
|
||||
}
|
||||
@@ -665,7 +665,7 @@ cleanup:
|
||||
* The object must be small, SDS-encoded, and with refcount = 1
|
||||
* (we must be the only owner) for us to cache it. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE &&
|
||||
o->refcount == 1 &&
|
||||
o->getrefcount(std::memory_order_relaxed) == 1 &&
|
||||
(o->encoding == OBJ_ENCODING_RAW ||
|
||||
o->encoding == OBJ_ENCODING_EMBSTR) &&
|
||||
sdslen((sds)ptrFromObj(o)) <= LUA_CMD_OBJCACHE_MAX_LEN)
|
||||
@@ -764,10 +764,10 @@ int luaRedisStatusReplyCommand(lua_State *lua) {
|
||||
* already started to write, returns false and stick to whole scripts
|
||||
* replication, which is our default. */
|
||||
int luaRedisReplicateCommandsCommand(lua_State *lua) {
|
||||
if (server.lua_write_dirty) {
|
||||
if (g_pserver->lua_write_dirty) {
|
||||
lua_pushboolean(lua,0);
|
||||
} else {
|
||||
server.lua_replicate_commands = 1;
|
||||
g_pserver->lua_replicate_commands = 1;
|
||||
/* When we switch to single commands replication, we can provide
|
||||
* different math.random() sequences at every call, which is what
|
||||
* the user normally expects. */
|
||||
@@ -817,7 +817,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
|
||||
int argc = lua_gettop(lua);
|
||||
int flags;
|
||||
|
||||
if (server.lua_replicate_commands == 0) {
|
||||
if (g_pserver->lua_replicate_commands == 0) {
|
||||
lua_pushstring(lua, "You can set the replication behavior only after turning on single commands replication with redis.replicate_commands().");
|
||||
return lua_error(lua);
|
||||
} else if (argc != 1) {
|
||||
@@ -830,7 +830,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
|
||||
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
server.lua_repl = flags;
|
||||
g_pserver->lua_repl = flags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -966,9 +966,12 @@ void scriptingInit(int setup) {
|
||||
lua_State *lua = lua_open();
|
||||
|
||||
if (setup) {
|
||||
server.lua_client = NULL;
|
||||
server.lua_caller = NULL;
|
||||
server.lua_timedout = 0;
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
{
|
||||
g_pserver->rgthreadvar[iel].lua_client = createClient(-1, iel);
|
||||
g_pserver->rgthreadvar[iel].lua_client->flags |= CLIENT_LUA;
|
||||
}
|
||||
g_pserver->lua_timedout = 0;
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
@@ -978,8 +981,8 @@ void scriptingInit(int setup) {
|
||||
/* Initialize a dictionary we use to map SHAs to scripts.
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
* as EVAL, so we need to remember the associated script. */
|
||||
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
|
||||
server.lua_scripts_mem = 0;
|
||||
g_pserver->lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
|
||||
g_pserver->lua_scripts_mem = 0;
|
||||
|
||||
/* Register the redis commands table and fields */
|
||||
lua_newtable(lua);
|
||||
@@ -1117,29 +1120,20 @@ void scriptingInit(int setup) {
|
||||
lua_pcall(lua,0,0,0);
|
||||
}
|
||||
|
||||
/* Create the (non connected) client that we use to execute Redis commands
|
||||
* inside the Lua interpreter.
|
||||
* Note: there is no need to create it again when this function is called
|
||||
* by scriptingReset(). */
|
||||
if (server.lua_client == NULL) {
|
||||
server.lua_client = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
server.lua_client->flags |= CLIENT_LUA;
|
||||
}
|
||||
|
||||
/* Lua beginners often don't use "local", this is likely to introduce
|
||||
* subtle bugs in their code. To prevent problems we protect accesses
|
||||
* to global variables. */
|
||||
scriptingEnableGlobalsProtection(lua);
|
||||
|
||||
server.lua = lua;
|
||||
g_pserver->lua = lua;
|
||||
}
|
||||
|
||||
/* Release resources related to Lua scripting.
|
||||
* This function is used in order to reset the scripting environment. */
|
||||
void scriptingRelease(void) {
|
||||
dictRelease(server.lua_scripts);
|
||||
server.lua_scripts_mem = 0;
|
||||
lua_close(server.lua);
|
||||
dictRelease(g_pserver->lua_scripts);
|
||||
g_pserver->lua_scripts_mem = 0;
|
||||
lua_close(g_pserver->lua);
|
||||
}
|
||||
|
||||
void scriptingReset(void) {
|
||||
@@ -1233,7 +1227,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
sha1hex(funcname+2,(char*)ptrFromObj(body),sdslen((sds)ptrFromObj(body)));
|
||||
|
||||
sds sha = sdsnewlen(funcname+2,40);
|
||||
if ((de = dictFind(server.lua_scripts,sha)) != NULL) {
|
||||
if ((de = dictFind(g_pserver->lua_scripts,sha)) != NULL) {
|
||||
sdsfree(sha);
|
||||
return (sds)dictGetKey(de);
|
||||
}
|
||||
@@ -1271,33 +1265,33 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
/* We also save a SHA1 -> Original script map in a dictionary
|
||||
* so that we can replicate / write in the AOF all the
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
|
||||
int retval = dictAdd(g_pserver->lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : serverTL->lua_client,NULL,retval == DICT_OK);
|
||||
g_pserver->lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
|
||||
incrRefCount(body);
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
long long elapsed = mstime() - server.lua_time_start;
|
||||
long long elapsed = mstime() - g_pserver->lua_time_start;
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
/* Set the timeout condition if not already set and the maximum
|
||||
* execution time was reached. */
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
if (elapsed >= g_pserver->lua_time_limit && g_pserver->lua_timedout == 0) {
|
||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
g_pserver->lua_timedout = 1;
|
||||
/* Once the script timeouts we reenter the event loop to permit others
|
||||
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
||||
* we need to mask the client executing the script from the event loop.
|
||||
* If we don't do that the client may disconnect and could no longer be
|
||||
* here when the EVAL command will return. */
|
||||
protectClient(server.lua_caller);
|
||||
protectClient(g_pserver->lua_caller);
|
||||
}
|
||||
if (server.lua_timedout) processEventsWhileBlocked(serverTL - server.rgthreadvar);
|
||||
if (server.lua_kill) {
|
||||
if (g_pserver->lua_timedout) processEventsWhileBlocked(serverTL - g_pserver->rgthreadvar);
|
||||
if (g_pserver->lua_kill) {
|
||||
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||
lua_error(lua);
|
||||
@@ -1305,10 +1299,10 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
}
|
||||
|
||||
void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_State *lua = server.lua;
|
||||
lua_State *lua = g_pserver->lua;
|
||||
char funcname[43];
|
||||
long long numkeys;
|
||||
long long initial_server_dirty = server.dirty;
|
||||
long long initial_server_dirty = g_pserver->dirty;
|
||||
int delhook = 0, err;
|
||||
|
||||
/* When we replicate whole scripts, we want the same PRNG sequence at
|
||||
@@ -1323,11 +1317,11 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
*
|
||||
* Thanks to this flag we'll raise an error every time a write command
|
||||
* is called after a random command was used. */
|
||||
server.lua_random_dirty = 0;
|
||||
server.lua_write_dirty = 0;
|
||||
server.lua_replicate_commands = server.lua_always_replicate_commands;
|
||||
server.lua_multi_emitted = 0;
|
||||
server.lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
|
||||
g_pserver->lua_random_dirty = 0;
|
||||
g_pserver->lua_write_dirty = 0;
|
||||
g_pserver->lua_replicate_commands = g_pserver->lua_always_replicate_commands;
|
||||
g_pserver->lua_multi_emitted = 0;
|
||||
g_pserver->lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
|
||||
|
||||
/* Get the number of arguments that are keys */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
|
||||
@@ -1393,7 +1387,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
|
||||
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
selectDb(serverTL->lua_client,c->db->id);
|
||||
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
@@ -1402,14 +1396,14 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
*
|
||||
* If we are debugging, we set instead a "line" hook so that the
|
||||
* debugger is call-back at every line executed by the script. */
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
g_pserver->lua_caller = c;
|
||||
g_pserver->lua_time_start = mstime();
|
||||
g_pserver->lua_kill = 0;
|
||||
if (g_pserver->lua_time_limit > 0 && ldb.active == 0) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
} else if (ldb.active) {
|
||||
lua_sethook(server.lua,luaLdbLineHook,LUA_MASKLINE|LUA_MASKCOUNT,100000);
|
||||
lua_sethook(g_pserver->lua,luaLdbLineHook,LUA_MASKLINE|LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
}
|
||||
|
||||
@@ -1420,14 +1414,14 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
|
||||
/* Perform some cleanup that we need to do both on error and success. */
|
||||
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
|
||||
if (server.lua_timedout) {
|
||||
server.lua_timedout = 0;
|
||||
if (g_pserver->lua_timedout) {
|
||||
g_pserver->lua_timedout = 0;
|
||||
/* Restore the client that was protected when the script timeout
|
||||
* was detected. */
|
||||
unprotectClient(c);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(server.masters, &li);
|
||||
listRewind(g_pserver->masters, &li);
|
||||
while ((ln = listNext(&li)))
|
||||
{
|
||||
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
|
||||
@@ -1435,7 +1429,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
queueClientForReprocessing(mi->master);
|
||||
}
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
g_pserver->lua_caller = NULL;
|
||||
|
||||
/* Call the Lua garbage collector from time to time to avoid a
|
||||
* full cycle performed by Lua, which adds too latency.
|
||||
@@ -1467,12 +1461,12 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
|
||||
/* If we are using single commands replication, emit EXEC if there
|
||||
* was at least a write. */
|
||||
if (server.lua_replicate_commands) {
|
||||
if (g_pserver->lua_replicate_commands) {
|
||||
preventCommandPropagation(c);
|
||||
if (server.lua_multi_emitted) {
|
||||
if (g_pserver->lua_multi_emitted) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
alsoPropagate(cserver.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
@@ -1485,15 +1479,15 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* To do so we use a cache of SHA1s of scripts that we already propagated
|
||||
* as full EVAL, that's called the Replication Script Cache.
|
||||
*
|
||||
* For repliation, everytime a new slave attaches to the master, we need to
|
||||
* For repliation, everytime a new replica attaches to the master, we need to
|
||||
* flush our cache of scripts that can be replicated as EVALSHA, while
|
||||
* for AOF we need to do so every time we rewrite the AOF file. */
|
||||
if (evalsha && !server.lua_replicate_commands) {
|
||||
if (evalsha && !g_pserver->lua_replicate_commands) {
|
||||
if (!replicationScriptCacheExists((sds)ptrFromObj(c->argv[1]))) {
|
||||
/* This script is not in our script cache, replicate it as
|
||||
* EVAL, then add it into the script cache, as from now on
|
||||
* slaves and AOF know about it. */
|
||||
robj *script = (robj*)dictFetchValue(server.lua_scripts,ptrFromObj(c->argv[1]));
|
||||
robj *script = (robj*)dictFetchValue(g_pserver->lua_scripts,ptrFromObj(c->argv[1]));
|
||||
|
||||
replicationScriptCacheAdd((sds)ptrFromObj(c->argv[1]));
|
||||
serverAssertWithInfo(c,NULL,script != NULL);
|
||||
@@ -1502,7 +1496,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* just to replicate it as SCRIPT LOAD, otherwise we risk running
|
||||
* an aborted script on slaves (that may then produce results there)
|
||||
* or just running a CPU costly read-only script on the slaves. */
|
||||
if (server.dirty == initial_server_dirty) {
|
||||
if (g_pserver->dirty == initial_server_dirty) {
|
||||
rewriteClientCommandVector(c,3,
|
||||
resetRefCount(createStringObject("SCRIPT",6)),
|
||||
resetRefCount(createStringObject("LOAD",4)),
|
||||
@@ -1556,31 +1550,31 @@ NULL
|
||||
scriptingReset();
|
||||
addReply(c,shared.ok);
|
||||
replicationScriptCacheFlush();
|
||||
server.dirty++; /* Propagating this command is a good idea. */
|
||||
g_pserver->dirty++; /* Propagating this command is a good idea. */
|
||||
} else if (c->argc >= 2 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"exists")) {
|
||||
int j;
|
||||
|
||||
addReplyArrayLen(c, c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (dictFind(server.lua_scripts,ptrFromObj(c->argv[j])))
|
||||
if (dictFind(g_pserver->lua_scripts,ptrFromObj(c->argv[j])))
|
||||
addReply(c,shared.cone);
|
||||
else
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"load")) {
|
||||
sds sha = luaCreateFunction(c,server.lua,c->argv[2]);
|
||||
sds sha = luaCreateFunction(c,g_pserver->lua,c->argv[2]);
|
||||
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
|
||||
addReplyBulkCBuffer(c,sha,40);
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
} else if (c->argc == 2 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
if (g_pserver->lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
||||
} else if (g_pserver->lua_caller->flags & CLIENT_MASTER) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
|
||||
} else if (server.lua_write_dirty) {
|
||||
} else if (g_pserver->lua_write_dirty) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
} else {
|
||||
server.lua_kill = 1;
|
||||
g_pserver->lua_kill = 1;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (c->argc == 3 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"debug")) {
|
||||
@@ -1616,7 +1610,7 @@ void ldbInit(void) {
|
||||
ldb.fd = -1;
|
||||
ldb.active = 0;
|
||||
ldb.logs = listCreate();
|
||||
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
||||
listSetFreeMethod(ldb.logs,(void (*)(const void*))sdsfree);
|
||||
ldb.children = listCreate();
|
||||
ldb.src = NULL;
|
||||
ldb.lines = 0;
|
||||
@@ -2289,7 +2283,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
* implementation, with ldb.step enabled, so as a side effect the Redis command
|
||||
* and its reply are logged. */
|
||||
void ldbRedis(lua_State *lua, sds *argv, int argc) {
|
||||
int j, saved_rc = server.lua_replicate_commands;
|
||||
int j, saved_rc = g_pserver->lua_replicate_commands;
|
||||
|
||||
lua_getglobal(lua,"redis");
|
||||
lua_pushstring(lua,"call");
|
||||
@@ -2297,10 +2291,10 @@ void ldbRedis(lua_State *lua, sds *argv, int argc) {
|
||||
for (j = 1; j < argc; j++)
|
||||
lua_pushlstring(lua,argv[j],sdslen(argv[j]));
|
||||
ldb.step = 1; /* Force redis.call() to log. */
|
||||
server.lua_replicate_commands = 1;
|
||||
g_pserver->lua_replicate_commands = 1;
|
||||
lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */
|
||||
ldb.step = 0; /* Disable logging. */
|
||||
server.lua_replicate_commands = saved_rc;
|
||||
g_pserver->lua_replicate_commands = saved_rc;
|
||||
lua_pop(lua,2); /* Discard the result and clean the stack. */
|
||||
}
|
||||
|
||||
@@ -2474,9 +2468,9 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
|
||||
/* Check if a timeout occurred. */
|
||||
if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) {
|
||||
mstime_t elapsed = mstime() - server.lua_time_start;
|
||||
mstime_t timelimit = server.lua_time_limit ?
|
||||
server.lua_time_limit : 5000;
|
||||
mstime_t elapsed = mstime() - g_pserver->lua_time_start;
|
||||
mstime_t timelimit = g_pserver->lua_time_limit ?
|
||||
g_pserver->lua_time_limit : 5000;
|
||||
if (elapsed >= timelimit) {
|
||||
timeout = 1;
|
||||
ldb.step = 1;
|
||||
@@ -2504,7 +2498,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
lua_pushstring(lua, "timeout during Lua debugging with client closing connection");
|
||||
lua_error(lua);
|
||||
}
|
||||
server.lua_time_start = mstime();
|
||||
g_pserver->lua_time_start = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,18 @@ static inline int sdsHdrSize(char type) {
|
||||
return sizeof(struct sdshdr32);
|
||||
case SDS_TYPE_64:
|
||||
return sizeof(struct sdshdr64);
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return sizeof(struct sdshdrrefcount);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline char sdsReqType(size_t string_size) {
|
||||
static inline char sdsReqType(ssize_t string_size) {
|
||||
if (string_size < 0){
|
||||
string_size = -string_size;
|
||||
if (string_size < 1<<16)
|
||||
return SDS_TYPE_REFCOUNTED;
|
||||
}
|
||||
if (string_size < 1<<5)
|
||||
return SDS_TYPE_5;
|
||||
if (string_size < 1<<8)
|
||||
@@ -86,10 +93,12 @@ static inline char sdsReqType(size_t string_size) {
|
||||
* You can print the string with printf() as there is an implicit \0 at the
|
||||
* end of the string. However the string is binary safe and can contain
|
||||
* \0 characters in the middle, as the length is stored in the sds header. */
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
sds sdsnewlen(const void *init, ssize_t initlen) {
|
||||
void *sh;
|
||||
sds s;
|
||||
char type = sdsReqType(initlen);
|
||||
if (initlen < 0)
|
||||
initlen = -initlen;
|
||||
/* Empty strings are usually created in order to append. Use type 8
|
||||
* since type 5 is not good at this. */
|
||||
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
|
||||
@@ -137,6 +146,13 @@ sds sdsnewlen(const void *init, size_t initlen) {
|
||||
*fp = type;
|
||||
break;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
sh->len = initlen;
|
||||
sh->refcount = 1;
|
||||
*fp = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (initlen && init)
|
||||
memcpy(s, init, initlen);
|
||||
@@ -157,13 +173,29 @@ sds sdsnew(const char *init) {
|
||||
}
|
||||
|
||||
/* Duplicate an sds string. */
|
||||
sds sdsdup(const sds s) {
|
||||
sds sdsdup(const char *s) {
|
||||
return sdsnewlen(s, sdslen(s));
|
||||
}
|
||||
|
||||
sds sdsdupshared(const char *s) {
|
||||
unsigned char flags = s[-1];
|
||||
if ((flags & SDS_TYPE_MASK) != SDS_TYPE_REFCOUNTED)
|
||||
return sdsnewlen(s, -sdslen(s));
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
__atomic_fetch_add(&sh->refcount, 1, __ATOMIC_RELAXED);
|
||||
return (sds)s;
|
||||
}
|
||||
|
||||
/* Free an sds string. No operation is performed if 's' is NULL. */
|
||||
void sdsfree(sds s) {
|
||||
void sdsfree(const char *s) {
|
||||
if (s == NULL) return;
|
||||
unsigned char flags = s[-1];
|
||||
if ((flags & SDS_TYPE_MASK) == SDS_TYPE_REFCOUNTED)
|
||||
{
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
if (__atomic_fetch_sub(&sh->refcount, 1, __ATOMIC_RELAXED) > 1)
|
||||
return;
|
||||
}
|
||||
s_free((char*)s-sdsHdrSize(s[-1]));
|
||||
}
|
||||
|
||||
@@ -368,6 +400,11 @@ void sdsIncrLen(sds s, ssize_t incr) {
|
||||
len = (sh->len += incr);
|
||||
break;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
len = (sh->len += incr);
|
||||
break;
|
||||
}
|
||||
default: len = 0; /* Just to avoid compilation warnings. */
|
||||
}
|
||||
s[len] = '\0';
|
||||
@@ -787,7 +824,7 @@ void sdstoupper(sds s) {
|
||||
* If two strings share exactly the same prefix, but one of the two has
|
||||
* additional characters, the longer string is considered to be greater than
|
||||
* the smaller one. */
|
||||
int sdscmp(const sds s1, const sds s2) {
|
||||
int sdscmp(const char *s1, const char *s2) {
|
||||
size_t l1, l2, minlen;
|
||||
int cmp;
|
||||
|
||||
|
||||
@@ -91,18 +91,30 @@ struct __attribute__ ((__packed__)) sdshdr64 {
|
||||
#endif
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) sdshdrrefcount {
|
||||
uint64_t len; /* used */
|
||||
uint16_t refcount;
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
#ifndef __cplusplus
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
|
||||
#define SDS_TYPE_5 0
|
||||
#define SDS_TYPE_8 1
|
||||
#define SDS_TYPE_16 2
|
||||
#define SDS_TYPE_32 3
|
||||
#define SDS_TYPE_64 4
|
||||
#define SDS_TYPE_REFCOUNTED 5
|
||||
#define SDS_TYPE_MASK 7
|
||||
#define SDS_TYPE_BITS 3
|
||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)(((void*)((s)-(sizeof(struct sdshdr##T)))));
|
||||
#define SDS_HDR_VAR_REFCOUNTED(s) struct sdshdrrefcount *sh = (struct sdshdrrefcount *)(((void*)((s)-(sizeof(struct sdshdrrefcount)))));
|
||||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
||||
#define SDS_HDR_REFCOUNTED(s) ((struct sdshdrrefcount *)((s)-(sizeof(struct sdshdrrefcount))))
|
||||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
||||
|
||||
static inline size_t sdslen(const sds s) {
|
||||
static inline size_t sdslen(const char *s) {
|
||||
unsigned char flags = s[-1];
|
||||
int type = flags & SDS_TYPE_MASK;
|
||||
|
||||
@@ -121,12 +133,14 @@ static inline size_t sdslen(const sds s) {
|
||||
return SDS_HDR(32,s)->len;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->len;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return SDS_HDR_REFCOUNTED(s)->len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline size_t sdsavail(const sds s) {
|
||||
static inline size_t sdsavail(const char * s) {
|
||||
unsigned char flags = s[-1];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5: {
|
||||
@@ -148,6 +162,9 @@ static inline size_t sdsavail(const sds s) {
|
||||
SDS_HDR_VAR(64,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
return 0; // immutable
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -173,6 +190,9 @@ static inline void sdssetlen(sds s, size_t newlen) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len = newlen;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
SDS_HDR_REFCOUNTED(s)->len = newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +218,9 @@ static inline void sdsinclen(sds s, size_t inc) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len += inc;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
SDS_HDR_REFCOUNTED(s)->len += inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +238,8 @@ static inline size_t sdsalloc(const sds s) {
|
||||
return SDS_HDR(32,s)->alloc;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->alloc;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return SDS_HDR_REFCOUNTED(s)->len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -237,14 +262,23 @@ static inline void sdssetalloc(sds s, size_t newlen) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->alloc = newlen;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
static inline int sdsisshared(const char *s)
|
||||
{
|
||||
unsigned char flags = s[-1];
|
||||
return ((flags & SDS_TYPE_MASK) == SDS_TYPE_REFCOUNTED);
|
||||
}
|
||||
|
||||
sds sdsnewlen(const void *init, ssize_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty(void);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
sds sdsdup(const char *s);
|
||||
sds sdsdupshared(const char *s);
|
||||
void sdsfree(const char *s);
|
||||
sds sdsgrowzero(sds s, size_t len);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
sds sdscat(sds s, const char *t);
|
||||
@@ -265,7 +299,7 @@ sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
int sdscmp(const char *s1, const char *s2);
|
||||
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
@@ -298,6 +332,120 @@ int sdsTest(int argc, char *argv[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
class sdsview
|
||||
{
|
||||
protected:
|
||||
sds m_str = nullptr;
|
||||
|
||||
sdsview() = default; // Not allowed to create a sdsview directly with a nullptr
|
||||
public:
|
||||
sdsview(sds str)
|
||||
: m_str(str)
|
||||
{}
|
||||
|
||||
sdsview(const char *str)
|
||||
: m_str((sds)str)
|
||||
{}
|
||||
|
||||
bool operator<(const sdsview &other) const
|
||||
{
|
||||
return sdscmp(m_str, other.m_str) < 0;
|
||||
}
|
||||
|
||||
bool operator==(const sdsview &other) const
|
||||
{
|
||||
return sdscmp(m_str, other.m_str) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const char *other) const
|
||||
{
|
||||
return sdscmp(m_str, other) == 0;
|
||||
}
|
||||
|
||||
char operator[](size_t idx) const
|
||||
{
|
||||
return m_str[idx];
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return sdslen(m_str);
|
||||
}
|
||||
|
||||
const char *get() const { return m_str; }
|
||||
|
||||
explicit operator const char*() const { return m_str; }
|
||||
};
|
||||
|
||||
class sdsstring : public sdsview
|
||||
{
|
||||
public:
|
||||
sdsstring() = default;
|
||||
explicit sdsstring(sds str)
|
||||
: sdsview(str)
|
||||
{}
|
||||
|
||||
sdsstring(const sdsstring &other)
|
||||
: sdsview(sdsdup(other.m_str))
|
||||
{}
|
||||
|
||||
sdsstring(sdsstring &&other)
|
||||
: sdsview(other.m_str)
|
||||
{
|
||||
other.m_str = nullptr;
|
||||
}
|
||||
|
||||
sdsstring &operator=(const sdsstring &other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdup(other.m_str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
sdsstring &operator=(sds other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdup(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~sdsstring()
|
||||
{
|
||||
sdsfree(m_str);
|
||||
}
|
||||
};
|
||||
|
||||
class sdsimmutablestring : public sdsstring
|
||||
{
|
||||
public:
|
||||
sdsimmutablestring() = default;
|
||||
explicit sdsimmutablestring(sds str)
|
||||
: sdsstring(str)
|
||||
{}
|
||||
|
||||
explicit sdsimmutablestring(const char *str)
|
||||
: sdsstring((sds)str)
|
||||
{}
|
||||
|
||||
sdsimmutablestring(const sdsimmutablestring &other)
|
||||
: sdsstring(sdsdupshared(other.m_str))
|
||||
{}
|
||||
|
||||
sdsimmutablestring(sdsimmutablestring &&other)
|
||||
: sdsstring(other.m_str)
|
||||
{
|
||||
other.m_str = nullptr;
|
||||
}
|
||||
|
||||
auto &operator=(const sdsimmutablestring &other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdupshared(other.m_str);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,345 @@
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include "compactvector.h"
|
||||
|
||||
/****************************************
|
||||
* semiorderedset.h:
|
||||
*
|
||||
* The ordered set is a hash set that maintains semi-ordering, that is you can iterate in sub-linear time over the set comparing a value.
|
||||
* It has a few other useful properties vs the traditional set:
|
||||
* 1. The key need not be the underlying type, the only requirement is the value type is castable to the key
|
||||
* 2. The key need not have total ordering. The set will iterate until it finds an exact match with operator== on the value
|
||||
* This provides additional flexibility on insert allowing us to optimize this case.
|
||||
*
|
||||
*/
|
||||
|
||||
extern uint64_t dictGenHashFunction(const void *key, int len);
|
||||
|
||||
template<typename T, typename T_KEY = T, bool MEMMOVE_SAFE = false>
|
||||
class semiorderedset
|
||||
{
|
||||
friend struct setiter;
|
||||
std::vector<compactvector<T, MEMMOVE_SAFE>> m_data;
|
||||
size_t celem = 0;
|
||||
static const size_t bits_min = 8;
|
||||
size_t bits = bits_min;
|
||||
size_t idxRehash = (1ULL << bits_min);
|
||||
bool fPauseRehash = false;
|
||||
|
||||
constexpr size_t targetElementsPerBucket()
|
||||
{
|
||||
// Aim for roughly 4 cache lines per bucket (determined by imperical testing)
|
||||
// lower values are faster but use more memory
|
||||
return std::max((64/sizeof(T))*8, (size_t)2);
|
||||
}
|
||||
|
||||
public:
|
||||
semiorderedset()
|
||||
{
|
||||
m_data.resize((1ULL << bits));
|
||||
}
|
||||
|
||||
struct setiter
|
||||
{
|
||||
semiorderedset *set;
|
||||
size_t idxPrimary = 0;
|
||||
size_t idxSecondary = 0;
|
||||
|
||||
setiter(semiorderedset *set)
|
||||
{
|
||||
this->set = set;
|
||||
}
|
||||
|
||||
bool operator==(const setiter &other) const
|
||||
{
|
||||
return (idxPrimary == other.idxPrimary) && (idxSecondary == other.idxSecondary);
|
||||
}
|
||||
|
||||
bool operator!=(const setiter &other) const { return !operator==(other); }
|
||||
|
||||
inline T &operator*() { return set->m_data[idxPrimary][idxSecondary]; }
|
||||
inline const T &operator*() const { return set->m_data[idxPrimary][idxSecondary]; }
|
||||
|
||||
inline T *operator->() { return &set->m_data[idxPrimary][idxSecondary]; }
|
||||
inline const T *operator->() const { return &set->m_data[idxPrimary][idxSecondary]; }
|
||||
};
|
||||
|
||||
setiter find(const T_KEY &key)
|
||||
{
|
||||
RehashStep();
|
||||
setiter itr(this);
|
||||
itr.idxPrimary = idxFromObj(key);
|
||||
|
||||
for (int hashset = 0; hashset < 2; ++hashset) // rehashing may only be 1 resize behind, so we check up to two slots
|
||||
{
|
||||
auto &vecBucket = m_data[itr.idxPrimary];
|
||||
|
||||
auto itrFind = std::find(vecBucket.begin(), vecBucket.end(), key);
|
||||
if (itrFind != vecBucket.end())
|
||||
{
|
||||
itr.idxSecondary = itrFind - vecBucket.begin();
|
||||
return itr;
|
||||
}
|
||||
|
||||
// See if we have to check the older slot
|
||||
size_t mask = (hashmask() >> 1);
|
||||
if (itr.idxPrimary == (itr.idxPrimary & mask))
|
||||
break; // same bucket we just checked
|
||||
itr.idxPrimary &= mask;
|
||||
if (FRehashedRow(itr.idxPrimary))
|
||||
break;
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
setiter end()
|
||||
{
|
||||
setiter itr(this);
|
||||
itr.idxPrimary = m_data.size();
|
||||
return itr;
|
||||
}
|
||||
|
||||
void insert(T &e, bool fRehash = false)
|
||||
{
|
||||
if (!fRehash)
|
||||
RehashStep();
|
||||
|
||||
auto idx = idxFromObj(static_cast<T_KEY>(e));
|
||||
if (!fRehash)
|
||||
++celem;
|
||||
|
||||
typename compactvector<T, MEMMOVE_SAFE>::iterator itrInsert;
|
||||
if (!m_data[idx].empty() && !(e < m_data[idx].back()))
|
||||
itrInsert = m_data[idx].end();
|
||||
else
|
||||
itrInsert = std::upper_bound(m_data[idx].begin(), m_data[idx].end(), e);
|
||||
itrInsert = m_data[idx].insert(itrInsert, e);
|
||||
|
||||
if (celem > ((1ULL << bits)*targetElementsPerBucket()))
|
||||
grow();
|
||||
}
|
||||
|
||||
// enumeration starting from the 'itrStart'th key. Note that the iter is a hint, and need no be valid anymore
|
||||
template<typename T_VISITOR, typename T_MAX>
|
||||
setiter enumerate(const setiter &itrStart, const T_MAX &max, T_VISITOR fn, long long *pccheck)
|
||||
{
|
||||
setiter itr(itrStart);
|
||||
|
||||
if (itrStart.set == this) // really if this case isn't true its probably a bug
|
||||
itr = itrStart; // but why crash the program when we can easily fix this?
|
||||
|
||||
fPauseRehash = true;
|
||||
if (itr.idxPrimary >= m_data.size())
|
||||
itr.idxPrimary = 0;
|
||||
|
||||
for (size_t ibucket = 0; ibucket < m_data.size(); ++ibucket)
|
||||
{
|
||||
if (!enumerate_bucket(itr, max, fn, pccheck))
|
||||
break;
|
||||
itr.idxSecondary = 0;
|
||||
|
||||
++itr.idxPrimary;
|
||||
if (itr.idxPrimary >= m_data.size())
|
||||
itr.idxPrimary = 0;
|
||||
}
|
||||
fPauseRehash = false;
|
||||
return itr;
|
||||
}
|
||||
|
||||
// This will "randomly" visit nodes biased towards lower values first
|
||||
template<typename T_VISITOR>
|
||||
size_t random_visit(T_VISITOR &fn)
|
||||
{
|
||||
bool fSawAny = true;
|
||||
size_t visited = 0;
|
||||
size_t basePrimary = rand() % m_data.size();
|
||||
for (size_t idxSecondary = 0; fSawAny; ++idxSecondary)
|
||||
{
|
||||
fSawAny = false;
|
||||
for (size_t idxPrimaryCount = 0; idxPrimaryCount < m_data.size(); ++idxPrimaryCount)
|
||||
{
|
||||
size_t idxPrimary = (basePrimary + idxPrimaryCount) % m_data.size();
|
||||
if (idxSecondary < m_data[idxPrimary].size())
|
||||
{
|
||||
++visited;
|
||||
fSawAny = true;
|
||||
if (!fn(m_data[idxPrimary][idxSecondary]))
|
||||
return visited;
|
||||
}
|
||||
}
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
const T& random_value() const
|
||||
{
|
||||
assert(!empty());
|
||||
for (;;)
|
||||
{
|
||||
size_t idxPrimary = rand() % m_data.size();
|
||||
if (m_data[idxPrimary].empty())
|
||||
continue;
|
||||
|
||||
return m_data[idxPrimary][rand() % m_data[idxPrimary].size()];
|
||||
}
|
||||
}
|
||||
|
||||
void erase(const setiter &itr)
|
||||
{
|
||||
auto &vecRow = m_data[itr.idxPrimary];
|
||||
vecRow.erase(vecRow.begin() + itr.idxSecondary);
|
||||
--celem;
|
||||
RehashStep();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_data = decltype(m_data)();
|
||||
bits = bits_min;
|
||||
m_data.resize(1ULL << bits);
|
||||
idxRehash = m_data.size();
|
||||
}
|
||||
|
||||
bool empty() const noexcept { return celem == 0; }
|
||||
size_t size() const noexcept { return celem; }
|
||||
|
||||
size_t bytes_used() const
|
||||
{
|
||||
size_t cb = sizeof(this) + (m_data.capacity()-m_data.size())*sizeof(T);
|
||||
for (auto &vec : m_data)
|
||||
{
|
||||
cb += vec.bytes_used();
|
||||
}
|
||||
return cb;
|
||||
}
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
size_t getstats(char *buf, size_t bufsize) const
|
||||
{
|
||||
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
||||
unsigned long totchainlen = 0;
|
||||
unsigned long clvector[DICT_STATS_VECTLEN] = {0};
|
||||
size_t l = 0;
|
||||
|
||||
if (empty()) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
for (auto &vec : m_data) {
|
||||
if (vec.empty()) {
|
||||
clvector[0]++;
|
||||
continue;
|
||||
}
|
||||
slots++;
|
||||
/* For each hash entry on this slot... */
|
||||
chainlen = vec.size();
|
||||
|
||||
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
|
||||
if (chainlen > maxchainlen) maxchainlen = chainlen;
|
||||
totchainlen += chainlen;
|
||||
}
|
||||
|
||||
size_t used = m_data.size()-clvector[0];
|
||||
/* Generate human readable stats. */
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
"semiordered set stats:\n"
|
||||
" table size: %ld\n"
|
||||
" number of slots: %ld\n"
|
||||
" used slots: %ld\n"
|
||||
" max chain length: %ld\n"
|
||||
" avg chain length (counted): %.02f\n"
|
||||
" avg chain length (computed): %.02f\n"
|
||||
" Chain length distribution:\n",
|
||||
size(), used, slots, maxchainlen,
|
||||
(float)totchainlen/slots, (float)size()/m_data.size());
|
||||
|
||||
for (i = 0; i < DICT_STATS_VECTLEN; i++) {
|
||||
if (clvector[i] == 0) continue;
|
||||
if (l >= bufsize) break;
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
" %s%ld: %ld (%.02f%%)\n",
|
||||
(i == DICT_STATS_VECTLEN-1)?">= ":"",
|
||||
i, clvector[i], ((float)clvector[i]/m_data.size())*100);
|
||||
}
|
||||
|
||||
/* Unlike snprintf(), teturn the number of characters actually written. */
|
||||
if (bufsize) buf[bufsize-1] = '\0';
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
inline size_t hashmask() const { return (1ULL << bits) - 1; }
|
||||
|
||||
size_t idxFromObj(const T_KEY &key)
|
||||
{
|
||||
size_t v = (size_t)dictGenHashFunction(&key, sizeof(key));
|
||||
return v & hashmask();
|
||||
}
|
||||
|
||||
bool FRehashedRow(size_t idx) const
|
||||
{
|
||||
return (idx >= (m_data.size()/2)) || (idx < idxRehash);
|
||||
}
|
||||
|
||||
void RehashStep()
|
||||
{
|
||||
if (fPauseRehash)
|
||||
return;
|
||||
|
||||
int steps = 0;
|
||||
for (; idxRehash < (m_data.size()/2); ++idxRehash)
|
||||
{
|
||||
compactvector<T, MEMMOVE_SAFE> vecT;
|
||||
std::swap(m_data[idxRehash], vecT);
|
||||
|
||||
for (auto &v : vecT)
|
||||
insert(v, true);
|
||||
|
||||
if (++steps > 1024)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
assert(idxRehash >= (m_data.size()/2)); // we should have finished rehashing by the time we need to grow again
|
||||
|
||||
++bits;
|
||||
m_data.resize(1ULL << bits);
|
||||
idxRehash = 0;
|
||||
RehashStep();
|
||||
}
|
||||
|
||||
template<typename T_VISITOR, typename T_MAX>
|
||||
inline bool enumerate_bucket(setiter &itr, const T_MAX &max, T_VISITOR &fn, long long *pcheckLimit)
|
||||
{
|
||||
auto &vec = m_data[itr.idxPrimary];
|
||||
for (; itr.idxSecondary < vec.size(); ++itr.idxSecondary)
|
||||
{
|
||||
// Assert we're ordered by T_MAX
|
||||
assert((itr.idxSecondary+1) >= vec.size()
|
||||
|| static_cast<T_MAX>(vec[itr.idxSecondary]) <= static_cast<T_MAX>(vec[itr.idxSecondary+1]));
|
||||
|
||||
(*pcheckLimit)--;
|
||||
if (max < static_cast<T_MAX>(*itr))
|
||||
return *pcheckLimit > 0;
|
||||
|
||||
size_t sizeBefore = vec.size();
|
||||
if (!fn(*itr))
|
||||
{
|
||||
itr.idxSecondary++; // we still visited this node
|
||||
return false;
|
||||
}
|
||||
if (vec.size() != sizeBefore)
|
||||
{
|
||||
assert(vec.size() == (sizeBefore-1)); // they may only remove the element passed to them
|
||||
--itr.idxSecondary; // they deleted the element
|
||||
}
|
||||
}
|
||||
vec.shrink_to_fit();
|
||||
return *pcheckLimit > 0;
|
||||
}
|
||||
};
|
||||
+17
-17
@@ -460,8 +460,8 @@ struct redisCommand sentinelcmds[] = {
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
* specific defaults. */
|
||||
void initSentinelConfig(void) {
|
||||
server.port = REDIS_SENTINEL_PORT;
|
||||
server.protected_mode = 0; /* Sentinel must be exposed. */
|
||||
g_pserver->port = REDIS_SENTINEL_PORT;
|
||||
g_pserver->protected_mode = 0; /* Sentinel must be exposed. */
|
||||
}
|
||||
|
||||
/* Perform the Sentinel mode initialization. */
|
||||
@@ -470,12 +470,12 @@ void initSentinel(void) {
|
||||
|
||||
/* Remove usual Redis commands from the command table, then just add
|
||||
* the SENTINEL command. */
|
||||
dictEmpty(server.commands,NULL);
|
||||
dictEmpty(g_pserver->commands,NULL);
|
||||
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
|
||||
int retval;
|
||||
struct redisCommand *cmd = sentinelcmds+j;
|
||||
|
||||
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
|
||||
retval = dictAdd(g_pserver->commands, sdsnew(cmd->name), cmd);
|
||||
serverAssert(retval == DICT_OK);
|
||||
|
||||
/* Translate the command string flags description into an actual
|
||||
@@ -504,14 +504,14 @@ void initSentinel(void) {
|
||||
void sentinelIsRunning(void) {
|
||||
int j;
|
||||
|
||||
if (server.configfile == NULL) {
|
||||
if (cserver.configfile == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Sentinel started without a config file. Exiting...");
|
||||
exit(1);
|
||||
} else if (access(server.configfile,W_OK) == -1) {
|
||||
} else if (access(cserver.configfile,W_OK) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Sentinel config file %s is not writable: %s. Exiting...",
|
||||
server.configfile,strerror(errno));
|
||||
cserver.configfile,strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ void sentinelEvent(int level, const char *type, sentinelRedisInstance *ri,
|
||||
}
|
||||
|
||||
/* Log the message if the log level allows it to be logged. */
|
||||
if (level >= server.verbosity)
|
||||
if (level >= cserver.verbosity)
|
||||
serverLog(level,"%s %s",type,msg);
|
||||
|
||||
/* Publish the message via Pub/Sub if it's not a debugging one. */
|
||||
@@ -1926,15 +1926,15 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
* On failure the function logs a warning on the Redis log. */
|
||||
void sentinelFlushConfig(void) {
|
||||
int fd = -1;
|
||||
int saved_hz = server.hz;
|
||||
int saved_hz = g_pserver->hz;
|
||||
int rewrite_status;
|
||||
|
||||
server.hz = CONFIG_DEFAULT_HZ;
|
||||
rewrite_status = rewriteConfig(server.configfile);
|
||||
server.hz = saved_hz;
|
||||
g_pserver->hz = CONFIG_DEFAULT_HZ;
|
||||
rewrite_status = rewriteConfig(cserver.configfile);
|
||||
g_pserver->hz = saved_hz;
|
||||
|
||||
if (rewrite_status == -1) goto werr;
|
||||
if ((fd = open(server.configfile,O_RDONLY)) == -1) goto werr;
|
||||
if ((fd = open(cserver.configfile,O_RDONLY)) == -1) goto werr;
|
||||
if (fsync(fd) == -1) goto werr;
|
||||
if (close(fd) == EOF) goto werr;
|
||||
return;
|
||||
@@ -2018,7 +2018,7 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
link->pending_commands = 0;
|
||||
link->cc_conn_time = mstime();
|
||||
link->cc->data = link;
|
||||
redisAeAttach(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->cc);
|
||||
redisAeAttach(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->cc);
|
||||
redisAsyncSetConnectCallback(link->cc,
|
||||
sentinelLinkEstablishedCallback);
|
||||
redisAsyncSetDisconnectCallback(link->cc,
|
||||
@@ -2042,7 +2042,7 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
|
||||
link->pc_conn_time = mstime();
|
||||
link->pc->data = link;
|
||||
redisAeAttach(server.rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->pc);
|
||||
redisAeAttach(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->pc);
|
||||
redisAsyncSetConnectCallback(link->pc,
|
||||
sentinelLinkEstablishedCallback);
|
||||
redisAsyncSetDisconnectCallback(link->pc,
|
||||
@@ -2585,7 +2585,7 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
announce_ip = ip;
|
||||
}
|
||||
announce_port = sentinel.announce_port ?
|
||||
sentinel.announce_port : server.port;
|
||||
sentinel.announce_port : g_pserver->port;
|
||||
|
||||
/* Format and send the Hello message. */
|
||||
snprintf(payload,sizeof(payload),
|
||||
@@ -4521,6 +4521,6 @@ void sentinelTimer(void) {
|
||||
* exactly continue to stay synchronized asking to be voted at the
|
||||
* same time again and again (resulting in nobody likely winning the
|
||||
* election because of split brain voting). */
|
||||
server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
|
||||
g_pserver->hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
|
||||
}
|
||||
|
||||
+1100
-886
File diff suppressed because it is too large.
Load diff
+677
-187
File diff suppressed because it is too large.
Load diff
+15
-15
@@ -72,7 +72,7 @@ slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long dur
|
||||
(unsigned long)
|
||||
sdslen(szFromObj(argv[j])) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
se->argv[j] = createObject(OBJ_STRING,s);
|
||||
} else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
|
||||
} else if (argv[j]->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT) {
|
||||
se->argv[j] = argv[j];
|
||||
} else {
|
||||
/* Here we need to dupliacate the string objects composing the
|
||||
@@ -87,7 +87,7 @@ slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long dur
|
||||
}
|
||||
se->time = time(NULL);
|
||||
se->duration = duration;
|
||||
se->id = server.slowlog_entry_id++;
|
||||
se->id = g_pserver->slowlog_entry_id++;
|
||||
se->peerid = sdsnew(getClientPeerId(c));
|
||||
se->cname = c->name ? sdsnew(szFromObj(c->name)) : sdsempty();
|
||||
return se;
|
||||
@@ -97,7 +97,7 @@ slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long dur
|
||||
* function matches the one of the 'free' method of adlist.c.
|
||||
*
|
||||
* This function will take care to release all the retained object. */
|
||||
void slowlogFreeEntry(void *septr) {
|
||||
void slowlogFreeEntry(const void *septr) {
|
||||
slowlogEntry *se = (slowlogEntry*)septr;
|
||||
int j;
|
||||
|
||||
@@ -112,29 +112,29 @@ void slowlogFreeEntry(void *septr) {
|
||||
/* Initialize the slow log. This function should be called a single time
|
||||
* at server startup. */
|
||||
void slowlogInit(void) {
|
||||
server.slowlog = listCreate();
|
||||
server.slowlog_entry_id = 0;
|
||||
listSetFreeMethod(server.slowlog,slowlogFreeEntry);
|
||||
g_pserver->slowlog = listCreate();
|
||||
g_pserver->slowlog_entry_id = 0;
|
||||
listSetFreeMethod(g_pserver->slowlog,slowlogFreeEntry);
|
||||
}
|
||||
|
||||
/* Push a new entry into the slow log.
|
||||
* This function will make sure to trim the slow log accordingly to the
|
||||
* configured max length. */
|
||||
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
|
||||
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
|
||||
if (duration >= server.slowlog_log_slower_than)
|
||||
listAddNodeHead(server.slowlog,
|
||||
if (g_pserver->slowlog_log_slower_than < 0) return; /* Slowlog disabled */
|
||||
if (duration >= g_pserver->slowlog_log_slower_than)
|
||||
listAddNodeHead(g_pserver->slowlog,
|
||||
slowlogCreateEntry(c,argv,argc,duration));
|
||||
|
||||
/* Remove old entries if needed. */
|
||||
while (listLength(server.slowlog) > server.slowlog_max_len)
|
||||
listDelNode(server.slowlog,listLast(server.slowlog));
|
||||
while (listLength(g_pserver->slowlog) > g_pserver->slowlog_max_len)
|
||||
listDelNode(g_pserver->slowlog,listLast(g_pserver->slowlog));
|
||||
}
|
||||
|
||||
/* Remove all the entries from the current slow log. */
|
||||
void slowlogReset(void) {
|
||||
while (listLength(server.slowlog) > 0)
|
||||
listDelNode(server.slowlog,listLast(server.slowlog));
|
||||
while (listLength(g_pserver->slowlog) > 0)
|
||||
listDelNode(g_pserver->slowlog,listLast(g_pserver->slowlog));
|
||||
}
|
||||
|
||||
/* The SLOWLOG command. Implements all the subcommands needed to handle the
|
||||
@@ -154,7 +154,7 @@ NULL
|
||||
slowlogReset();
|
||||
addReply(c,shared.ok);
|
||||
} else if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"len")) {
|
||||
addReplyLongLong(c,listLength(server.slowlog));
|
||||
addReplyLongLong(c,listLength(g_pserver->slowlog));
|
||||
} else if ((c->argc == 2 || c->argc == 3) &&
|
||||
!strcasecmp(szFromObj(c->argv[1]),"get"))
|
||||
{
|
||||
@@ -168,7 +168,7 @@ NULL
|
||||
getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
listRewind(server.slowlog,&li);
|
||||
listRewind(g_pserver->slowlog,&li);
|
||||
totentries = addReplyDeferredLen(c);
|
||||
while(count-- && (ln = listNext(&li))) {
|
||||
int j;
|
||||
|
||||
+19
-18
@@ -61,7 +61,8 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
|
||||
robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag) {
|
||||
char *p, *f, *k;
|
||||
sds spat, ssub;
|
||||
robj *keyobj, *fieldobj = NULL, *o;
|
||||
robj *keyobj, *fieldobj = NULL;
|
||||
robj_roptr o;
|
||||
int prefixlen, sublen, postfixlen, fieldlen;
|
||||
|
||||
/* If the pattern is "#" return the substitution object itself in order
|
||||
@@ -110,7 +111,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag)
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
else
|
||||
o = lookupKeyWrite(db,keyobj);
|
||||
if (o == NULL) goto noobj;
|
||||
if (o == nullptr) goto noobj;
|
||||
|
||||
if (fieldobj) {
|
||||
if (o->type != OBJ_HASH) goto noobj;
|
||||
@@ -127,7 +128,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag)
|
||||
}
|
||||
decrRefCount(keyobj);
|
||||
if (fieldobj) decrRefCount(fieldobj);
|
||||
return o;
|
||||
return o.unsafe_robjcast();
|
||||
|
||||
noobj:
|
||||
decrRefCount(keyobj);
|
||||
@@ -142,7 +143,7 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
const redisSortObject *so1 = (redisSortObject*)s1, *so2 = (redisSortObject*)s2;
|
||||
int cmp;
|
||||
|
||||
if (!server.sort_alpha) {
|
||||
if (!g_pserver->sort_alpha) {
|
||||
/* Numeric sorting. Here it's trivial as we precomputed scores */
|
||||
if (so1->u.score > so2->u.score) {
|
||||
cmp = 1;
|
||||
@@ -156,7 +157,7 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
}
|
||||
} else {
|
||||
/* Alphanumeric sorting */
|
||||
if (server.sort_bypattern) {
|
||||
if (g_pserver->sort_bypattern) {
|
||||
if (!so1->u.cmpobj || !so2->u.cmpobj) {
|
||||
/* At least one compare object is NULL */
|
||||
if (so1->u.cmpobj == so2->u.cmpobj)
|
||||
@@ -167,7 +168,7 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
cmp = 1;
|
||||
} else {
|
||||
/* We have both the objects, compare them. */
|
||||
if (server.sort_store) {
|
||||
if (g_pserver->sort_store) {
|
||||
cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj);
|
||||
} else {
|
||||
/* Here we can use strcoll() directly as we are sure that
|
||||
@@ -177,14 +178,14 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
}
|
||||
} else {
|
||||
/* Compare elements directly. */
|
||||
if (server.sort_store) {
|
||||
if (g_pserver->sort_store) {
|
||||
cmp = compareStringObjects(so1->obj,so2->obj);
|
||||
} else {
|
||||
cmp = collateStringObjects(so1->obj,so2->obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return server.sort_desc ? -cmp : cmp;
|
||||
return g_pserver->sort_desc ? -cmp : cmp;
|
||||
}
|
||||
|
||||
/* The SORT command is the most complex command in Redis. Warning: this code
|
||||
@@ -238,7 +239,7 @@ void sortCommand(client *c) {
|
||||
} else {
|
||||
/* If BY is specified with a real patter, we can't accept
|
||||
* it in cluster mode. */
|
||||
if (server.cluster_enabled) {
|
||||
if (g_pserver->cluster_enabled) {
|
||||
addReplyError(c,"BY option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
@@ -246,7 +247,7 @@ void sortCommand(client *c) {
|
||||
}
|
||||
j++;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"get") && leftargs >= 1) {
|
||||
if (server.cluster_enabled) {
|
||||
if (g_pserver->cluster_enabled) {
|
||||
addReplyError(c,"GET option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
@@ -271,7 +272,7 @@ void sortCommand(client *c) {
|
||||
|
||||
/* Lookup the key to sort. It must be of the right types */
|
||||
if (storekey)
|
||||
sortval = lookupKeyRead(c->db,c->argv[1]);
|
||||
sortval = lookupKeyRead(c->db,c->argv[1]).unsafe_robjcast();
|
||||
else
|
||||
sortval = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (sortval && sortval->type != OBJ_SET &&
|
||||
@@ -474,7 +475,7 @@ void sortCommand(client *c) {
|
||||
|
||||
vector[j].u.score = strtod(szFromObj(byval),&eptr);
|
||||
if (eptr[0] != '\0' || errno == ERANGE ||
|
||||
isnan(vector[j].u.score))
|
||||
std::isnan(vector[j].u.score))
|
||||
{
|
||||
int_conversion_error = 1;
|
||||
}
|
||||
@@ -495,10 +496,10 @@ void sortCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
server.sort_desc = desc;
|
||||
server.sort_alpha = alpha;
|
||||
server.sort_bypattern = sortby ? 1 : 0;
|
||||
server.sort_store = storekey ? 1 : 0;
|
||||
g_pserver->sort_desc = desc;
|
||||
g_pserver->sort_alpha = alpha;
|
||||
g_pserver->sort_bypattern = sortby ? 1 : 0;
|
||||
g_pserver->sort_store = storekey ? 1 : 0;
|
||||
if (sortby && (start != 0 || end != vectorlen-1))
|
||||
pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
|
||||
else
|
||||
@@ -573,11 +574,11 @@ void sortCommand(client *c) {
|
||||
setKey(c->db,storekey,sobj);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"sortstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += outputlen;
|
||||
g_pserver->dirty += outputlen;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
decrRefCount(sobj);
|
||||
addReplyLongLong(c,outputlen);
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
/* ----------------- Blocking sockets I/O with timeouts --------------------- */
|
||||
|
||||
/* Redis performs most of the I/O in a nonblocking way, with the exception
|
||||
* of the SYNC command where the slave does it in a blocking way, and
|
||||
* of the SYNC command where the replica does it in a blocking way, and
|
||||
* the MIGRATE command that must be blocking in order to be atomic from the
|
||||
* point of view of the two instances (one migrating the key and one receiving
|
||||
* the key). This is why need the following blocking I/O functions.
|
||||
|
||||
+45
-45
@@ -44,7 +44,7 @@ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
if (sdsEncodedObject(argv[i]) &&
|
||||
sdslen(szFromObj(argv[i])) > server.hash_max_ziplist_value)
|
||||
sdslen(szFromObj(argv[i])) > g_pserver->hash_max_ziplist_value)
|
||||
{
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
@@ -54,8 +54,8 @@ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
|
||||
/* Get the value from a ziplist encoded hash, identified by field.
|
||||
* Returns -1 when the field cannot be found. */
|
||||
int hashTypeGetFromZiplist(robj *o, sds field,
|
||||
unsigned char **vstr,
|
||||
int hashTypeGetFromZiplist(robj_roptr o, const char *field,
|
||||
const unsigned char **vstr,
|
||||
unsigned int *vlen,
|
||||
long long *vll)
|
||||
{
|
||||
@@ -64,7 +64,7 @@ int hashTypeGetFromZiplist(robj *o, sds field,
|
||||
|
||||
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
|
||||
|
||||
zl = (unsigned char*)ptrFromObj(o);
|
||||
zl = (unsigned char*)(ptrFromObj(o));
|
||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
|
||||
@@ -76,7 +76,7 @@ int hashTypeGetFromZiplist(robj *o, sds field,
|
||||
}
|
||||
|
||||
if (vptr != NULL) {
|
||||
ret = ziplistGet(vptr, vstr, vlen, vll);
|
||||
ret = ziplistGet(vptr, (unsigned char**)vstr, vlen, vll);
|
||||
serverAssert(ret);
|
||||
return 0;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ int hashTypeGetFromZiplist(robj *o, sds field,
|
||||
/* Get the value from a hash table encoded hash, identified by field.
|
||||
* Returns NULL when the field cannot be found, otherwise the SDS value
|
||||
* is returned. */
|
||||
sds hashTypeGetFromHashTable(robj *o, sds field) {
|
||||
const char *hashTypeGetFromHashTable(robj_roptr o, const char *field) {
|
||||
dictEntry *de;
|
||||
|
||||
serverAssert(o->encoding == OBJ_ENCODING_HT);
|
||||
@@ -106,15 +106,15 @@ sds hashTypeGetFromHashTable(robj *o, sds field) {
|
||||
* If *vll is populated *vstr is set to NULL, so the caller
|
||||
* can always check the function return by checking the return value
|
||||
* for C_OK and checking if vll (or vstr) is NULL. */
|
||||
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
||||
int hashTypeGetValue(robj_roptr o, sds field, const unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
*vstr = NULL;
|
||||
if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0)
|
||||
return C_OK;
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds value;
|
||||
const char *value;
|
||||
if ((value = hashTypeGetFromHashTable(o, field)) != NULL) {
|
||||
*vstr = (unsigned char*) value;
|
||||
*vstr = (const unsigned char*) value;
|
||||
*vlen = sdslen(value);
|
||||
return C_OK;
|
||||
}
|
||||
@@ -128,8 +128,8 @@ int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vle
|
||||
* interaction with the hash type outside t_hash.c.
|
||||
* The function returns NULL if the field is not found in the hash. Otherwise
|
||||
* a newly allocated string object with the value is returned. */
|
||||
robj *hashTypeGetValueObject(robj *o, sds field) {
|
||||
unsigned char *vstr;
|
||||
robj *hashTypeGetValueObject(robj_roptr o, sds field) {
|
||||
const unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
|
||||
@@ -141,17 +141,17 @@ robj *hashTypeGetValueObject(robj *o, sds field) {
|
||||
/* Higher level function using hashTypeGet*() to return the length of the
|
||||
* object associated with the requested field, or 0 if the field does not
|
||||
* exist. */
|
||||
size_t hashTypeGetValueLength(robj *o, sds field) {
|
||||
size_t hashTypeGetValueLength(robj_roptr o, const char *field) {
|
||||
size_t len = 0;
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
const unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
|
||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
|
||||
len = vstr ? vlen : sdigits10(vll);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds aux;
|
||||
const char *aux;
|
||||
|
||||
if ((aux = hashTypeGetFromHashTable(o, field)) != NULL)
|
||||
len = sdslen(aux);
|
||||
@@ -163,9 +163,9 @@ size_t hashTypeGetValueLength(robj *o, sds field) {
|
||||
|
||||
/* Test if the specified field exists in the given hash. Returns 1 if the field
|
||||
* exists, and 0 when it doesn't. */
|
||||
int hashTypeExists(robj *o, sds field) {
|
||||
int hashTypeExists(robj_roptr o, const char *field) {
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
const unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
|
||||
@@ -234,7 +234,7 @@ int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
||||
o->m_ptr = zl;
|
||||
|
||||
/* Check if the ziplist needs to be converted to a hash table */
|
||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
||||
if (hashTypeLength(o) > g_pserver->hash_max_ziplist_entries)
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
dictEntry *de = dictFind((dict*)ptrFromObj(o),field);
|
||||
@@ -308,7 +308,7 @@ int hashTypeDelete(robj *o, sds field) {
|
||||
}
|
||||
|
||||
/* Return the number of elements in a hash. */
|
||||
unsigned long hashTypeLength(const robj *o) {
|
||||
unsigned long hashTypeLength(robj_roptr o) {
|
||||
unsigned long length = ULONG_MAX;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -321,7 +321,7 @@ unsigned long hashTypeLength(const robj *o) {
|
||||
return length;
|
||||
}
|
||||
|
||||
hashTypeIterator *hashTypeInitIterator(robj *subject) {
|
||||
hashTypeIterator *hashTypeInitIterator(robj_roptr subject) {
|
||||
hashTypeIterator *hi = (hashTypeIterator*)zmalloc(sizeof(hashTypeIterator), MALLOC_LOCAL);
|
||||
hi->subject = subject;
|
||||
hi->encoding = subject->encoding;
|
||||
@@ -523,7 +523,7 @@ void hsetnxCommand(client *c) {
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,14 +553,14 @@ void hsetCommand(client *c) {
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
void hincrbyCommand(client *c) {
|
||||
long long value, incr, oldvalue;
|
||||
robj *o;
|
||||
sds newstr;
|
||||
unsigned char *vstr;
|
||||
const unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
|
||||
@@ -588,7 +588,7 @@ void hincrbyCommand(client *c) {
|
||||
addReplyLongLong(c,value);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
void hincrbyfloatCommand(client *c) {
|
||||
@@ -596,7 +596,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
long long ll;
|
||||
robj *o;
|
||||
sds newstr;
|
||||
unsigned char *vstr;
|
||||
const unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
|
||||
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
|
||||
@@ -615,7 +615,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
if (isnan(value) || isinf(value)) {
|
||||
if (std::isnan(value) || std::isinf(value)) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
@@ -627,7 +627,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
addReplyBulkCBuffer(c,buf,len);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
|
||||
* in order to make sure that differences in float pricision or formatting
|
||||
@@ -641,16 +641,16 @@ void hincrbyfloatCommand(client *c) {
|
||||
decrRefCount(newobj);
|
||||
}
|
||||
|
||||
static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
static void addHashFieldToReply(client *c, robj_roptr o, sds field) {
|
||||
int ret;
|
||||
|
||||
if (o == NULL) {
|
||||
if (o == nullptr) {
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
const unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
|
||||
@@ -666,7 +666,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
}
|
||||
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds value = hashTypeGetFromHashTable(o, field);
|
||||
const char* value = hashTypeGetFromHashTable(o, field);
|
||||
if (value == NULL)
|
||||
addReplyNull(c);
|
||||
else
|
||||
@@ -677,22 +677,22 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
}
|
||||
|
||||
void hgetCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == nullptr ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addHashFieldToReply(c, o, szFromObj(c->argv[2]));
|
||||
}
|
||||
|
||||
void hmgetCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
int i;
|
||||
|
||||
/* Don't abort when the key cannot be found. Non-existing keys are empty
|
||||
* hashes, where HMGET should respond with a series of null bulks. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (o != NULL && o->type != OBJ_HASH) {
|
||||
if (o != nullptr && o->type != OBJ_HASH) {
|
||||
addReply(c, shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
@@ -726,24 +726,24 @@ void hdelCommand(client *c) {
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
void hlenCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addReplyLongLong(c,hashTypeLength(o));
|
||||
}
|
||||
|
||||
void hstrlenCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
addReplyLongLong(c,hashTypeGetValueLength(o,szFromObj(c->argv[2])));
|
||||
}
|
||||
@@ -768,11 +768,11 @@ static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int wh
|
||||
}
|
||||
|
||||
void genericHgetallCommand(client *c, int flags) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
hashTypeIterator *hi;
|
||||
int length, count = 0;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == NULL
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == nullptr
|
||||
|| checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
/* We return a map if the user requested keys and values, like in the
|
||||
@@ -816,19 +816,19 @@ void hgetallCommand(client *c) {
|
||||
}
|
||||
|
||||
void hexistsCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
robj_roptr o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addReply(c, hashTypeExists(o,szFromObj(c->argv[2])) ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
void hscanCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == nullptr ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
scanGenericCommand(c,o,cursor);
|
||||
}
|
||||
+30
-30
@@ -71,16 +71,16 @@ robj *listTypePop(robj *subject, int where) {
|
||||
return value;
|
||||
}
|
||||
|
||||
unsigned long listTypeLength(const robj *subject) {
|
||||
unsigned long listTypeLength(robj_roptr subject) {
|
||||
if (subject->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
return quicklistCount((quicklist*)ptrFromObj(subject));
|
||||
return quicklistCount((const quicklist*)ptrFromObj(subject));
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize an iterator at the specified index. */
|
||||
listTypeIterator *listTypeInitIterator(robj *subject, long index,
|
||||
listTypeIterator *listTypeInitIterator(robj_roptr subject, long index,
|
||||
unsigned char direction) {
|
||||
listTypeIterator *li = (listTypeIterator*)zmalloc(sizeof(listTypeIterator), MALLOC_LOCAL);
|
||||
li->subject = subject;
|
||||
@@ -181,8 +181,8 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
serverAssertWithInfo(NULL,subject,subject->encoding==OBJ_ENCODING_ZIPLIST);
|
||||
|
||||
if (enc == OBJ_ENCODING_QUICKLIST) {
|
||||
size_t zlen = server.list_max_ziplist_size;
|
||||
int depth = server.list_compress_depth;
|
||||
size_t zlen = g_pserver->list_max_ziplist_size;
|
||||
int depth = g_pserver->list_compress_depth;
|
||||
subject->m_ptr = quicklistCreateFromZiplist(zlen, depth, (unsigned char*)ptrFromObj(subject));
|
||||
subject->encoding = OBJ_ENCODING_QUICKLIST;
|
||||
} else {
|
||||
@@ -206,8 +206,8 @@ void pushGenericCommand(client *c, int where) {
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (!lobj) {
|
||||
lobj = createQuicklistObject();
|
||||
quicklistSetOptions((quicklist*)ptrFromObj(lobj), server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
quicklistSetOptions((quicklist*)ptrFromObj(lobj), g_pserver->list_max_ziplist_size,
|
||||
g_pserver->list_compress_depth);
|
||||
dbAdd(c->db,c->argv[1],lobj);
|
||||
}
|
||||
listTypePush(lobj,c->argv[j],where);
|
||||
@@ -220,7 +220,7 @@ void pushGenericCommand(client *c, int where) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
}
|
||||
server.dirty += pushed;
|
||||
g_pserver->dirty += pushed;
|
||||
}
|
||||
|
||||
void lpushCommand(client *c) {
|
||||
@@ -250,7 +250,7 @@ void pushxGenericCommand(client *c, int where) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
}
|
||||
server.dirty += pushed;
|
||||
g_pserver->dirty += pushed;
|
||||
}
|
||||
|
||||
void lpushxCommand(client *c) {
|
||||
@@ -295,7 +295,7 @@ void linsertCommand(client *c) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"linsert",
|
||||
c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
addReplyLongLong(c,-1);
|
||||
@@ -306,14 +306,14 @@ void linsertCommand(client *c) {
|
||||
}
|
||||
|
||||
void llenCommand(client *c) {
|
||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
|
||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||
robj_roptr o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
|
||||
if (o == nullptr || checkType(c,o,OBJ_LIST)) return;
|
||||
addReplyLongLong(c,listTypeLength(o));
|
||||
}
|
||||
|
||||
void lindexCommand(client *c) {
|
||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
|
||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||
robj_roptr o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
|
||||
if (o == nullptr || checkType(c,o,OBJ_LIST)) return;
|
||||
long index;
|
||||
robj *value = NULL;
|
||||
|
||||
@@ -331,7 +331,7 @@ void lindexCommand(client *c) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
addReplyNull(c,shared.nullbulk);
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
@@ -357,7 +357,7 @@ void lsetCommand(client *c) {
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
@@ -383,7 +383,7 @@ void popGenericCommand(client *c, int where) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,13 +396,13 @@ void rpopCommand(client *c) {
|
||||
}
|
||||
|
||||
void lrangeCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
long start, end, llen, rangelen;
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == NULL
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == nullptr
|
||||
|| checkType(c,o,OBJ_LIST)) return;
|
||||
llen = listTypeLength(o);
|
||||
|
||||
@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
addReplyNull(c);
|
||||
addReplyNull(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
if (end >= llen) end = llen-1;
|
||||
@@ -483,7 +483,7 @@ void ltrimCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ void lremCommand(client *c) {
|
||||
while (listTypeNext(li,&entry)) {
|
||||
if (listTypeEqual(&entry,obj)) {
|
||||
listTypeDelete(li, &entry);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
removed++;
|
||||
if (toremove && removed == toremove) break;
|
||||
}
|
||||
@@ -551,8 +551,8 @@ static void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *val
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dstobj) {
|
||||
dstobj = createQuicklistObject();
|
||||
quicklistSetOptions((quicklist*)ptrFromObj(dstobj), server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
quicklistSetOptions((quicklist*)ptrFromObj(dstobj), g_pserver->list_max_ziplist_size,
|
||||
g_pserver->list_compress_depth);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
@@ -595,7 +595,7 @@ void rpoplpushCommand(client *c) {
|
||||
}
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
if (c->cmd->proc == brpoplpushCommand) {
|
||||
rewriteClientCommandVector(c,3,shared.rpoplpush,c->argv[1],c->argv[2]);
|
||||
}
|
||||
@@ -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 fron the head (BLPOP) or tail (BRPOP) so that
|
||||
* 'value' element was popped from 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
|
||||
@@ -636,7 +636,7 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
shared.rpop;
|
||||
argv[1] = key;
|
||||
propagate((where == LIST_HEAD) ?
|
||||
server.lpopCommand : server.rpopCommand,
|
||||
cserver.lpopCommand : cserver.rpopCommand,
|
||||
db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
|
||||
/* BRPOP/BLPOP */
|
||||
@@ -659,7 +659,7 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
/* Propagate the RPOP operation. */
|
||||
argv[0] = shared.rpop;
|
||||
argv[1] = key;
|
||||
propagate(server.rpopCommand,
|
||||
propagate(cserver.rpopCommand,
|
||||
db->id,argv,2,
|
||||
PROPAGATE_AOF|
|
||||
PROPAGATE_REPL);
|
||||
@@ -669,7 +669,7 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
argv[0] = shared.lpush;
|
||||
argv[1] = dstkey;
|
||||
argv[2] = value;
|
||||
propagate(server.lpushCommand,
|
||||
propagate(cserver.lpushCommand,
|
||||
db->id,argv,3,
|
||||
PROPAGATE_AOF|
|
||||
PROPAGATE_REPL);
|
||||
@@ -721,7 +721,7 @@ void blockingPopGenericCommand(client *c, int where) {
|
||||
c->argv[j],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* Replicate it as an [LR]POP instead of B[LR]POP. */
|
||||
rewriteClientCommandVector(c,2,
|
||||
|
||||
+48
-43
@@ -39,7 +39,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
/* Factory method to return a set that *can* hold "value". When the object has
|
||||
* an integer-encodable value, an intset will be returned. Otherwise a regular
|
||||
* hash table. */
|
||||
robj *setTypeCreate(sds value) {
|
||||
robj *setTypeCreate(const char *value) {
|
||||
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
|
||||
return createIntsetObject();
|
||||
return createSetObject();
|
||||
@@ -49,11 +49,11 @@ robj *setTypeCreate(sds value) {
|
||||
*
|
||||
* If the value was already member of the set, nothing is done and 0 is
|
||||
* returned, otherwise the new element is added and 1 is returned. */
|
||||
int setTypeAdd(robj *subject, sds value) {
|
||||
int setTypeAdd(robj *subject, const char *value) {
|
||||
long long llval;
|
||||
if (subject->encoding == OBJ_ENCODING_HT) {
|
||||
dict *ht = (dict*)subject->m_ptr;
|
||||
dictEntry *de = dictAddRaw(ht,value,NULL);
|
||||
dictEntry *de = dictAddRaw(ht,(char*)value,NULL);
|
||||
if (de) {
|
||||
dictSetKey(ht,de,sdsdup(value));
|
||||
dictSetVal(ht,de,NULL);
|
||||
@@ -66,7 +66,7 @@ int setTypeAdd(robj *subject, sds value) {
|
||||
if (success) {
|
||||
/* Convert to regular set when the intset contains
|
||||
* too many entries. */
|
||||
if (intsetLen((intset*)subject->m_ptr) > server.set_max_intset_entries)
|
||||
if (intsetLen((intset*)subject->m_ptr) > g_pserver->set_max_intset_entries)
|
||||
setTypeConvert(subject,OBJ_ENCODING_HT);
|
||||
return 1;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ int setTypeAdd(robj *subject, sds value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setTypeRemove(robj *setobj, sds value) {
|
||||
int setTypeRemove(robj *setobj, const char *value) {
|
||||
long long llval;
|
||||
if (setobj->encoding == OBJ_ENCODING_HT) {
|
||||
if (dictDelete((dict*)setobj->m_ptr,value) == DICT_OK) {
|
||||
@@ -104,7 +104,7 @@ int setTypeRemove(robj *setobj, sds value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setTypeIsMember(robj *subject, sds value) {
|
||||
int setTypeIsMember(robj_roptr subject, const char *value) {
|
||||
long long llval;
|
||||
if (subject->encoding == OBJ_ENCODING_HT) {
|
||||
return dictFind((dict*)subject->m_ptr,value) != NULL;
|
||||
@@ -118,7 +118,7 @@ int setTypeIsMember(robj *subject, sds value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
setTypeIterator *setTypeInitIterator(robj *subject) {
|
||||
setTypeIterator *setTypeInitIterator(robj_roptr subject) {
|
||||
setTypeIterator *si = (setTypeIterator*)zmalloc(sizeof(setTypeIterator), MALLOC_LOCAL);
|
||||
si->subject = subject;
|
||||
si->encoding = subject->encoding;
|
||||
@@ -151,7 +151,7 @@ void setTypeReleaseIterator(setTypeIterator *si) {
|
||||
* used field with values which are easy to trap if misused.
|
||||
*
|
||||
* When there are no longer elements -1 is returned. */
|
||||
int setTypeNext(setTypeIterator *si, sds *sdsele, int64_t *llele) {
|
||||
int setTypeNext(setTypeIterator *si, const char **sdsele, int64_t *llele) {
|
||||
if (si->encoding == OBJ_ENCODING_HT) {
|
||||
dictEntry *de = dictNext(si->di);
|
||||
if (de == NULL) return -1;
|
||||
@@ -176,7 +176,7 @@ int setTypeNext(setTypeIterator *si, sds *sdsele, int64_t *llele) {
|
||||
* an issue. */
|
||||
sds setTypeNextObject(setTypeIterator *si) {
|
||||
int64_t intele;
|
||||
sds sdsele;
|
||||
const char *sdsele;
|
||||
int encoding;
|
||||
|
||||
encoding = setTypeNext(si,&sdsele,&intele);
|
||||
@@ -219,7 +219,12 @@ int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
|
||||
return setobj->encoding;
|
||||
}
|
||||
|
||||
unsigned long setTypeSize(const robj *subject) {
|
||||
int setTypeRandomElement(robj_roptr setobj, const char **sdsele, const int64_t *llele)
|
||||
{
|
||||
return setTypeRandomElement(setobj.unsafe_robjcast(), (sds*)sdsele, const_cast<int64_t*>(llele));
|
||||
}
|
||||
|
||||
unsigned long setTypeSize(robj_roptr subject) {
|
||||
if (subject->encoding == OBJ_ENCODING_HT) {
|
||||
return dictSize((const dict*)subject->m_ptr);
|
||||
} else if (subject->encoding == OBJ_ENCODING_INTSET) {
|
||||
@@ -240,7 +245,7 @@ void setTypeConvert(robj *setobj, int enc) {
|
||||
if (enc == OBJ_ENCODING_HT) {
|
||||
int64_t intele;
|
||||
dict *d = dictCreate(&setDictType,NULL);
|
||||
sds element;
|
||||
const char *element;
|
||||
|
||||
/* Presize the dict to avoid rehashing */
|
||||
dictExpand(d,intsetLen((intset*)setobj->m_ptr));
|
||||
@@ -248,8 +253,8 @@ void setTypeConvert(robj *setobj, int enc) {
|
||||
/* To add the elements we extract integers and create redis objects */
|
||||
si = setTypeInitIterator(setobj);
|
||||
while (setTypeNext(si,&element,&intele) != -1) {
|
||||
element = sdsfromlonglong(intele);
|
||||
serverAssert(dictAdd(d,element,NULL) == DICT_OK);
|
||||
sds elementNew = sdsfromlonglong(intele);
|
||||
serverAssert(dictAdd(d,elementNew,NULL) == DICT_OK);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
@@ -283,7 +288,7 @@ void saddCommand(client *c) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
|
||||
}
|
||||
server.dirty += added;
|
||||
g_pserver->dirty += added;
|
||||
addReplyLongLong(c,added);
|
||||
}
|
||||
|
||||
@@ -310,7 +315,7 @@ void sremCommand(client *c) {
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -360,20 +365,20 @@ void smoveCommand(client *c) {
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,szFromObj(ele))) {
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
}
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
|
||||
void sismemberCommand(client *c) {
|
||||
robj *set;
|
||||
robj_roptr set;
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
if (setTypeIsMember(set,szFromObj(c->argv[2])))
|
||||
@@ -383,9 +388,9 @@ void sismemberCommand(client *c) {
|
||||
}
|
||||
|
||||
void scardCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_SET)) return;
|
||||
|
||||
addReplyLongLong(c,setTypeSize(o));
|
||||
@@ -429,7 +434,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Generate an SPOP keyspace notification */
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||
server.dirty += count;
|
||||
g_pserver->dirty += count;
|
||||
|
||||
/* CASE 1:
|
||||
* The number of requested elements is greater than or equal to
|
||||
@@ -445,7 +450,7 @@ void spopWithCountCommand(client *c) {
|
||||
/* Propagate this command as an DEL operation */
|
||||
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -458,7 +463,7 @@ void spopWithCountCommand(client *c) {
|
||||
addReplySetLen(c,count);
|
||||
|
||||
/* Common iteration vars. */
|
||||
sds sdsele;
|
||||
const char *sdsele;
|
||||
robj *objele;
|
||||
int encoding;
|
||||
int64_t llele;
|
||||
@@ -487,7 +492,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
|
||||
alsoPropagate(cserver.sremCommand,c->db->id,propargv,3,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
}
|
||||
@@ -530,7 +535,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
|
||||
alsoPropagate(cserver.sremCommand,c->db->id,propargv,3,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
}
|
||||
@@ -547,7 +552,7 @@ void spopWithCountCommand(client *c) {
|
||||
decrRefCount(propargv[0]);
|
||||
preventCommandPropagation(c);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
void spopCommand(client *c) {
|
||||
@@ -600,7 +605,7 @@ void spopCommand(client *c) {
|
||||
|
||||
/* Set has been modified */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
/* handle the "SRANDMEMBER key <count>" variant. The normal version of the
|
||||
@@ -615,8 +620,8 @@ void srandmemberWithCountCommand(client *c) {
|
||||
long l;
|
||||
unsigned long count, size;
|
||||
int uniq = 1;
|
||||
robj *set;
|
||||
sds ele;
|
||||
robj_roptr set;
|
||||
const char *ele;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
@@ -633,7 +638,7 @@ void srandmemberWithCountCommand(client *c) {
|
||||
}
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
== nullptr || checkType(c,set,OBJ_SET)) return;
|
||||
size = setTypeSize(set);
|
||||
|
||||
/* If count is zero, serve it ASAP to avoid special cases later. */
|
||||
@@ -747,8 +752,8 @@ void srandmemberWithCountCommand(client *c) {
|
||||
}
|
||||
|
||||
void srandmemberCommand(client *c) {
|
||||
robj *set;
|
||||
sds ele;
|
||||
robj_roptr set;
|
||||
const char *ele;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
@@ -761,7 +766,7 @@ void srandmemberCommand(client *c) {
|
||||
}
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
== nullptr || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == OBJ_ENCODING_INTSET) {
|
||||
@@ -794,7 +799,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
robj **sets = (robj**)zmalloc(sizeof(robj*)*setnum, MALLOC_SHARED);
|
||||
setTypeIterator *si;
|
||||
robj *dstset = NULL;
|
||||
sds elesds;
|
||||
const char *elesds;
|
||||
int64_t intobj;
|
||||
void *replylen = NULL;
|
||||
unsigned long j, cardinality = 0;
|
||||
@@ -803,13 +808,13 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
lookupKeyWrite(c->db,setkeys[j]) :
|
||||
lookupKeyRead(c->db,setkeys[j]);
|
||||
lookupKeyRead(c->db,setkeys[j]).unsafe_robjcast();
|
||||
if (!setobj) {
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
@@ -912,7 +917,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
setDeferredSetLen(c,replylen,cardinality);
|
||||
}
|
||||
@@ -943,7 +948,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
lookupKeyWrite(c->db,setkeys[j]) :
|
||||
lookupKeyRead(c->db,setkeys[j]);
|
||||
lookupKeyRead(c->db,setkeys[j]).unsafe_robjcast();
|
||||
if (!setobj) {
|
||||
sets[j] = NULL;
|
||||
continue;
|
||||
@@ -1067,7 +1072,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
sdsfree(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
server.lazyfree_lazy_server_del ? freeObjAsync(dstset) :
|
||||
g_pserver->lazyfree_lazy_server_del ? freeObjAsync(dstset) :
|
||||
decrRefCount(dstset);
|
||||
} else {
|
||||
/* If we have a target key where to store the resulting set
|
||||
@@ -1087,7 +1092,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
zfree(sets);
|
||||
}
|
||||
@@ -1109,11 +1114,11 @@ void sdiffstoreCommand(client *c) {
|
||||
}
|
||||
|
||||
void sscanCommand(client *c) {
|
||||
robj *set;
|
||||
robj_roptr set;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return;
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == nullptr ||
|
||||
checkType(c,set,OBJ_SET)) return;
|
||||
scanGenericCommand(c,set,cursor);
|
||||
}
|
||||
+37
-37
@@ -241,18 +241,18 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* if we need to switch to the next one. 'lp' will be set to NULL if
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes > server.stream_node_max_bytes)
|
||||
if (g_pserver->stream_node_max_bytes &&
|
||||
lp_bytes > g_pserver->stream_node_max_bytes)
|
||||
{
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
} else if (g_pserver->stream_node_max_entries) {
|
||||
int64_t count = lpGetInteger(lpFirst(lp));
|
||||
if (count > server.stream_node_max_entries) lp = NULL;
|
||||
if (count > g_pserver->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 > g_pserver->stream_node_max_bytes) {
|
||||
master_id = id;
|
||||
streamEncodeID(rax_key,&id);
|
||||
/* Create the listpack having the master entry ID and fields. */
|
||||
@@ -492,14 +492,14 @@ void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamI
|
||||
streamEncodeID(si->start_key,start);
|
||||
} else {
|
||||
si->start_key[0] = 0;
|
||||
si->start_key[0] = 0;
|
||||
si->start_key[1] = 0;
|
||||
}
|
||||
|
||||
if (end) {
|
||||
streamEncodeID(si->end_key,end);
|
||||
} else {
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
si->end_key[1] = UINT64_MAX;
|
||||
}
|
||||
|
||||
/* Seek the correct node in the radix tree. */
|
||||
@@ -803,7 +803,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
* RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
|
||||
*
|
||||
* Note that JUSTID is useful in order to avoid that XCLAIM will do
|
||||
* useless work in the slave side, trying to fetch the stream item. */
|
||||
* useless work in the replica side, trying to fetch the stream item. */
|
||||
robj *argv[14];
|
||||
argv[0] = createStringObject("XCLAIM",6);
|
||||
argv[1] = key;
|
||||
@@ -819,7 +819,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
argv[11] = createStringObject("JUSTID",6);
|
||||
argv[12] = createStringObject("LASTID",6);
|
||||
argv[13] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
propagate(cserver.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[3]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -846,7 +846,7 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
propagate(cserver.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -1242,7 +1242,7 @@ void xaddCommand(client *c) {
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
if (maxlen >= 0) {
|
||||
/* Notify xtrim event if needed. */
|
||||
@@ -1260,13 +1260,13 @@ void xaddCommand(client *c) {
|
||||
|
||||
/* We need to signal to blocked clients that there is new data on this
|
||||
* stream. */
|
||||
if (server.blocked_clients_by_type[BLOCKED_STREAM])
|
||||
if (g_pserver->blocked_clients_by_type[BLOCKED_STREAM])
|
||||
signalKeyAsReady(c->db, c->argv[1]);
|
||||
}
|
||||
|
||||
/* XRANGE/XREVRANGE actual implementation. */
|
||||
void xrangeGenericCommand(client *c, int rev) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
stream *s;
|
||||
streamID startid, endid;
|
||||
long long count = -1;
|
||||
@@ -1293,7 +1293,7 @@ void xrangeGenericCommand(client *c, int rev) {
|
||||
}
|
||||
|
||||
/* Return the specified range to the user. */
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == nullptr ||
|
||||
checkType(c,o,OBJ_STREAM)) return;
|
||||
|
||||
s = (stream*)ptrFromObj(o);
|
||||
@@ -1318,8 +1318,8 @@ void xrevrangeCommand(client *c) {
|
||||
|
||||
/* XLEN */
|
||||
void xlenCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL
|
||||
robj_roptr o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr
|
||||
|| checkType(c,o,OBJ_STREAM)) return;
|
||||
stream *s = (stream*)ptrFromObj(o);
|
||||
addReplyLongLong(c,s->length);
|
||||
@@ -1419,14 +1419,14 @@ void xreadCommand(client *c) {
|
||||
* starting from now. */
|
||||
int id_idx = i - streams_arg - streams_count;
|
||||
robj *key = c->argv[i-streams_count];
|
||||
robj *o = lookupKeyRead(c->db,key);
|
||||
robj_roptr o = lookupKeyRead(c->db,key);
|
||||
if (o && checkType(c,o,OBJ_STREAM)) goto cleanup;
|
||||
streamCG *group = NULL;
|
||||
|
||||
/* If a group was specified, than we need to be sure that the
|
||||
* key and group actually exist. */
|
||||
if (groupname) {
|
||||
if (o == NULL ||
|
||||
if (o == nullptr ||
|
||||
(group = streamLookupCG((stream*)ptrFromObj(o),szFromObj(groupname))) == NULL)
|
||||
{
|
||||
addReplyErrorFormat(c, "-NOGROUP No such key '%s' or consumer "
|
||||
@@ -1475,8 +1475,8 @@ void xreadCommand(client *c) {
|
||||
|
||||
/* Try to serve the client synchronously. */
|
||||
for (int i = 0; i < streams_count; i++) {
|
||||
robj *o = lookupKeyRead(c->db,c->argv[streams_arg+i]);
|
||||
if (o == NULL) continue;
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[streams_arg+i]);
|
||||
if (o == nullptr) continue;
|
||||
stream *s = (stream*)ptrFromObj(o);
|
||||
streamID *gt = ids+i; /* ID must be greater than this. */
|
||||
int serve_synchronously = 0;
|
||||
@@ -1534,7 +1534,7 @@ void xreadCommand(client *c) {
|
||||
streamReplyWithRange(c,s,&start,NULL,count,0,
|
||||
groups ? groups[i] : NULL,
|
||||
consumer, flags, &spi);
|
||||
if (groups) server.dirty++;
|
||||
if (groups) g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1798,7 +1798,7 @@ NULL
|
||||
streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
|
||||
if (cg) {
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-create",
|
||||
c->argv[2],c->db->id);
|
||||
} else {
|
||||
@@ -1814,14 +1814,14 @@ NULL
|
||||
}
|
||||
cg->last_id = id;
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-setid",c->argv[2],c->db->id);
|
||||
} else if (!strcasecmp(opt,"DESTROY") && c->argc == 4) {
|
||||
if (cg) {
|
||||
raxRemove(s->cgroups,(unsigned char*)grpname,sdslen(grpname),NULL);
|
||||
streamFreeCG(cg);
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
|
||||
c->argv[2],c->db->id);
|
||||
} else {
|
||||
@@ -1832,7 +1832,7 @@ NULL
|
||||
* that were yet associated with such a consumer. */
|
||||
long long pending = streamDelConsumer(cg,szFromObj(c->argv[4]));
|
||||
addReplyLongLong(c,pending);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer",
|
||||
c->argv[2],c->db->id);
|
||||
} else if (!strcasecmp(opt,"HELP")) {
|
||||
@@ -1872,7 +1872,7 @@ void xsetidCommand(client *c) {
|
||||
}
|
||||
s->last_id = id;
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xsetid",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
@@ -1887,14 +1887,14 @@ void xsetidCommand(client *c) {
|
||||
*/
|
||||
void xackCommand(client *c) {
|
||||
streamCG *group = NULL;
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o) {
|
||||
if (checkType(c,o,OBJ_STREAM)) return; /* Type error. */
|
||||
group = streamLookupCG((stream*)ptrFromObj(o),szFromObj(c->argv[2]));
|
||||
}
|
||||
|
||||
/* No key or group? Nothing to ack. */
|
||||
if (o == NULL || group == NULL) {
|
||||
if (o == nullptr || group == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
@@ -1915,7 +1915,7 @@ void xackCommand(client *c) {
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
streamFreeNACK(nack);
|
||||
acknowledged++;
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
addReplyLongLong(c,acknowledged);
|
||||
@@ -1958,11 +1958,11 @@ void xpendingCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Lookup the key and the group inside the stream. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[1]);
|
||||
streamCG *group;
|
||||
|
||||
if (o && checkType(c,o,OBJ_STREAM)) return;
|
||||
if (o == NULL ||
|
||||
if (o == nullptr ||
|
||||
(group = streamLookupCG((stream*)ptrFromObj(o),szFromObj(groupname))) == NULL)
|
||||
{
|
||||
addReplyErrorFormat(c, "-NOGROUP No such key '%s' or consumer "
|
||||
@@ -2137,7 +2137,7 @@ void xpendingCommand(client *c) {
|
||||
* what messages it is now in charge of. */
|
||||
void xclaimCommand(client *c) {
|
||||
streamCG *group = NULL;
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[1]);
|
||||
long long minidle; /* Minimum idle time argument. */
|
||||
long long retrycount = -1; /* -1 means RETRYCOUNT option not given. */
|
||||
mstime_t deliverytime = -1; /* -1 means IDLE/TIME options not given. */
|
||||
@@ -2151,7 +2151,7 @@ void xclaimCommand(client *c) {
|
||||
|
||||
/* No key or group? Send an error given that the group creation
|
||||
* is mandatory. */
|
||||
if (o == NULL || group == NULL) {
|
||||
if (o == nullptr || group == NULL) {
|
||||
addReplyErrorFormat(c,"-NOGROUP No such key '%s' or "
|
||||
"consumer group '%s'", (char*)ptrFromObj(c->argv[1]),
|
||||
(char*)ptrFromObj(c->argv[2]));
|
||||
@@ -2309,12 +2309,12 @@ void xclaimCommand(client *c) {
|
||||
/* Propagate this change. */
|
||||
streamPropagateXCLAIM(c,c->argv[1],group,c->argv[2],c->argv[j],nack);
|
||||
propagate_last_id = 0; /* Will be propagated by XCLAIM itself. */
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
if (propagate_last_id) {
|
||||
streamPropagateGroupID(c,c->argv[1],group,c->argv[2]);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
setDeferredArrayLen(c,arraylenptr,arraylen);
|
||||
preventCommandPropagation(c);
|
||||
@@ -2352,7 +2352,7 @@ void xdelCommand(client *c) {
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -2429,7 +2429,7 @@ void xtrimCommand(client *c) {
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
|
||||
+22
-21
@@ -28,7 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h> /* isnan(), isinf() */
|
||||
#include <cmath> /* isnan(), isinf() */
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* String Commands
|
||||
@@ -84,8 +84,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
return;
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
||||
g_pserver->dirty++;
|
||||
if (expire) setExpire(c,c->db,key,nullptr,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"expire",key,c->db->id);
|
||||
@@ -155,9 +155,9 @@ void psetexCommand(client *c) {
|
||||
}
|
||||
|
||||
int getGenericCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == nullptr)
|
||||
return C_OK;
|
||||
|
||||
if (o->type != OBJ_STRING) {
|
||||
@@ -178,7 +178,7 @@ void getsetCommand(client *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setKey(c->db,c->argv[1],c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
void setrangeCommand(client *c) {
|
||||
@@ -236,29 +236,30 @@ void setrangeCommand(client *c) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,
|
||||
"setrange",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen((sds)ptrFromObj(o)));
|
||||
}
|
||||
|
||||
void getrangeCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
long long start, end;
|
||||
char *str, llbuf[32];
|
||||
const char *str;
|
||||
char llbuf[32];
|
||||
size_t strlen;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
|
||||
return;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
|
||||
return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == nullptr ||
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_INT) {
|
||||
str = llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)ptrFromObj(o));
|
||||
} else {
|
||||
str = (char*)ptrFromObj(o);
|
||||
str = szFromObj(o);
|
||||
strlen = sdslen(str);
|
||||
}
|
||||
|
||||
@@ -287,8 +288,8 @@ void mgetCommand(client *c) {
|
||||
|
||||
addReplyArrayLen(c,c->argc-1);
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) {
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == nullptr) {
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
if (o->type != OBJ_STRING) {
|
||||
@@ -324,7 +325,7 @@ void msetGenericCommand(client *c, int nx) {
|
||||
setKey(c->db,c->argv[j],c->argv[j+1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
|
||||
}
|
||||
server.dirty += (c->argc-1)/2;
|
||||
g_pserver->dirty += (c->argc-1)/2;
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
}
|
||||
|
||||
@@ -352,7 +353,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
}
|
||||
value += incr;
|
||||
|
||||
if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
|
||||
if (o && o->getrefcount(std::memory_order_relaxed) == 1 && o->encoding == OBJ_ENCODING_INT &&
|
||||
(value < 0 || value >= OBJ_SHARED_INTEGERS) &&
|
||||
value >= LONG_MIN && value <= LONG_MAX)
|
||||
{
|
||||
@@ -368,7 +369,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReply(c,shared.colon);
|
||||
addReply(c,newObj);
|
||||
addReply(c,shared.crlf);
|
||||
@@ -407,7 +408,7 @@ void incrbyfloatCommand(client *c) {
|
||||
return;
|
||||
|
||||
value += incr;
|
||||
if (isnan(value) || isinf(value)) {
|
||||
if (std::isnan(value) || std::isinf(value)) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
@@ -418,7 +419,7 @@ void incrbyfloatCommand(client *c) {
|
||||
dbAdd(c->db,c->argv[1],newObj);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReplyBulk(c,newObj);
|
||||
|
||||
/* Always replicate INCRBYFLOAT as a SET command with the final value
|
||||
@@ -459,13 +460,13 @@ void appendCommand(client *c) {
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
void strlenCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
robj_roptr o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == nullptr ||
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
addReplyLongLong(c,stringObjectLen(o));
|
||||
}
|
||||
+55
-49
@@ -134,7 +134,7 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
|
||||
unsigned int rank[ZSKIPLIST_MAXLEVEL];
|
||||
int i, level;
|
||||
|
||||
serverAssert(!isnan(score));
|
||||
serverAssert(!std::isnan(score));
|
||||
x = zsl->header;
|
||||
for (i = zsl->level-1; i >= 0; i--) {
|
||||
/* store rank that is crossed to reach the insert position */
|
||||
@@ -530,11 +530,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
||||
} else {
|
||||
if (((char*)ptrFromObj(min))[0] == '(') {
|
||||
spec->min = strtod((char*)ptrFromObj(min)+1,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
|
||||
if (eptr[0] != '\0' || std::isnan(spec->min)) return C_ERR;
|
||||
spec->minex = 1;
|
||||
} else {
|
||||
spec->min = strtod((char*)ptrFromObj(min),&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
|
||||
if (eptr[0] != '\0' || std::isnan(spec->min)) return C_ERR;
|
||||
}
|
||||
}
|
||||
if (max->encoding == OBJ_ENCODING_INT) {
|
||||
@@ -542,11 +542,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
||||
} else {
|
||||
if (((char*)ptrFromObj(max))[0] == '(') {
|
||||
spec->max = strtod((char*)ptrFromObj(max)+1,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
|
||||
if (eptr[0] != '\0' || std::isnan(spec->max)) return C_ERR;
|
||||
spec->maxex = 1;
|
||||
} else {
|
||||
spec->max = strtod((char*)ptrFromObj(max),&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
|
||||
if (eptr[0] != '\0' || std::isnan(spec->max)) return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,7 +777,7 @@ int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int cl
|
||||
return cmp;
|
||||
}
|
||||
|
||||
unsigned int zzlLength(unsigned char *zl) {
|
||||
unsigned int zzlLength(const unsigned char *zl) {
|
||||
return ziplistLen(zl)/2;
|
||||
}
|
||||
|
||||
@@ -1153,10 +1153,10 @@ unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsig
|
||||
* Common sorted set API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
unsigned long zsetLength(const robj *zobj) {
|
||||
unsigned long zsetLength(robj_roptr zobj) {
|
||||
unsigned long length = 0;
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
length = zzlLength((unsigned char*)zobj->m_ptr);
|
||||
length = zzlLength((const unsigned char*)zobj->m_ptr);
|
||||
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
length = ((const zset*)zobj->m_ptr)->zsl->length;
|
||||
} else {
|
||||
@@ -1243,8 +1243,8 @@ void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
|
||||
zset *set = (zset*)zobj->m_ptr;
|
||||
|
||||
if (set->zsl->length <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
if (set->zsl->length <= g_pserver->zset_max_ziplist_entries &&
|
||||
maxelelen <= g_pserver->zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
|
||||
@@ -1252,7 +1252,7 @@ void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
|
||||
* storing it into *score. If the element does not exist C_ERR is returned
|
||||
* otherwise C_OK is returned and *score is correctly populated.
|
||||
* If 'zobj' or 'member' is NULL, C_ERR is returned. */
|
||||
int zsetScore(robj *zobj, sds member, double *score) {
|
||||
int zsetScore(robj_roptr zobj, sds member, double *score) {
|
||||
if (!zobj || !member) return C_ERR;
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -1320,7 +1320,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
double curscore;
|
||||
|
||||
/* NaN as input is an error regardless of all the other parameters. */
|
||||
if (isnan(score)) {
|
||||
if (std::isnan(score)) {
|
||||
*flags = ZADD_NAN;
|
||||
return 0;
|
||||
}
|
||||
@@ -1339,7 +1339,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
/* Prepare the score for the increment if needed. */
|
||||
if (incr) {
|
||||
score += curscore;
|
||||
if (isnan(score)) {
|
||||
if (std::isnan(score)) {
|
||||
*flags |= ZADD_NAN;
|
||||
return 0;
|
||||
}
|
||||
@@ -1357,9 +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->m_ptr = zzlInsert((unsigned char*)zobj->m_ptr,ele,score);
|
||||
if (zzlLength((unsigned char*)zobj->m_ptr) > server.zset_max_ziplist_entries)
|
||||
if (zzlLength((unsigned char*)zobj->m_ptr) > g_pserver->zset_max_ziplist_entries)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (sdslen(ele) > server.zset_max_ziplist_value)
|
||||
if (sdslen(ele) > g_pserver->zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (newscore) *newscore = score;
|
||||
*flags |= ZADD_ADDED;
|
||||
@@ -1385,7 +1385,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
/* Prepare the score for the increment if needed. */
|
||||
if (incr) {
|
||||
score += curscore;
|
||||
if (isnan(score)) {
|
||||
if (std::isnan(score)) {
|
||||
*flags |= ZADD_NAN;
|
||||
return 0;
|
||||
}
|
||||
@@ -1470,7 +1470,7 @@ int zsetDel(robj *zobj, sds ele) {
|
||||
* the one with the lowest score. Otherwise if 'reverse' is non-zero
|
||||
* the rank is computed considering as element with rank 0 the one with
|
||||
* the highest score. */
|
||||
long zsetRank(robj *zobj, sds ele, int reverse) {
|
||||
long zsetRank(robj_roptr zobj, sds ele, int reverse) {
|
||||
unsigned long llen;
|
||||
unsigned long rank;
|
||||
|
||||
@@ -1600,8 +1600,8 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
zobj = lookupKeyWrite(c->db,key);
|
||||
if (zobj == NULL) {
|
||||
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
|
||||
if (server.zset_max_ziplist_entries == 0 ||
|
||||
server.zset_max_ziplist_value < sdslen(szFromObj(c->argv[scoreidx+1])))
|
||||
if (g_pserver->zset_max_ziplist_entries == 0 ||
|
||||
g_pserver->zset_max_ziplist_value < sdslen(szFromObj(c->argv[scoreidx+1])))
|
||||
{
|
||||
zobj = createZsetObject();
|
||||
} else {
|
||||
@@ -1631,7 +1631,7 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
if (!(retflags & ZADD_NOP)) processed++;
|
||||
score = newscore;
|
||||
}
|
||||
server.dirty += (added+updated);
|
||||
g_pserver->dirty += (added+updated);
|
||||
|
||||
reply_to_client:
|
||||
if (incr) { /* ZINCRBY or INCR option. */
|
||||
@@ -1682,7 +1682,7 @@ void zremCommand(client *c) {
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1784,7 +1784,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
}
|
||||
server.dirty += deleted;
|
||||
g_pserver->dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
|
||||
cleanup:
|
||||
@@ -2150,7 +2150,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
|
||||
/* The result of adding two doubles is NaN when one variable
|
||||
* is +inf and the other is -inf. When these numbers are added,
|
||||
* we maintain the convention of the result being 0.0. */
|
||||
if (isnan(*target)) *target = 0.0;
|
||||
if (std::isnan(*target)) *target = 0.0;
|
||||
} else if (aggregate == REDIS_AGGR_MIN) {
|
||||
*target = val < *target ? val : *target;
|
||||
} else if (aggregate == REDIS_AGGR_MAX) {
|
||||
@@ -2283,7 +2283,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
double score, value;
|
||||
|
||||
score = src[0].weight * zval.score;
|
||||
if (isnan(score)) score = 0;
|
||||
if (std::isnan(score)) score = 0;
|
||||
|
||||
for (j = 1; j < setnum; j++) {
|
||||
/* It is not safe to access the zset we are
|
||||
@@ -2330,7 +2330,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
while (zuiNext(&src[i],&zval)) {
|
||||
/* Initialize value */
|
||||
score = src[i].weight * zval.score;
|
||||
if (isnan(score)) score = 0;
|
||||
if (std::isnan(score)) score = 0;
|
||||
|
||||
/* Search for this element in the accumulating dictionary. */
|
||||
de = dictAddRaw(accumulator,zuiSdsFromValue(&zval),&existing);
|
||||
@@ -2387,14 +2387,14 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
(op == SET_OP_UNION) ? "zunionstore" : "zinterstore",
|
||||
dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
decrRefCount(dstobj);
|
||||
addReply(c,shared.czero);
|
||||
if (touched) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
zfree(src);
|
||||
@@ -2410,7 +2410,7 @@ void zinterstoreCommand(client *c) {
|
||||
|
||||
void zrangeGenericCommand(client *c, int reverse) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
int withscores = 0;
|
||||
long start;
|
||||
long end;
|
||||
@@ -2427,7 +2427,7 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == NULL
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == nullptr
|
||||
|| checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
/* Sanitize indexes. */
|
||||
@@ -2439,7 +2439,7 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
addReplyNull(c);
|
||||
addReplyNull(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
if (end >= llen) end = llen-1;
|
||||
@@ -2527,7 +2527,7 @@ void zrevrangeCommand(client *c) {
|
||||
void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
zrangespec range;
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
long offset = 0, limit = -1;
|
||||
int withscores = 0;
|
||||
unsigned long rangelen = 0;
|
||||
@@ -2575,7 +2575,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
/* Ok, lookup the key and get the range */
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,(c->resp < 3) ? shared.emptyarray : shared.null[c->resp])) == nullptr ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -2595,7 +2595,10 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (eptr == NULL) {
|
||||
addReplyNull(c);
|
||||
if (c->resp < 3)
|
||||
addReply(c, shared.emptyarray);
|
||||
else
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2662,7 +2665,10 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (ln == NULL) {
|
||||
addReplyNull(c);
|
||||
if (c->resp < 3)
|
||||
addReply(c, shared.emptyarray);
|
||||
else
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2719,7 +2725,7 @@ void zrevrangebyscoreCommand(client *c) {
|
||||
|
||||
void zcountCommand(client *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
zrangespec range;
|
||||
unsigned long count = 0;
|
||||
|
||||
@@ -2730,7 +2736,7 @@ void zcountCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Lookup the sorted set */
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.czero)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.czero)) == nullptr ||
|
||||
checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -2796,7 +2802,7 @@ void zcountCommand(client *c) {
|
||||
|
||||
void zlexcountCommand(client *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
zlexrangespec range;
|
||||
unsigned long count = 0;
|
||||
|
||||
@@ -2807,7 +2813,7 @@ void zlexcountCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Lookup the sorted set */
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.czero)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.czero)) == nullptr ||
|
||||
checkType(c, zobj, OBJ_ZSET))
|
||||
{
|
||||
zslFreeLexRange(&range);
|
||||
@@ -2877,7 +2883,7 @@ void zlexcountCommand(client *c) {
|
||||
void genericZrangebylexCommand(client *c, int reverse) {
|
||||
zlexrangespec range;
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
long offset = 0, limit = -1;
|
||||
unsigned long rangelen = 0;
|
||||
void *replylen = NULL;
|
||||
@@ -2920,7 +2926,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
/* Ok, lookup the key and get the range */
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == nullptr ||
|
||||
checkType(c,zobj,OBJ_ZSET))
|
||||
{
|
||||
zslFreeLexRange(&range);
|
||||
@@ -3063,9 +3069,9 @@ void zrevrangebylexCommand(client *c) {
|
||||
|
||||
void zcardCommand(client *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == nullptr ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
addReplyLongLong(c,zsetLength(zobj));
|
||||
@@ -3073,10 +3079,10 @@ void zcardCommand(client *c) {
|
||||
|
||||
void zscoreCommand(client *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
double score;
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == nullptr ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
if (zsetScore(zobj,szFromObj(c->argv[2]),&score) == C_ERR) {
|
||||
@@ -3089,10 +3095,10 @@ void zscoreCommand(client *c) {
|
||||
void zrankGenericCommand(client *c, int reverse) {
|
||||
robj *key = c->argv[1];
|
||||
robj *ele = c->argv[2];
|
||||
robj *zobj;
|
||||
robj_roptr zobj;
|
||||
long rank;
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == nullptr ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
serverAssertWithInfo(c,ele,sdsEncodedObject(ele));
|
||||
@@ -3113,11 +3119,11 @@ void zrevrankCommand(client *c) {
|
||||
}
|
||||
|
||||
void zscanCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == nullptr ||
|
||||
checkType(c,o,OBJ_ZSET)) return;
|
||||
scanGenericCommand(c,o,cursor);
|
||||
}
|
||||
@@ -3211,7 +3217,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
|
||||
serverAssertWithInfo(c,zobj,zsetDel(zobj,ele));
|
||||
server.dirty++;
|
||||
g_pserver->dirty++;
|
||||
|
||||
if (arraylen == 0) { /* Do this only for the first iteration. */
|
||||
const char *events[2] = {"zpopmin","zpopmax"};
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/* tracking.c - Client side caching: keys tracking and invalidation
|
||||
*
|
||||
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* The tracking table is constituted by 2^24 radix trees (each tree, and the
|
||||
* table itself, are allocated in a lazy way only when needed) tracking
|
||||
* clients that may have certain keys in their local, client side, cache.
|
||||
*
|
||||
* Keys are grouped into 2^24 slots, in a way similar to Redis Cluster hash
|
||||
* slots, however here the function we use is crc64, taking the least
|
||||
* significant 24 bits of the output.
|
||||
*
|
||||
* When a client enables tracking with "CLIENT TRACKING on", each key served to
|
||||
* the client is hashed to one of such slots, and Redis will remember what
|
||||
* client may have keys about such slot. Later, when a key in a given slot is
|
||||
* modified, all the clients that may have local copies of keys in that slot
|
||||
* will receive an invalidation message. There is no distinction of database
|
||||
* number: a single table is used.
|
||||
*
|
||||
* Clients will normally take frequently requested objects in memory, removing
|
||||
* them when invalidation messages are received. A strategy clients may use is
|
||||
* to just cache objects in a dictionary, associating to each cached object
|
||||
* some incremental epoch, or just a timestamp. When invalidation messages are
|
||||
* received clients may store, in a different table, the timestamp (or epoch)
|
||||
* of the invalidation of such given slot: later when accessing objects, the
|
||||
* eviction of stale objects may be performed in a lazy way by checking if the
|
||||
* cached object timestamp is older than the invalidation timestamp for such
|
||||
* objects.
|
||||
*
|
||||
* The output of the 24 bit hash function is very large (more than 16 million
|
||||
* possible slots), so clients that may want to use less resources may only
|
||||
* use the most significant bits instead of the full 24 bits. */
|
||||
#define TRACKING_TABLE_SIZE (1<<24)
|
||||
rax **TrackingTable = NULL;
|
||||
robj *TrackingChannelName;
|
||||
|
||||
/* Remove the tracking state from the client 'c'. Note that there is not much
|
||||
* to do for us here, if not to decrement the counter of the clients in
|
||||
* tracking mode, because we just store the ID of the client in the tracking
|
||||
* table, so we'll remove the ID reference in a lazy way. Otherwise when a
|
||||
* client with many entries in the table is removed, it would cost a lot of
|
||||
* time to do the cleanup. */
|
||||
void disableTracking(client *c) {
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
g_pserver->tracking_clients--;
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR);
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable the tracking state for the client 'c', and as a side effect allocates
|
||||
* the tracking table if needed. If the 'redirect_to' argument is non zero, the
|
||||
* invalidation messages for this client will be sent to the client ID
|
||||
* specified by the 'redirect_to' argument. Note that if such client will
|
||||
* eventually get freed, we'll send a message to the original client to
|
||||
* inform it of the condition. Multiple clients can redirect the invalidation
|
||||
* messages to the same client ID. */
|
||||
void enableTracking(client *c, uint64_t redirect_to) {
|
||||
if (c->flags & CLIENT_TRACKING) return;
|
||||
c->flags |= CLIENT_TRACKING;
|
||||
c->flags &= ~CLIENT_TRACKING_BROKEN_REDIR;
|
||||
c->client_tracking_redirection = redirect_to;
|
||||
g_pserver->tracking_clients++;
|
||||
if (TrackingTable == NULL) {
|
||||
TrackingTable = (rax**)zcalloc(sizeof(rax*) * TRACKING_TABLE_SIZE, MALLOC_LOCAL);
|
||||
TrackingChannelName = createStringObject("__redis__:invalidate",20);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called after the excution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled. It will populate the
|
||||
* tracking ivalidation table according to the keys the user fetched, so that
|
||||
* Redis will know what are the clients that should receive an invalidation
|
||||
* message with certain groups of keys are modified. */
|
||||
void trackingRememberKeys(client *c) {
|
||||
int numkeys;
|
||||
int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
if (keys == NULL) return;
|
||||
|
||||
for(int j = 0; j < numkeys; j++) {
|
||||
int idx = keys[j];
|
||||
sds sdskey = (sds)ptrFromObj(c->argv[idx]);
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
if (TrackingTable[hash] == NULL)
|
||||
TrackingTable[hash] = raxNew();
|
||||
raxTryInsert(TrackingTable[hash],
|
||||
(unsigned char*)&c->id,sizeof(c->id),NULL,NULL);
|
||||
}
|
||||
getKeysFreeResult(keys);
|
||||
}
|
||||
|
||||
/* This function is called from signalModifiedKey() or other places in Redis
|
||||
* when a key changes value. In the context of keys tracking, our task here is
|
||||
* to send a notification to every client that may have keys about such . */
|
||||
void trackingInvalidateKey(robj *keyobj) {
|
||||
sds sdskey = (sds)ptrFromObj(keyobj);
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
if (TrackingTable == NULL || TrackingTable[hash] == NULL) return;
|
||||
|
||||
raxIterator ri;
|
||||
raxStart(&ri,TrackingTable[hash]);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id;
|
||||
memcpy(&id,ri.key,ri.key_len);
|
||||
client *c = lookupClientByID(id);
|
||||
int using_redirection = 0;
|
||||
if (c->client_tracking_redirection) {
|
||||
client *redir = lookupClientByID(c->client_tracking_redirection);
|
||||
if (!redir) {
|
||||
/* We need to signal to the original connection that we
|
||||
* are unable to send invalidation messages to the redirected
|
||||
* connection, because the client no longer exist. */
|
||||
if (c->resp > 2) {
|
||||
addReplyPushLen(c,3);
|
||||
addReplyBulkCBuffer(c,"tracking-redir-broken",21);
|
||||
addReplyLongLong(c,c->client_tracking_redirection);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
c = redir;
|
||||
using_redirection = 1;
|
||||
}
|
||||
|
||||
/* Only send such info for clients in RESP version 3 or more. However
|
||||
* if redirection is active, and the connection we redirect to is
|
||||
* in Pub/Sub mode, we can support the feature with RESP 2 as well,
|
||||
* by sending Pub/Sub messages in the __redis__:invalidate channel. */
|
||||
if (c->resp > 2) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCBuffer(c,"invalidate",10);
|
||||
addReplyLongLong(c,hash);
|
||||
} else if (using_redirection && c->flags & CLIENT_PUBSUB) {
|
||||
robj *msg = createStringObjectFromLongLong(hash);
|
||||
addReplyPubsubMessage(c,TrackingChannelName,msg);
|
||||
decrRefCount(msg);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Free the tracking table: we'll create the radix tree and populate it
|
||||
* again if more keys will be modified in this hash slot. */
|
||||
raxFree(TrackingTable[hash]);
|
||||
TrackingTable[hash] = NULL;
|
||||
}
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
#define REDIS_VERSION "0.9.4"
|
||||
#define KEYDB_REAL_VERSION "5.3.3"
|
||||
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
|
||||
|
||||
+4
-4
@@ -468,7 +468,7 @@ int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
|
||||
}
|
||||
|
||||
/* Return the total number of bytes used by the entry pointed to by 'p'. */
|
||||
unsigned int zipRawEntryLength(unsigned char *p) {
|
||||
unsigned int zipRawEntryLength(const unsigned char *p) {
|
||||
unsigned int prevlensize, encoding, lensize, len;
|
||||
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
|
||||
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
|
||||
@@ -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+1;
|
||||
unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
|
||||
unsigned char *zl = zmalloc(bytes, MALLOC_SHARED);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
|
||||
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
|
||||
@@ -1165,12 +1165,12 @@ unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int v
|
||||
}
|
||||
|
||||
/* Return length of ziplist. */
|
||||
unsigned int ziplistLen(unsigned char *zl) {
|
||||
unsigned int ziplistLen(const unsigned char *zl) {
|
||||
unsigned int len = 0;
|
||||
if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) {
|
||||
len = intrev16ifbe(ZIPLIST_LENGTH(zl));
|
||||
} else {
|
||||
unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
|
||||
const unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
|
||||
while (*p != ZIP_END) {
|
||||
p += zipRawEntryLength(p);
|
||||
len++;
|
||||
|
||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user