Compare commits
241
Commits
8.2-m01-int2
...
6.2.19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa00bd2fff | ||
|
|
59b7aa5823 | ||
|
|
0fe6743593 | ||
|
|
df47cffd06 | ||
|
|
ee4d13ab88 | ||
|
|
ffa361256a | ||
|
|
5e93f9cb9d | ||
|
|
98f91fea4e | ||
|
|
0d6a6dca38 | ||
|
|
773f141504 | ||
|
|
699b6db0fc | ||
|
|
cd2f92d5ca | ||
|
|
ff4ce3ce01 | ||
|
|
c652d95ea1 | ||
|
|
441001a4e5 | ||
|
|
ca0a9ab822 | ||
|
|
ad950e4c32 | ||
|
|
0e9710d4dd | ||
|
|
9317bf6465 | ||
|
|
1f7c148be2 | ||
|
|
91863dd854 | ||
|
|
2f27ebb414 | ||
|
|
89391e42c8 | ||
|
|
8f30d2a604 | ||
|
|
24c0bf5c5b | ||
|
|
b313fa4166 | ||
|
|
f613873de0 | ||
|
|
405d14fd44 | ||
|
|
2d4b9401f1 | ||
|
|
521e05cb8b | ||
|
|
7afd1724dd | ||
|
|
eee173a64e | ||
|
|
126348536a | ||
|
|
e030e351fd | ||
|
|
720ea82eab | ||
|
|
0a8a45f94d | ||
|
|
f44b6a0e9a | ||
|
|
1797c5e374 | ||
|
|
683a4ce4b2 | ||
|
|
2322f3d87b | ||
|
|
f084778cea | ||
|
|
658f9bf8c4 | ||
|
|
9c76b1ab55 | ||
|
|
e12aacf3a2 | ||
|
|
137696d808 | ||
|
|
37b3b2a7e0 | ||
|
|
5453899878 | ||
|
|
3148f3e8a5 | ||
|
|
3ebace932d | ||
|
|
423c78f4fa | ||
|
|
de4d78d7df | ||
|
|
e5e642aa9b | ||
|
|
52cccfbe94 | ||
|
|
052e01e75d | ||
|
|
5d66aa3d22 | ||
|
|
2f94d40e02 | ||
|
|
d345d1da9b | ||
|
|
0a3ae0f0a4 | ||
|
|
6029bd560e | ||
|
|
39e5a6fa2f | ||
|
|
4ac3d79bfc | ||
|
|
51fa40ff42 | ||
|
|
042512aa36 | ||
|
|
3f39c18ac1 | ||
|
|
d8102f5ff8 | ||
|
|
65ad4ff87e | ||
|
|
ec5564a4cf | ||
|
|
2cd5f6f3ff | ||
|
|
fbcd591b28 | ||
|
|
e1b17f1232 | ||
|
|
8c76e0f288 | ||
|
|
b4de9e832d | ||
|
|
70aaf50082 | ||
|
|
66472a5ee3 | ||
|
|
fd7bde5726 | ||
|
|
9d40f1cbb5 | ||
|
|
389c5e14ad | ||
|
|
64c657a8af | ||
|
|
e6f67092f8 | ||
|
|
3053337043 | ||
|
|
215c5eacb0 | ||
|
|
afb48c6cc9 | ||
|
|
7fddebc272 | ||
|
|
28d20b8f14 | ||
|
|
ba2feb3004 | ||
|
|
aa0606db08 | ||
|
|
49c1c96fc1 | ||
|
|
d92f2f5ad6 | ||
|
|
e1c09d5982 | ||
|
|
8d9e75c769 | ||
|
|
2643c691be | ||
|
|
ef78757e69 | ||
|
|
35e697de83 | ||
|
|
159981e73c | ||
|
|
ec06e93319 | ||
|
|
d6a8e64e69 | ||
|
|
a06f10b009 | ||
|
|
0a26869a13 | ||
|
|
2c529844fc | ||
|
|
33f7e12b88 | ||
|
|
90891a7dde | ||
|
|
4a59230d82 | ||
|
|
e24b947c9b | ||
|
|
c5a753c890 | ||
|
|
64327e4169 | ||
|
|
aab9b12786 | ||
|
|
066b68328e | ||
|
|
db3e3ebcb2 | ||
|
|
86db5091c7 | ||
|
|
93c1d31d97 | ||
|
|
8fca090ede | ||
|
|
e38d0b5a5f | ||
|
|
94013b8ecb | ||
|
|
968cd2b967 | ||
|
|
19c2b179c7 | ||
|
|
3ba7c6acbe | ||
|
|
936ee01759 | ||
|
|
8a0515d9a3 | ||
|
|
21ab5d4f78 | ||
|
|
0fb96d55bd | ||
|
|
f5d8a36983 | ||
|
|
11b602fbf8 | ||
|
|
b2ce3719af | ||
|
|
21414ad480 | ||
|
|
13c1e1f298 | ||
|
|
34505d26f7 | ||
|
|
4930d19e70 | ||
|
|
aba9517542 | ||
|
|
7540708a61 | ||
|
|
664c7b36a5 | ||
|
|
c390972c76 | ||
|
|
73d286d523 | ||
|
|
4cbccab2bd | ||
|
|
23ed37dd87 | ||
|
|
9b25484a13 | ||
|
|
49f8f43890 | ||
|
|
c936f801c0 | ||
|
|
694a869e78 | ||
|
|
a2e8a3a241 | ||
|
|
2f8ec0e024 | ||
|
|
09c63c45dd | ||
|
|
1ed0f049fe | ||
|
|
4b04ca0b18 | ||
|
|
2f54107289 | ||
|
|
e34f06ae5d | ||
|
|
77386ae011 | ||
|
|
0c959294a8 | ||
|
|
8892b5cf9e | ||
|
|
9da232c05a | ||
|
|
1ce06604de | ||
|
|
530c70b0a9 | ||
|
|
dadc67a92e | ||
|
|
2775a3526e | ||
|
|
8f241ab3b8 | ||
|
|
3e09be56a8 | ||
|
|
757f8f771e | ||
|
|
04ba485042 | ||
|
|
b1149a49b2 | ||
|
|
db09f6eb2e | ||
|
|
835d15b536 | ||
|
|
bae0512c8a | ||
|
|
8d6134952a | ||
|
|
1d7c0e5949 | ||
|
|
37b0f3617d | ||
|
|
6866117194 | ||
|
|
b622537199 | ||
|
|
355b1b6a57 | ||
|
|
d6f4273241 | ||
|
|
de1b19ea88 | ||
|
|
79fa5618f1 | ||
|
|
91bf2ab86d | ||
|
|
88655019cc | ||
|
|
fad44611dc | ||
|
|
8884971223 | ||
|
|
07e1248686 | ||
|
|
6cd84b64f0 | ||
|
|
c6b3966d02 | ||
|
|
432e056659 | ||
|
|
bbb6c2fb53 | ||
|
|
095a6e5937 | ||
|
|
56020fd821 | ||
|
|
11235c0e40 | ||
|
|
406caa5f56 | ||
|
|
8bd5e39102 | ||
|
|
c547dff650 | ||
|
|
e6df2f6210 | ||
|
|
312ccae003 | ||
|
|
ff27217639 | ||
|
|
3f4f9b6331 | ||
|
|
5b8e395174 | ||
|
|
9914b676b3 | ||
|
|
abd44c8393 | ||
|
|
f70916f8b0 | ||
|
|
ef3ad68020 | ||
|
|
f9470c9a17 | ||
|
|
e9a1438ac4 | ||
|
|
5102c0da92 | ||
|
|
d68b775df5 | ||
|
|
ed995ffa03 | ||
|
|
875a1f07d8 | ||
|
|
d070546568 | ||
|
|
511be70eba | ||
|
|
840f7f61c5 | ||
|
|
9d69d6e8fb | ||
|
|
15c078df61 | ||
|
|
e90e5640e7 | ||
|
|
2df6695f2b | ||
|
|
92e3b1802f | ||
|
|
0463520693 | ||
|
|
922e3bf59f | ||
|
|
42f2ad0516 | ||
|
|
1eab6202ac | ||
|
|
d30ee6c44a | ||
|
|
4f2da00e94 | ||
|
|
34b9a3fa2e | ||
|
|
5c7b869e61 | ||
|
|
6cbea7d29b | ||
|
|
8cfa37fc21 | ||
|
|
16c53085f7 | ||
|
|
2ccb926314 | ||
|
|
6384fe3414 | ||
|
|
ef64333e63 | ||
|
|
0851705304 | ||
|
|
b97a4ad7f2 | ||
|
|
d01afe885c | ||
|
|
959d6035e5 | ||
|
|
aa730ef1ea | ||
|
|
f5ca1f9ee9 | ||
|
|
92bde124ca | ||
|
|
7018ad69a2 | ||
|
|
46df4db87b | ||
|
|
445aa844b9 | ||
|
|
f098fe319b | ||
|
|
2ab6fef099 | ||
|
|
2dba1e391d | ||
|
|
95338f9cc4 | ||
|
|
d0854927fc | ||
|
|
ec2d180739 | ||
|
|
049f2f0805 | ||
|
|
b34fc03e5e | ||
|
|
b8c67ce41b |
@@ -1,12 +1,17 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Submit a crash report
|
||||
title: '[CRASH]'
|
||||
title: '[CRASH] <short description>'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Notice!
|
||||
- If a Redis module was involved, please open an issue in the module's repo instead!
|
||||
- If you're using docker on Apple M1, please make sure the image you're using was compiled for ARM!
|
||||
|
||||
|
||||
**Crash report**
|
||||
|
||||
Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context.
|
||||
|
||||
@@ -19,12 +19,15 @@ jobs:
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
build-debian-old:
|
||||
runs-on: ubuntu-latest
|
||||
container: debian:oldoldstable
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror'
|
||||
run: |
|
||||
apt-get update && apt-get install -y build-essential
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
|
||||
build-macos-latest:
|
||||
runs-on: macos-latest
|
||||
@@ -49,12 +52,20 @@ jobs:
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror' MALLOC=libc
|
||||
|
||||
build-centos7-jemalloc:
|
||||
build-old-chain-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
container: centos:7
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
yum -y install gcc make
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
|
||||
+43
-27
@@ -151,7 +151,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1 --dump-logs
|
||||
./runtest --valgrind --verbose --clients 1 --tags -large-memory --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
|
||||
- name: unittest
|
||||
@@ -171,25 +171,33 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1 --dump-logs
|
||||
./runtest --valgrind --verbose --clients 1 --tags -large-memory --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
|
||||
|
||||
test-centos7-jemalloc:
|
||||
test-old-chain-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: centos:7
|
||||
container: ubuntu:20.04
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
yum -y install gcc make
|
||||
make
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: apt-get install -y tcl tcltls tclx
|
||||
- name: test
|
||||
run: |
|
||||
yum -y install which tcl
|
||||
./runtest --accurate --verbose --dump-logs
|
||||
run: ./runtest --accurate --verbose --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
@@ -197,22 +205,31 @@ jobs:
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-centos7-tls:
|
||||
test-old-chain-tls:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: centos:7
|
||||
container: ubuntu:20.04
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
yum -y install centos-release-scl epel-release
|
||||
yum -y install devtoolset-7 openssl-devel openssl
|
||||
scl enable devtoolset-7 "make BUILD_TLS=yes"
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 openssl libssl-dev
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
make CC=gcc BUILD_TLS=yes REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
apt-get install -y tcl tcltls tclx
|
||||
./utils/gen-test-certs.sh
|
||||
- name: test
|
||||
run: |
|
||||
yum -y install tcl tcltls
|
||||
./utils/gen-test-certs.sh
|
||||
./runtest --accurate --verbose --tls --dump-logs
|
||||
./runtest --accurate --verbose --dump-logs
|
||||
- name: module api test
|
||||
@@ -247,23 +264,22 @@ jobs:
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-freebsd:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: test
|
||||
uses: vmactions/freebsd-vm@v0.1.2
|
||||
uses: cross-platform-actions/action@v0.22.0
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
prepare: pkg install -y bash gmake lang/tcl86
|
||||
run: >
|
||||
gmake &&
|
||||
./runtest --accurate --verbose --no-latency --dump-logs &&
|
||||
MAKE=gmake ./runtest-moduleapi --verbose &&
|
||||
./runtest-sentinel &&
|
||||
./runtest-cluster
|
||||
operating_system: freebsd
|
||||
environment_variables: MAKE
|
||||
version: 13.2
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx gcc
|
||||
gmake
|
||||
./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol
|
||||
|
||||
test-alpine-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
+941
-11
@@ -1,16 +1,946 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Redis, the place where all the development happens.
|
||||
Redis 6.2 release notes
|
||||
=======================
|
||||
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development environments
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
http://download.redis.io/releases/redis-stable.tar.gz
|
||||
================================================================================
|
||||
Redis 6.2.19 Released Sun 6 Jul 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
More information is available at https://redis.io
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
Happy hacking!
|
||||
|
||||
### Security fixes
|
||||
|
||||
* (CVE-2025-32023) Fix out-of-bounds write in `HyperLogLog` commands
|
||||
* (CVE-2025-48367) Retry accepting other connections even if the accepted connection reports an error
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.18 Released Wed 23 Apr 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
|
||||
### Security fixes
|
||||
|
||||
* (CVE-2025-21605) An unauthenticated client can cause an unlimited growth of output buffers
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.17 Released Mon 6 Jan 2025 12:30:00 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
* (CVE-2024-46981) Lua script commands may lead to remote code execution
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.16 Released Wed 02 Oct 2024 20:17:04 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
|
||||
* (CVE-2024-31449) Lua library commands may lead to stack overflow and potential RCE.
|
||||
* (CVE-2024-31228) Potential Denial-of-service due to unbounded pattern matching.
|
||||
|
||||
6.2.15 was an erroneous release,
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.14 Released Wed 18 Oct 2023 10:33:40 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
|
||||
* (CVE-2023-45145) The wrong order of listen(2) and chmod(2) calls creates a
|
||||
race condition that can be used by another process to bypass desired Unix
|
||||
socket permissions on startup.
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.13 Released Mon July 10 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-24834) A specially crafted Lua script executing in Redis can trigger
|
||||
a heap overflow in the cjson and cmsgpack libraries, and result in heap
|
||||
corruption and potentially remote code execution. The problem exists in all
|
||||
versions of Redis with Lua scripting support, starting from 2.6, and affects
|
||||
only authenticated and authorized users.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Re-enable downscale rehashing while there is a fork child (#12276)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.12 Released Mon Apr 17 16:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2023-28856) Authenticated users can use the HINCRBYFLOAT command to create
|
||||
an invalid hash field that will crash Redis on access
|
||||
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix CLIENT REPLY OFF|SKIP to not silence push notifications (#11875)
|
||||
* Disconnect pub-sub subscribers when revoking allchannels permission (#11992)
|
||||
* Trim excessive memory usage in stream nodes when exceeding `stream-node-max-bytes` (#11885)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.11 Released Tue Feb 28 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2023-25155) Specially crafted SRANDMEMBER, ZRANDMEMBER, and HRANDFIELD
|
||||
commands can trigger an integer overflow, resulting in a runtime assertion
|
||||
and termination of the Redis server process.
|
||||
* (CVE-2022-36021) String matching commands (like SCAN or KEYS) with a specially
|
||||
crafted pattern to trigger a denial-of-service attack on Redis, causing it to
|
||||
hang and consume 100% CPU time.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix a crash when reaching the maximum invalidations limit of client-side tracking (#11814)
|
||||
* Fix cluster inbound link keepalive time (#11785)
|
||||
* Make sure that fork child doesn't do incremental rehashing (#11692)
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Avoid realloc to reduce size of strings when it is unneeded (#11766)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.10 Released Mon Jan 17 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: MODERATE, a quick followup fix for a recently released 6.2.9.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Revert the change to KEYS in the recent client output buffer limit fix (#11676)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.9 Released Mon Jan 16 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-35977) Integer overflow in the Redis SETRANGE and SORT/SORT_RO
|
||||
commands can drive Redis to OOM panic
|
||||
* (CVE-2023-22458) Integer overflow in the Redis HRANDFIELD and ZRANDMEMBER
|
||||
commands can lead to denial-of-service
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Avoid possible hang when client issues long KEYS, SRANDMEMBER, HRANDFIELD,
|
||||
and ZRANDMEMBER commands and gets disconnected by client output buffer limit (#11676)
|
||||
* Fix sentinel issue if replica changes IP (#11590)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.8 Released Mon 12 Dec 2022 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: MODERATE, Contains fixes for a few non-critical or unlikely bugs
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Optimize zset conversion on large ZRANGESTORE (#10789)
|
||||
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Fix crash in CLIENT_CHANGE event, when the selected database is not 0 (#11500)
|
||||
* Fix RM_SetAbsExpire and RM_GetAbsExpire API registration (#11025, #8564)
|
||||
|
||||
Security improvements
|
||||
=====================
|
||||
|
||||
* Sentinel: avoid logging auth-pass value (#9652)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix a crash when a Lua script returns a meta-table (#11032)
|
||||
* Fix ZRANGESTORE crash when zset_max_listpack_entries is 0 (#10767)
|
||||
* Unpause clients after manual failover ends instead of waiting for timed (#9676)
|
||||
* TLS: Notify clients on connection shutdown (#10931)
|
||||
* Avoid hang of diskless replication fork child when parent crashes (#11463)
|
||||
* Fix sentinel function that compares hostnames if failed resolve (#11419)
|
||||
* Fix a hang when eviction is combined with lazy-free and maxmemory-eviction-tenacity
|
||||
is set to 100 (#11237)
|
||||
* Fix bug with scripts ignoring client tracking NOLOOP (#11052)
|
||||
* Fix client-side tracking breaking protocol when FLUSHDB / FLUSHALL / SWAPDB is
|
||||
used inside MULTI-EXEC (#11038)
|
||||
* Fix BITFIELD overflow detection on some compilers due to undefined behaviour (#9601)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.7 Released Wed Apr 27 12:00:00 IDT 2022
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-24736) An attacker attempting to load a specially crafted Lua script
|
||||
can cause NULL pointer dereference which will result with a crash of the
|
||||
redis-server process. This issue affects all versions of Redis.
|
||||
[reported by Aviv Yahav].
|
||||
* (CVE-2022-24735) By exploiting weaknesses in the Lua script execution
|
||||
environment, an attacker with access to Redis can inject Lua code that will
|
||||
execute with the (potentially higher) privileges of another Redis user.
|
||||
[reported by Aviv Yahav].
|
||||
|
||||
|
||||
Potentially Breaking Fixes
|
||||
==========================
|
||||
|
||||
* LPOP/RPOP with count against non-existing list return null array (#10095)
|
||||
* LPOP/RPOP used to produce wrong replies when count is 0 (#9692)
|
||||
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Speed optimization in command execution pipeline (#10502)
|
||||
* Fix regression in Z[REV]RANGE commands (by-rank) introduced in Redis 6.2 (#10337)
|
||||
|
||||
|
||||
Platform / toolchain support related improvements
|
||||
=================================================
|
||||
|
||||
* Fix RSS metrics on NetBSD and OpenBSD (#10116, #10149)
|
||||
* Fix OpenSSL 3.0.x related issues (#10291)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Lua: Add checks for min-slave-* configs when evaluating Lua scripts (#10160)
|
||||
* Lua: fix crash on a script call with many arguments, a regression in v6.2.6 (#9809)
|
||||
* Tracking: Make invalidation messages always after command's reply (#9422)
|
||||
* Fix excessive stream trimming due to an overflow (#10068)
|
||||
* Add missed error counting for INFO errorstats (#9646)
|
||||
* Fix geo search bounding box check causing missing results (#10018)
|
||||
* Improve EXPIRE TTL overflow detection (#9839)
|
||||
* Modules: Fix thread safety violation when a module thread adds an error reply, broken in 6.2 (#10278)
|
||||
* Modules: Fix missing and duplicate error stats (#10278)
|
||||
* Module APIs: release clients blocked on module commands in cluster resharding
|
||||
and down state (#9483)
|
||||
* Sentinel: Fix memory leak with TLS (#9753)
|
||||
* Sentinel: Fix issues with hostname support (#10146)
|
||||
* Sentinel: Fix election failures on certain container environments (#10197)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.6 Released Mon Oct 4 12:00:00 IDT 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2021-41099) Integer to heap buffer overflow handling certain string
|
||||
commands and network payloads, when proto-max-bulk-len is manually configured
|
||||
to a non-default, very large value [reported by yiyuaner].
|
||||
* (CVE-2021-32762) Integer to heap buffer overflow issue in redis-cli and
|
||||
redis-sentinel parsing large multi-bulk replies on some older and less common
|
||||
platforms [reported by Microsoft Vulnerability Research].
|
||||
* (CVE-2021-32687) Integer to heap buffer overflow with intsets, when
|
||||
set-max-intset-entries is manually configured to a non-default, very large
|
||||
value [reported by Pawel Wieczorkiewicz, AWS].
|
||||
* (CVE-2021-32675) Denial Of Service when processing RESP request payloads with
|
||||
a large number of elements on many connections.
|
||||
* (CVE-2021-32672) Random heap reading issue with Lua Debugger [reported by
|
||||
Meir Shpilraien].
|
||||
* (CVE-2021-32628) Integer to heap buffer overflow handling ziplist-encoded
|
||||
data types, when configuring a large, non-default value for
|
||||
hash-max-ziplist-entries, hash-max-ziplist-value, zset-max-ziplist-entries
|
||||
or zset-max-ziplist-value [reported by sundb].
|
||||
* (CVE-2021-32627) Integer to heap buffer overflow issue with streams, when
|
||||
configuring a non-default, large value for proto-max-bulk-len and
|
||||
client-query-buffer-limit [reported by sundb].
|
||||
* (CVE-2021-32626) Specially crafted Lua scripts may result with Heap buffer
|
||||
overflow [reported by Meir Shpilraien].
|
||||
|
||||
Bug fixes that involve behavior changes:
|
||||
* GEO* STORE with empty source key deletes the destination key and return 0 (#9271)
|
||||
Previously it would have returned an empty array like the non-STORE variant.
|
||||
* PUBSUB NUMPAT replies with number of patterns rather than number of subscriptions (#9209)
|
||||
This actually changed in 6.2.0 but was overlooked and omitted from the release notes.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix CLIENT PAUSE, used an old timeout from previous PAUSE (#9477)
|
||||
* Fix CLIENT PAUSE in a replica would mess the replication offset (#9448)
|
||||
* Add some missing error statistics in INFO errorstats (#9328)
|
||||
|
||||
Other bug fixes:
|
||||
* Fix incorrect reply of COMMAND command key positions for MIGRATE command (#9455)
|
||||
* Fix appendfsync to always guarantee fsync before reply, on MacOS and FreeBSD (kqueue) (#9416)
|
||||
* Fix the wrong mis-detection of sync_file_range system call, affecting performance (#9371)
|
||||
|
||||
CLI tools:
|
||||
* When redis-cli received ASK response, it didn't handle it (#8930)
|
||||
|
||||
Improvements:
|
||||
* Add latency monitor sample when key is deleted via lazy expire (#9317)
|
||||
* Sanitize corrupt payload improvements (#9321, #9399)
|
||||
* Delete empty keys when loading RDB file or handling a RESTORE command (#9297, #9349)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.5 Released Wed Jul 21 16:32:19 IDT 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues that affect
|
||||
authenticated client connections on 32-bit versions. MODERATE otherwise.
|
||||
|
||||
Fix integer overflow in BITFIELD on 32-bit versions (CVE-2021-32761).
|
||||
An integer overflow bug in Redis version 2.2 or newer can be exploited using the
|
||||
BITFIELD command to corrupt the heap and potentially result with remote code
|
||||
execution.
|
||||
|
||||
Bug fixes that involve behavior changes:
|
||||
* Change reply type for ZPOPMAX/MIN with count in RESP3 to nested array (#8981).
|
||||
Was using a flat array like in RESP2 instead of a nested array like ZRANGE does.
|
||||
* Fix reply type for HRANDFIELD and ZRANDMEMBER when key is missing (#9178).
|
||||
Was using a null array instead of an empty array.
|
||||
* Fix reply type for ZRANGESTORE when source key is missing (#9089).
|
||||
Was using an empty array like ZRANGE instead of 0 (used in the STORE variant).
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* ZRANDMEMBER WITHSCORES with negative COUNT may return bad score (#9162)
|
||||
* Fix crash after CLIENT UNPAUSE when threaded I/O config is enabled (#9041)
|
||||
* Fix XTRIM or XADD with LIMIT may delete more entries than the limit (#9048)
|
||||
* Fix build issue with OpenSSL 1.1.0 (#9233)
|
||||
|
||||
Other bug fixes:
|
||||
* Fail EXEC command in case a watched key is expired (#9194)
|
||||
* Fix SMOVE not to invalidate dest key (WATCH and tracking) when member already exists (#9244)
|
||||
* Fix SINTERSTORE not to delete dest key when getting a wrong type error (#9032)
|
||||
* Fix overflows on 32-bit versions in GETBIT, SETBIT, BITCOUNT, BITPOS, and BITFIELD (#9191)
|
||||
* Improve MEMORY USAGE on stream keys (#9164)
|
||||
* Set TCP keepalive on inbound cluster bus connections (#9230)
|
||||
* Fix diskless replica loading to recover from RDB short read on module AUX data (#9199)
|
||||
* Fix race in client side tracking (#9116)
|
||||
* Fix ziplist length updates on big-endian platforms (#2080)
|
||||
|
||||
CLI tools:
|
||||
* redis-cli cluster import command may issue wrong MIGRATE command, sending COPY instead of REPLACE (#8945)
|
||||
* redis-cli --rdb fixes when using "-" to write to stdout (#9136, #9135)
|
||||
* redis-cli support for RESP3 set type in CSV and RAW output (#7338)
|
||||
|
||||
Modules:
|
||||
* Module API for getting current command name (#8792)
|
||||
* Fix RM_StringTruncate when newlen is 0 (#3718)
|
||||
* Fix CLIENT UNBLOCK crashing modules without timeout callback (#9167)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.4 Released Tue June 1 12:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, Contains fixes to security issues that affect
|
||||
authenticated client connections. MODERATE otherwise.
|
||||
|
||||
Fix integer overflow in STRALGO LCS (CVE-2021-32625)
|
||||
An integer overflow bug in Redis version 6.0 or newer can be exploited using the
|
||||
STRALGO LCS command to corrupt the heap and potentially result with remote code
|
||||
execution. This is a result of an incomplete fix by CVE-2021-29477.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix crash after a diskless replication fork child is terminated (#8991)
|
||||
* Fix redis-benchmark crash on unsupported configs (#8916)
|
||||
|
||||
Other bug fixes:
|
||||
* Fix crash in UNLINK on a stream key with deleted consumer groups (#8932)
|
||||
* SINTERSTORE: Add missing keyspace del event when none of the sources exist (#8949)
|
||||
* Sentinel: Fix CONFIG SET of empty string sentinel-user/sentinel-pass configs (#8958)
|
||||
* Enforce client output buffer soft limit when no traffic (#8833)
|
||||
|
||||
Improvements:
|
||||
* Hide AUTH passwords in MIGRATE command from slowlog (#8859)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.3 Released Mon May 3 19:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, Contains fixes to security issues that affect
|
||||
authenticated client connections. LOW otherwise.
|
||||
|
||||
Integer overflow in STRALGO LCS command (CVE-2021-29477):
|
||||
An integer overflow bug in Redis version 6.0 or newer could be exploited using
|
||||
the STRALGO LCS command to corrupt the heap and potentially result in remote
|
||||
code execution. The integer overflow bug exists in all versions of Redis
|
||||
starting with 6.0.
|
||||
|
||||
Integer overflow in COPY command for large intsets (CVE-2021-29478):
|
||||
An integer overflow bug in Redis 6.2 could be exploited to corrupt the heap and
|
||||
potentially result with remote code execution. The vulnerability involves
|
||||
changing the default set-max-intset-entries configuration value, creating a
|
||||
large set key that consists of integer values and using the COPY command to
|
||||
duplicate it. The integer overflow bug exists in all versions of Redis starting
|
||||
with 2.6, where it could result with a corrupted RDB or DUMP payload, but not
|
||||
exploited through COPY (which did not exist before 6.2).
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix memory leak in moduleDefragGlobals (#8853)
|
||||
* Fix memory leak when doing lazy freeing client tracking table (#8822)
|
||||
* Block abusive replicas from sending command that could assert and crash redis (#8868)
|
||||
|
||||
Other bug fixes:
|
||||
* Use a monotonic clock to check for Lua script timeout (#8812)
|
||||
* redis-cli: Do not use unix socket when we got redirected in cluster mode (#8870)
|
||||
|
||||
Modules:
|
||||
* Fix RM_GetClusterNodeInfo() to correctly populate master id (#8846)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.2 Released Mon April 19 19:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: HIGH, if you're using ACL and pub/sub, CONFIG REWRITE, or
|
||||
suffering from performance regression. see below.
|
||||
|
||||
Bug fixes for regressions in previous releases of Redis 6.2:
|
||||
* Fix BGSAVE, AOFRW, and replication slowdown due to child reporting CoW (#8645)
|
||||
* Fix short busy loop when timer event is about to fire (#8764)
|
||||
* Fix default user, overwritten and reset users losing pubsub channel permissions (#8723)
|
||||
* Fix config rewrite with an empty `save` config resulsing in default `save` values (#8719)
|
||||
* Fix not starting on alpine/libmusl without IPv6 (#8655)
|
||||
* Fix issues with propagation and MULTI/EXEC in modules (#8617)
|
||||
Several issues around nested calls and thread safe contexts
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* ACL Pub/Sub channels permission handling for save/load scenario (#8794)
|
||||
* Fix early rejection of PUBLISH inside MULTI-EXEC transaction (#8534)
|
||||
* Fix missing SLOWLOG records for blocked commands (#8632)
|
||||
* Allow RESET command during busy scripts (#8629)
|
||||
* Fix some error replies were not counted on stats (#8659)
|
||||
|
||||
Bug fixes:
|
||||
* Add a timeout mechanism for replicas stuck in fullsync (#8762)
|
||||
* Process HELLO command even if the default user has no permissions (#8633)
|
||||
* Client issuing a long running script and using a pipeline, got disconnected (#8715)
|
||||
* Fix script kill to work also on scripts that use `pcall` (#8661)
|
||||
* Fix list-compress-depth may compress more node than required (#8311)
|
||||
* Fix redis-cli handling of rediss:// URL scheme (#8705)
|
||||
* Cluster: Skip unnecessary check which may prevent failure detection (#8585)
|
||||
* Cluster: Fix hang manual failover when replica just started (#8651)
|
||||
* Sentinel: Fix info-refresh time field before sentinel get first response (#8567)
|
||||
* Sentinel: Fix possible crash on failed connection attempt (#8627)
|
||||
* Systemd: Send the readiness notification when a replica is ready to accept connections (#8409)
|
||||
|
||||
Command behavior changes:
|
||||
* ZADD: fix wrong reply when INCR used with GT/LT which blocked the update (#8717)
|
||||
It was responding with the incremented value rather than nil
|
||||
* XAUTOCLAIM: fix response to return the next available id as the cursor (#8725)
|
||||
Previous behavior was retuning the last one which was already scanned
|
||||
* XAUTOCLAIM: fix JUSTID to prevent incrementing delivery_count (#8724)
|
||||
|
||||
New config options:
|
||||
* Add cluster-allow-replica-migration config option (#5285)
|
||||
* Add replica-announced config option (#8653)
|
||||
* Add support for plaintext clients in TLS cluster (#8587)
|
||||
* Add support for reading encrypted keyfiles (#8644)
|
||||
|
||||
Improvements:
|
||||
* Fix performance regression in BRPOP on Redis 6.0 (#8689)
|
||||
* Avoid adding slowlog entries for config with sensitive data (#8584)
|
||||
* Improve redis-cli non-binary safe string handling (#8566)
|
||||
* Optimize CLUSTER SLOTS reply (#8541)
|
||||
* Handle remaining fsync errors (#8419)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Strip % sign from current_fork_perc info field (#8628)
|
||||
* Fix RSS memory info on FreeBSD (#8620)
|
||||
* Fix client_recent_max_input/output_buffer in 'INFO CLIENTS' when all clients drop (#8588)
|
||||
* Fix invalid master_link_down_since_seconds in info replication (#8785)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Fix FreeBSD <12.x builds (#8603)
|
||||
|
||||
Modules:
|
||||
* Add macros for RedisModule_log logging levels (#4246)
|
||||
* Add RedisModule_GetAbsExpire / RedisModule_SetAbsExpire (#8564)
|
||||
* Add a module type for key space notification (#8759)
|
||||
* Set module eviction context flag only in masters (#8631)
|
||||
* Fix unusable RedisModule_IsAOFClient API (#8596)
|
||||
* Fix missing EXEC on modules propagation after failed EVAL execution (#8654)
|
||||
* Fix edge-case when a module client is unblocked (#8618)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.1 Released Mon Mar 1 17:51:36 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: LOW.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2.0,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
Bug fixes:
|
||||
* Fix sanitize-dump-payload for stream with deleted records (#8568)
|
||||
* Prevent client-query-buffer-limit config from being set to lower than 1mb (#8557)
|
||||
|
||||
Improvements:
|
||||
* Make port, tls-port and bind config options modifiable at runtime (#8510)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Fix compilation error on non-glibc systems if jemalloc is not used (#8533)
|
||||
* Improved memory consumption and memory usage tracking on FreeBSD (#8545)
|
||||
* Fix compilation on ARM64 MacOS with jemalloc (#8458)
|
||||
|
||||
Modules:
|
||||
* New Module API for getting user name of a client (#8508)
|
||||
* Optimize RM_Call by utilizing a shared reusable client (#8516)
|
||||
* Fix crash running CLIENT INFO via RM_Call (#8560)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.0 GA Released Tue Feb 22 14:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY if you use 32bit build of redis (see bellow), MODERATE
|
||||
if you used earlier versions of Redis 6.2, LOW otherwise.
|
||||
|
||||
Integer overflow on 32-bit systems (CVE-2021-21309):
|
||||
Redis 4.0 or newer uses a configurable limit for the maximum supported bulk
|
||||
input size. By default, it is 512MB which is a safe value for all platforms.
|
||||
If the limit is significantly increased, receiving a large request from a client
|
||||
may trigger several integer overflow scenarios, which would result with buffer
|
||||
overflow and heap corruption.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC3,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
Bug fixes:
|
||||
* Avoid 32-bit overflows when proto-max-bulk-len is set high (#8522)
|
||||
* Fix broken protocol in client tracking tracking-redir-broken message (#8456)
|
||||
* Avoid unsafe field name characters in INFO commandstats, errorstats, modules (#8492)
|
||||
* XINFO able to access expired keys during CLIENT PAUSE WRITE (#8436)
|
||||
* Fix allowed length for REPLCONF ip-address, needed due to Sentinel's support for hostnames (#8517)
|
||||
* Fix broken protocol in redis-benchmark when used with -a or --dbnum (#8486)
|
||||
* XADD counts deleted records too when considering switching to a new listpack (#8390)
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fixes in GEOSEARCH bybox (accuracy and mismatch between width and height) (#8445)
|
||||
* Fix risk of OOM panic in HRANDFIELD, ZRANDMEMBER commands with huge negative count (#8429)
|
||||
* Fix duplicate replicas issue in Sentinel, needed due to hostname support (#8481)
|
||||
* Fix Sentinel configuration rewrite, an improvement of #8271 (#8480)
|
||||
|
||||
Command behavior changes:
|
||||
* SRANDMEMBER uses RESP3 array type instead of set type (#8504)
|
||||
* EXPIRE, EXPIREAT, SETEX, GETEX: Return error when provided expire time overflows (#8287)
|
||||
|
||||
Other behavior changes:
|
||||
* Remove ACL subcommand validation if fully added command exists. (#8483)
|
||||
|
||||
Improvements:
|
||||
* Optimize sorting in GEORADIUS / GEOSEARCH with COUNT (#8326)
|
||||
* Optimize HRANDFIELD and ZRANDMEMBER case 4 when ziplist encoded (#8444)
|
||||
* Optimize in-place replacement of elements in HSET, HINCRBY, LSET (#8493)
|
||||
* Remove redundant list to store pubsub patterns (#8472)
|
||||
* Add --insecure option to command line tools (#8416)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add INFO fields to track progress of BGSAVE, AOFRW, replication (#8414)
|
||||
|
||||
Modules:
|
||||
* RM_ZsetRem: Delete key if empty, the bug could leave empty zset keys (#8453)
|
||||
* RM_HashSet: Add COUNT_ALL flag and set errno (#8446)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC3 Released Tue Feb 1 14:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the third Release Candidate of Redis 6.2.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC2,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add HRANDFIELD and ZRANDMEMBER commands (#8297)
|
||||
* Add FAILOVER command (#8315)
|
||||
* Add GETEX, GETDEL commands (#8327)
|
||||
* Add PXAT/EXAT arguments to SET command (#8327)
|
||||
* Add SYNC arg to FLUSHALL and FLUSHDB, and ASYNC/SYNC arg to SCRIPT FLUSH (#8258)
|
||||
|
||||
Sentinel:
|
||||
* Add hostname support to Sentinel (#8282)
|
||||
* Prevent file descriptors from leaking into Sentinel scripts (#8242)
|
||||
* Fix config file line order dependency and config rewrite sequence (#8271)
|
||||
|
||||
New configuration options:
|
||||
* Add set-proc-title config option to disable changes to the process title (#3623)
|
||||
* Add proc-title-template option to control what's shown in the process title (#8397)
|
||||
* Add lazyfree-lazy-user-flush config option to control FLUSHALL, FLUSHDB and SCRIPT FLUSH (#8258)
|
||||
|
||||
Bug fixes:
|
||||
* AOF: recover from last write error by turning on/off appendonly config (#8030)
|
||||
* Exit on fsync error when the AOF fsync policy is 'always' (#8347)
|
||||
* Avoid assertions (on older kernels) when testing arm64 CoW bug (#8405)
|
||||
* CONFIG REWRITE should honor umask settings (#8371)
|
||||
* Fix firstkey,lastkey,step in COMMAND command for some commands (#8367)
|
||||
|
||||
Special considerations:
|
||||
* Fix misleading description of the save configuration directive (#8337)
|
||||
|
||||
Improvements:
|
||||
* A way to get RDB file via replication without excessive replication buffers (#8303)
|
||||
* Optimize performance of clusterGenNodesDescription for large clusters (#8182)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* SLOWLOG and LATENCY monitor include unblocking time of blocked commands (#7491)
|
||||
|
||||
Modules:
|
||||
* Add modules API for streams (#8288)
|
||||
* Add event for fork child birth and termination (#8289)
|
||||
* Add RM_BlockedClientMeasureTime* etc, to track background processing in commandstats (#7491)
|
||||
* Fix bug in v6.2, wrong value passed to the new unlink callback (#8381)
|
||||
* Fix bug in v6.2, modules blocked on keys unblock on commands like LPUSH (#8356)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC2 Released Tue Jan 12 16:17:20 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the second Release Candidate of Redis 6.2.
|
||||
|
||||
IMPORTANT: If you're running Redis on ARM64 or a big-endian system, upgrade may
|
||||
have significant implications. Please be sure to read the notes below.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC1,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add the REV, BYLEX and BYSCORE arguments to ZRANGE, and the ZRANGESTORE command (#7844)
|
||||
* Add the XAUTOCLAIM command (#7973)
|
||||
* Add the MINID trimming strategy and the LIMIT argument to XADD and XTRIM (#8169)
|
||||
* Add the ANY argument to GEOSEARCH and GEORADIUS (#8259)
|
||||
* Add the CH, NX, XX arguments to GEOADD (#8227)
|
||||
* Add the COUNT argument to LPOP and RPOP (#8179)
|
||||
* Add the WRITE argument to CLIENT PAUSE for pausing write commands exclusively (#8170)
|
||||
* Change the proto-ver argument of HELLO to optional (#7377)
|
||||
* Add the CLIENT TRACKINGINFO subcommand (#7309)
|
||||
|
||||
Command behavior changes:
|
||||
* CLIENT TRACKING yields an error when given overlapping BCAST prefixes (#8176)
|
||||
* SWAPDB invalidates WATCHed keys (#8239)
|
||||
* SORT command behaves differently when used on a writable replica (#8283)
|
||||
|
||||
Other behavior changes:
|
||||
* Avoid propagating MULTI/EXEC for read-only transactions (#8216)
|
||||
* Remove the read-only flag from TIME, ECHO, ROLE, LASTSAVE (#8216)
|
||||
* Fix the command flags of PFDEBUG (#8222)
|
||||
* Tracking clients will no longer receive unnecessary key invalidation messages after FLUSHDB (#8039)
|
||||
* Sentinel: Fix missing updates to the config file after SENTINEL SET command (#8229)
|
||||
|
||||
Bug fixes with compatibility implications (bugs introduced in Redis 6.0):
|
||||
* Fix RDB CRC64 checksum on big-endian systems (#8270)
|
||||
If you're using big-endian please consider the compatibility implications with
|
||||
RESTORE, replication and persistence.
|
||||
* Fix wrong order of key/value in Lua's map response (#8266)
|
||||
If your scripts use redis.setresp() or return a map (new in Redis 6.0), please
|
||||
consider the implications.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Resolve rare assertions in active defragmentation while loading (#8284, #8281)
|
||||
|
||||
Bug fixes:
|
||||
* Fix the selection of a random element from large hash tables (#8133)
|
||||
* Fix an issue where a forked process deletes the parent's pidfile (#8231)
|
||||
* Fix crashes when enabling io-threads-do-reads (#8230)
|
||||
* Fix a crash in redis-cli after executing cluster backup (#8267)
|
||||
* Fix redis-benchmark to use an IP address for the first cluster node (#8154)
|
||||
* Fix saving of strings larger than 2GB into RDB files (#8306)
|
||||
|
||||
Additional improvements:
|
||||
* Improve replication handshake time (#8214)
|
||||
* Release client tracking table memory asynchronously in cases where the DB is also freed asynchronously (#8039)
|
||||
* Avoid wasteful transient memory allocation in certain cases (#8286, #5954)
|
||||
* Handle binary string values by the 'requirepass' and 'masterauth' configs (#8200)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Install redis-check-rdb and redis-check-aof as symlinks to redis-server (#5745)
|
||||
* Add a check for an ARM64 Linux kernel bug (#8224)
|
||||
Due to the potential severity of this issue, Redis will refuse to run on
|
||||
affected platforms by default.
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add the errorstats section to the INFO command (#8217)
|
||||
* Add the failed_calls and rejected_calls fields INFO's commandstats section (#8217)
|
||||
* Report child copy-on-write metrics continuously (#8264)
|
||||
|
||||
Module API changes:
|
||||
* Add the RedisModule_SendChildCOWInfo API (#8264)
|
||||
* Add the may-replicate command flag (#8170)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC1 Released Mon Dec 14 11:50:00 IST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the first Release Candidate of Redis 6.2.
|
||||
|
||||
Introduction to the Redis 6.2 release
|
||||
=====================================
|
||||
|
||||
This release is the first significant Redis release managed by the core team
|
||||
under the new project governance model.
|
||||
|
||||
Redis 6.2 includes many new commands and improvements, but no big features. It
|
||||
mainly makes Redis more complete and addresses issues that have been requested
|
||||
by many users frequently or for a long time.
|
||||
|
||||
Many of these changes were not eligible for 6.0.x for several reasons:
|
||||
|
||||
1. They are not backward compatible, which is always the case with new or
|
||||
extended commands (that cannot be replicated to an older replica).
|
||||
2. They require a longer release-candidate test cycle.
|
||||
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.0.9,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add SMISMEMBER command that checks multiple members (#7615)
|
||||
* Add ZMSCORE command that returns an array of scores (#7593)
|
||||
* Add LMOVE and BLMOVE commands that pop and push arbitrarily (#6929)
|
||||
* Add RESET command that resets client connection state (#7982)
|
||||
* Add COPY command that copies keys (#7953)
|
||||
* Add ZDIFF and ZDIFFSTORE commands (#7961)
|
||||
* Add ZINTER and ZUNION commands (#7794)
|
||||
* Add GEOSEARCH/GEOSEARCHSTORE commands for bounding box spatial queries (#8094)
|
||||
* Add GET parameter to SET command, for more powerful GETSET (#7852)
|
||||
* Add exclusive range query to XPENDING (#8130)
|
||||
* Add exclusive range query to X[REV]RANGE (#8072)
|
||||
* Add GT and LT options to ZADD for conditional score updates (#7818)
|
||||
* Add CLIENT INFO and CLIENT LIST for specific ids (#8113)
|
||||
* Add IDLE argument to XPENDING command (#7972)
|
||||
* Add local address to CLIENT LIST, and a CLIENT KILL filter. (#7913)
|
||||
* Add NOMKSTREAM option to XADD command (#7910)
|
||||
* Add command introspection to Sentinel (#7940)
|
||||
* Add SENTINEL MYID subcommand (#7858)
|
||||
|
||||
New features:
|
||||
* Dump payload sanitization: prevent corrupt payload causing crashes (#7807)
|
||||
Has flags to enable full O(N) validation (disabled by default).
|
||||
* ACL patterns for Pub/Sub channels (#7993)
|
||||
* Support ACL for Sentinel mode (#7888)
|
||||
* Support getting configuration from both stdin and file at the same time (#7893)
|
||||
Lets you avoid storing secrets on the disk.
|
||||
|
||||
New features in CLI tools:
|
||||
* redis-cli RESP3 push support (#7609)
|
||||
* redis-cli cluster import support source and target that require auth (#7994)
|
||||
* redis-cli URIs able to provide user name in addition to password (#8048)
|
||||
* redis-cli/redis-benchmark allow specifying the prefered ciphers/ciphersuites (#8005)
|
||||
* redis-cli add -e option to exit with code when command execution fails (#8136)
|
||||
|
||||
Command behavior changes:
|
||||
* EXISTS should not alter LRU (#8016)
|
||||
In Redis 5.0 and 6.0 it would have touched the LRU/LFU of the key.
|
||||
* OBJECT should not reveal logically expired keys (#8016)
|
||||
Will now behave the same TYPE or any other non-DEBUG command.
|
||||
* Improve db id range check for SELECT and MOVE (#8085)
|
||||
Changes the error message text on a wrong db index.
|
||||
* Modify AUTH / HELLO error message (#7648)
|
||||
Changes the error message text when the user isn't found or is disabled.
|
||||
* BITOPS length limited to proto_max_bulk_len rather than 512MB (#8096)
|
||||
The limit is now configurable like in SETRANGE, and APPEND.
|
||||
* GEORADIUS[BYMEMBER] can fail with -OOM if Redis is over the memory limit (#8107)
|
||||
|
||||
Other behavior changes:
|
||||
* Optionally (default) fail to start if requested bind address is not available (#7936)
|
||||
If you rely on Redis starting successfully even if one of the bind addresses
|
||||
is not available, you'll need to tune the new config.
|
||||
* Limit the main db dictionaries expansion to prevent key eviction (#7954)
|
||||
In the past big dictionary rehashing could result in massive data eviction.
|
||||
Now this rehashing is delayed (up to a limit), which can result in performance
|
||||
loss due to hash collisions.
|
||||
* CONFIG REWRITE is atomic and safer, but requires write access to the config file's folder (#7824, #8051)
|
||||
This change was already present in 6.0.9, but was missing from the release
|
||||
notes.
|
||||
* A new incremental eviction mechanism that reduces latency on eviction spikes (#7653)
|
||||
In pathological cases this can cause memory to grow uncontrolled and may require
|
||||
specific tuning.
|
||||
* Not resetting "save" config when Redis is started with command line arguments. (#7092)
|
||||
In case you provide command line arguments without "save" and count on it
|
||||
being disabled, Now the defaults "save" config will kick in.
|
||||
* Update memory metrics for INFO during loading (#7690)
|
||||
* When "supervised" config is enabled, it takes precedence over "daemonize". (#8036)
|
||||
* Assertion and panic, print crash log without generating SIGSEGV (#7585)
|
||||
* Added crash log report on SIGABRT, instead of silently exiting (#8004)
|
||||
* Disable THP (Transparent Huge Pages) if enabled (#7381)
|
||||
If you deliberately enabled it, you'll need to config Redis to keep it.
|
||||
|
||||
Bug fixes:
|
||||
* Handle output buffer limits for module blocked clients (#8141)
|
||||
Could result in a module sending reply to a blocked client to go beyond the
|
||||
limit.
|
||||
* Fix setproctitle related crashes. (#8150, #8088)
|
||||
Caused various crashes on startup, mainly on Apple M1 chips or under
|
||||
instrumentation.
|
||||
* A module doing RM_Call could cause replicas to get nested MULTI (#8097).
|
||||
* Backup/restore cluster mode keys to slots map for repl-diskless-load=swapdb (#8108)
|
||||
In cluster mode with repl-diskless-load, when loading failed, slot map
|
||||
wouldn't have been restored.
|
||||
* Fix oom-score-adj-values range, and bug when used in config file (#8046)
|
||||
Enabling setting this in the config file in a line after enabling it, would
|
||||
have been buggy.
|
||||
* Reset average ttl when empty databases (#8106)
|
||||
Just causing misleading metric in INFO
|
||||
* Disable rehash when Redis has child process (#8007)
|
||||
This could have caused excessive CoW during BGSAVE, replication or AOFRW.
|
||||
* Further improved ACL algorithm for picking categories (#7966)
|
||||
Output of ACL GETUSER is now more similar to the one provided by ACL SETUSER.
|
||||
* Fix bug with module GIL being released prematurely (#8061)
|
||||
Could in theory (and rarely) cause multi-threaded modules to corrupt memory.
|
||||
* Fix cluster redirect for module command with no firstkey. (#7539)
|
||||
* Reduce effect of client tracking causing feedback loop in key eviction (#8100)
|
||||
* Kill disk-based fork child when all replicas drop and 'save' is not enabled (#7819)
|
||||
* Rewritten commands (modified for propagation) are logged as their original command (#8006)
|
||||
* Fix cluster access to unaligned memory (SIGBUS on old ARM) #7958
|
||||
* If diskless repl child is killed, make sure to reap the child pid (#7742)
|
||||
* Broadcast a PONG message when slot's migration is over, may reduce MOVED responses (#7571)
|
||||
|
||||
Other improvements:
|
||||
* TLS Support in redis-benchmark (#7959)
|
||||
* Accelerate diskless master connections, and general re-connections (#6271)
|
||||
* Run active defrag while blocked / loading (#7726)
|
||||
* Performance and memory reporting improvement - sds take control of its internal fragmentation (#7875)
|
||||
* Speedup cluster failover. (#7948)
|
||||
|
||||
Platform / toolchain support related improvements:
|
||||
* Optionally (not by default) use H/W Monotonic clock for faster time sampling (#7644)
|
||||
* Remove the requirements for C11 and _Atomic supporting compiler (#7707)
|
||||
This would allow to more easily build and use Redis on older systems and
|
||||
compilers again.
|
||||
* Fix crash log registers output on ARM. (#8020)
|
||||
* Raspberry build fix. (#8095)
|
||||
* Setting process title support for Haiku. (#8060)
|
||||
* DragonFlyBSD RSS memory sampling support. (#8023)
|
||||
|
||||
New configuration options:
|
||||
* Enable configuring OpenSSL using the standard openssl.cnf (#8143)
|
||||
* oom-score-adj-values config can now take absolute values (besides relative ones) (#8046)
|
||||
* TLS: Add different client cert support. (#8076)
|
||||
* Note that a few other changes listed above added their config options.
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add INFO fields to track diskless and disk-based replication progress (#7981)
|
||||
* Add INFO field for main thread cpu time, and scrape system time. (#8132)
|
||||
* Add total_forks to INFO STATS (#8155)
|
||||
* Add maxclients and cluster_connections to INFO CLIENTS (#7979)
|
||||
* Add tracking bcast flag and client redirection in client list (#7995)
|
||||
* Fixed INFO client_recent_max_input_buffer includes argv array (#8065, see #7874)
|
||||
* Note that a few other changes listed above added their info fields.
|
||||
|
||||
Module API changes:
|
||||
* Add CTX_FLAGS_DENY_BLOCKING as a unified the way to know if blocking is allowed (#8025)
|
||||
* Add data type callbacks for lazy free effort, and unlink (#7912)
|
||||
* Add data type callback for COPY command (#8112)
|
||||
* Add callbacks for defrag support. (#8149)
|
||||
* Add module event for repl-diskless-load swapdb (#8153)
|
||||
|
||||
Module related fixes:
|
||||
* Moved RMAPI_FUNC_SUPPORTED so that it's usable (#8037)
|
||||
* Improve timer accuracy (#7987)
|
||||
* Allow '\0' inside of result of RM_CreateStringPrintf (#6260)
|
||||
|
||||
|
||||
Thanks to all the users and developers who made this release possible.
|
||||
We'll follow up with more RC releases, until the code looks production ready
|
||||
and we don't get reports of serious issues for a while.
|
||||
|
||||
A special thank you for the amount of work put into this release by:
|
||||
- Oran Agra
|
||||
- Yossi Gottlieb
|
||||
- Viktor Söderqvist
|
||||
- Yang Bodong
|
||||
- Filipe Oliveira
|
||||
- Guy Benoish
|
||||
- Itamar Haber
|
||||
- Madelyn Olson
|
||||
- Wang Yuan
|
||||
- Felipe Machado
|
||||
- Wen Hui
|
||||
- Tatsuya Arisawa
|
||||
- Jonah H. Harris
|
||||
- Raghav Muddur
|
||||
- Jim Brunner
|
||||
- Yaacov Hazan
|
||||
- Allen Farris
|
||||
- Chen Yang
|
||||
- Nitai Caro
|
||||
- sundb
|
||||
- Meir Shpilraien
|
||||
- maohuazhu
|
||||
- Valentino Geron
|
||||
- Zhao Zhao
|
||||
- Qu Chen
|
||||
- George Prekas
|
||||
- Tyson Andre
|
||||
- Uri Yagelnik
|
||||
- Michael Grunder
|
||||
- Huang Zw
|
||||
- alexronke-channeladvisor
|
||||
- Andy Pan
|
||||
- Wu Yunlong
|
||||
- Wei Kukey
|
||||
- Yoav Steinberg
|
||||
- Greg Femec
|
||||
- Uri Shachar
|
||||
- Nykolas Laurentino de Lima
|
||||
- xhe
|
||||
- zhenwei pi
|
||||
- David CARLIER
|
||||
|
||||
Migrating from 6.0 to 6.2
|
||||
=========================
|
||||
|
||||
Redis 6.2 is mostly a strict superset of 6.0, you should not have any problem
|
||||
upgrading your application from 6.0 to 6.2. However there are some small changes
|
||||
of behavior listed above, please make sure you are not badly affected by any of
|
||||
them.
|
||||
|
||||
Specifically these sections:
|
||||
* Command behavior changes
|
||||
* Other behavior changes
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Cheers,
|
||||
The Redis team
|
||||
|
||||
Vendored
+7
@@ -2,6 +2,8 @@
|
||||
|
||||
uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
LUA_COVERAGE?=no
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
@@ -71,6 +73,11 @@ endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' -DLUA_USE_MKSTEMP $(CFLAGS)
|
||||
LUA_LDFLAGS+= $(LDFLAGS)
|
||||
ifeq ($(LUA_COVERAGE),yes)
|
||||
LUA_CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
LUA_LDFLAGS += -fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
|
||||
Vendored
+6
-1
@@ -77,7 +77,12 @@ ifeq ($(USE_SSL),1)
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
ifdef OPENSSL_PREFIX
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
else
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
endif
|
||||
else
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
|
||||
Vendored
+4
@@ -68,6 +68,10 @@ void *hi_malloc(size_t size) {
|
||||
}
|
||||
|
||||
void *hi_calloc(size_t nmemb, size_t size) {
|
||||
/* Overflow check as the user can specify any arbitrary allocator */
|
||||
if (SIZE_MAX / size < nmemb)
|
||||
return NULL;
|
||||
|
||||
return hiredisAllocFns.callocFn(nmemb, size);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+5
@@ -32,6 +32,7 @@
|
||||
#define HIREDIS_ALLOC_H
|
||||
|
||||
#include <stddef.h> /* for size_t */
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -59,6 +60,10 @@ static inline void *hi_malloc(size_t size) {
|
||||
}
|
||||
|
||||
static inline void *hi_calloc(size_t nmemb, size_t size) {
|
||||
/* Overflow check as the user can specify any arbitrary allocator */
|
||||
if (SIZE_MAX / size < nmemb)
|
||||
return NULL;
|
||||
|
||||
return hiredisAllocFns.callocFn(nmemb, size);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+14
@@ -498,6 +498,20 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Multi-bulk never overflows regardless of maxelements: ");
|
||||
size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;
|
||||
char bad_mbulk_reply[100];
|
||||
snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n",
|
||||
(unsigned long long) bad_mbulk_len);
|
||||
|
||||
reader = redisReaderCreate();
|
||||
reader->maxelements = 0; /* Don't rely on default limit */
|
||||
redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
|
||||
@@ -235,7 +235,7 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr) {
|
||||
int free_in_slab = extent_nfree_get(slab);
|
||||
if (free_in_slab) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
int curslabs = bin->stats.curslabs;
|
||||
unsigned long curslabs = bin->stats.curslabs;
|
||||
size_t curregs = bin->stats.curregs;
|
||||
if (bin->slabcur) {
|
||||
/* remove slabcur from the overall utilization */
|
||||
|
||||
Vendored
+22
@@ -674,6 +674,8 @@ LUA_API void lua_rawset (lua_State *L, int idx) {
|
||||
api_checknelems(L, 2);
|
||||
t = index2adr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
if (hvalue(t)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);
|
||||
luaC_barriert(L, hvalue(t), L->top-1);
|
||||
L->top -= 2;
|
||||
@@ -687,6 +689,8 @@ LUA_API void lua_rawseti (lua_State *L, int idx, int n) {
|
||||
api_checknelems(L, 1);
|
||||
o = index2adr(L, idx);
|
||||
api_check(L, ttistable(o));
|
||||
if (hvalue(o)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1);
|
||||
luaC_barriert(L, hvalue(o), L->top-1);
|
||||
L->top--;
|
||||
@@ -709,6 +713,8 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) {
|
||||
}
|
||||
switch (ttype(obj)) {
|
||||
case LUA_TTABLE: {
|
||||
if (hvalue(obj)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
hvalue(obj)->metatable = mt;
|
||||
if (mt)
|
||||
luaC_objbarriert(L, hvalue(obj), mt);
|
||||
@@ -1085,3 +1091,19 @@ LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) {
|
||||
return name;
|
||||
}
|
||||
|
||||
LUA_API void lua_enablereadonlytable (lua_State *L, int objindex, int enabled) {
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
api_check(L, t != hvalue(registry(L)));
|
||||
t->readonly = enabled;
|
||||
}
|
||||
|
||||
LUA_API int lua_isreadonlytable (lua_State *L, int objindex) {
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
api_check(L, t != hvalue(registry(L)));
|
||||
return t->readonly;
|
||||
}
|
||||
|
||||
|
||||
Vendored
-1
@@ -80,7 +80,6 @@ LUA_API int lua_gethookcount (lua_State *L) {
|
||||
return L->basehookcount;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
|
||||
int status;
|
||||
CallInfo *ci;
|
||||
|
||||
Vendored
+2
-1
@@ -337,7 +337,8 @@ typedef struct Node {
|
||||
|
||||
typedef struct Table {
|
||||
CommonHeader;
|
||||
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
|
||||
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
|
||||
int readonly;
|
||||
lu_byte lsizenode; /* log2 of size of `node' array */
|
||||
struct Table *metatable;
|
||||
TValue *array; /* array part */
|
||||
|
||||
Vendored
+1
@@ -364,6 +364,7 @@ Table *luaH_new (lua_State *L, int narray, int nhash) {
|
||||
t->array = NULL;
|
||||
t->sizearray = 0;
|
||||
t->lsizenode = 0;
|
||||
t->readonly = 0;
|
||||
t->node = cast(Node *, dummynode);
|
||||
setarrayvector(L, t, narray);
|
||||
setnodevector(L, t, nhash);
|
||||
|
||||
Vendored
+3
@@ -358,6 +358,9 @@ struct lua_Debug {
|
||||
int i_ci; /* active function */
|
||||
};
|
||||
|
||||
LUA_API void lua_enablereadonlytable (lua_State *L, int index, int enabled);
|
||||
LUA_API int lua_isreadonlytable (lua_State *L, int objindex);
|
||||
|
||||
/* }====================================================================== */
|
||||
|
||||
|
||||
|
||||
Vendored
+1
@@ -131,6 +131,7 @@ static int bit_tohex(lua_State *L)
|
||||
const char *hexdigits = "0123456789abcdef";
|
||||
char buf[8];
|
||||
int i;
|
||||
if (n == INT32_MIN) n = INT32_MIN+1;
|
||||
if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; }
|
||||
if (n > 8) n = 8;
|
||||
for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; }
|
||||
|
||||
Vendored
+6
-3
@@ -39,6 +39,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
@@ -141,13 +142,13 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
json_token_type_t type;
|
||||
int index;
|
||||
size_t index;
|
||||
union {
|
||||
const char *string;
|
||||
double number;
|
||||
int boolean;
|
||||
} value;
|
||||
int string_len;
|
||||
size_t string_len;
|
||||
} json_token_t;
|
||||
|
||||
static const char *char2escape[256] = {
|
||||
@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
* This buffer is reused constantly for small strings
|
||||
* If there are any excess pages, they won't be hit anyway.
|
||||
* This gains ~5% speedup. */
|
||||
if (len > SIZE_MAX / 6 - 3)
|
||||
abort(); /* Overflow check */
|
||||
strbuf_ensure_empty_length(json, len * 6 + 2);
|
||||
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
@@ -706,7 +709,7 @@ static int json_encode(lua_State *l)
|
||||
strbuf_t local_encode_buf;
|
||||
strbuf_t *encode_buf;
|
||||
char *json;
|
||||
int len;
|
||||
size_t len;
|
||||
|
||||
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
||||
|
||||
|
||||
Vendored
+17
-14
@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) {
|
||||
|
||||
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newsize = (buf->len+len)*2;
|
||||
size_t newsize = buf->len+len;
|
||||
if (newsize < buf->len || newsize >= SIZE_MAX/2) abort();
|
||||
newsize *= 2;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
|
||||
buf->free = newsize - buf->len;
|
||||
@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
|
||||
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
size_t hdrlen;
|
||||
|
||||
if (len < 32) {
|
||||
hdr[0] = 0xa0 | (len&0xff); /* fix raw */
|
||||
@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
|
||||
|
||||
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n >= 0) {
|
||||
if (n <= 127) {
|
||||
@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n <= 15) {
|
||||
b[0] = 0x90 | (n & 0xf); /* fix array */
|
||||
@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -790,7 +792,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
@@ -802,10 +804,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */
|
||||
return luaL_error(L,
|
||||
"Invalid request to unpack with offset of %d and limit of %d.",
|
||||
offset, len);
|
||||
(int) offset, (int) len);
|
||||
else if (offset > len)
|
||||
return luaL_error(L,
|
||||
"Start offset %d greater than input length %d.", offset, len);
|
||||
"Start offset %d greater than input length %d.", (int) offset, (int) len);
|
||||
|
||||
if (decode_all) limit = INT_MAX;
|
||||
|
||||
@@ -827,12 +829,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
/* c->left is the remaining size of the input buffer.
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
size_t new_offset = len - c.left;
|
||||
if (new_offset > LONG_MAX) abort();
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset);
|
||||
/* Results are returned with the arg elements still
|
||||
* in place. Lua takes care of only returning
|
||||
* elements above the args for us.
|
||||
@@ -851,15 +854,15 @@ int mp_unpack(lua_State *L) {
|
||||
}
|
||||
|
||||
int mp_unpack_one(lua_State *L) {
|
||||
int offset = luaL_optinteger(L, 2, 0);
|
||||
lua_Integer offset = luaL_optinteger(L, 2, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
return mp_unpack_full(L, 1, offset);
|
||||
}
|
||||
|
||||
int mp_unpack_limit(lua_State *L) {
|
||||
int limit = luaL_checkinteger(L, 2);
|
||||
int offset = luaL_optinteger(L, 3, 0);
|
||||
lua_Integer limit = luaL_checkinteger(L, 2);
|
||||
lua_Integer offset = luaL_optinteger(L, 3, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
|
||||
|
||||
Vendored
+2
@@ -138,6 +138,8 @@ void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
const TValue *tm;
|
||||
if (ttistable(t)) { /* `t' is a table? */
|
||||
Table *h = hvalue(t);
|
||||
if (h->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
TValue *oldval = luaH_set(L, h, key); /* do a primitive set */
|
||||
if (!ttisnil(oldval) || /* result is no nil? */
|
||||
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
|
||||
|
||||
Vendored
+28
-81
@@ -26,6 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
@@ -38,22 +39,22 @@ static void die(const char *fmt, ...)
|
||||
va_end(arg);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
exit(-1);
|
||||
abort();
|
||||
}
|
||||
|
||||
void strbuf_init(strbuf_t *s, int len)
|
||||
void strbuf_init(strbuf_t *s, size_t len)
|
||||
{
|
||||
int size;
|
||||
size_t size;
|
||||
|
||||
if (len <= 0)
|
||||
if (!len)
|
||||
size = STRBUF_DEFAULT_SIZE;
|
||||
else
|
||||
size = len + 1; /* \0 terminator */
|
||||
|
||||
size = len + 1;
|
||||
if (size < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
s->buf = NULL;
|
||||
s->size = size;
|
||||
s->length = 0;
|
||||
s->increment = STRBUF_DEFAULT_INCREMENT;
|
||||
s->dynamic = 0;
|
||||
s->reallocs = 0;
|
||||
s->debug = 0;
|
||||
@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len)
|
||||
strbuf_ensure_null(s);
|
||||
}
|
||||
|
||||
strbuf_t *strbuf_new(int len)
|
||||
strbuf_t *strbuf_new(size_t len)
|
||||
{
|
||||
strbuf_t *s;
|
||||
|
||||
@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
|
||||
return s;
|
||||
}
|
||||
|
||||
void strbuf_set_increment(strbuf_t *s, int increment)
|
||||
{
|
||||
/* Increment > 0: Linear buffer growth rate
|
||||
* Increment < -1: Exponential buffer growth rate */
|
||||
if (increment == 0 || increment == -1)
|
||||
die("BUG: Invalid string increment");
|
||||
|
||||
s->increment = increment;
|
||||
}
|
||||
|
||||
static inline void debug_stats(strbuf_t *s)
|
||||
{
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
|
||||
(long)s, s->reallocs, s->length, s->size);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
|
||||
free(s);
|
||||
}
|
||||
|
||||
char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int calculate_new_size(strbuf_t *s, int len)
|
||||
static size_t calculate_new_size(strbuf_t *s, size_t len)
|
||||
{
|
||||
int reqsize, newsize;
|
||||
size_t reqsize, newsize;
|
||||
|
||||
if (len <= 0)
|
||||
die("BUG: Invalid strbuf length requested");
|
||||
|
||||
/* Ensure there is room for optional NULL termination */
|
||||
reqsize = len + 1;
|
||||
if (reqsize < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
|
||||
/* If the user has requested to shrink the buffer, do it exactly */
|
||||
if (s->size > reqsize)
|
||||
return reqsize;
|
||||
|
||||
newsize = s->size;
|
||||
if (s->increment < 0) {
|
||||
if (reqsize >= SIZE_MAX / 2) {
|
||||
newsize = reqsize;
|
||||
} else {
|
||||
/* Exponential sizing */
|
||||
while (newsize < reqsize)
|
||||
newsize *= -s->increment;
|
||||
} else {
|
||||
/* Linear sizing */
|
||||
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
|
||||
newsize *= 2;
|
||||
}
|
||||
|
||||
if (newsize < reqsize)
|
||||
die("BUG: strbuf length would overflow, len: %zu", len);
|
||||
|
||||
return newsize;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
|
||||
* optional termination). */
|
||||
void strbuf_resize(strbuf_t *s, int len)
|
||||
void strbuf_resize(strbuf_t *s, size_t len)
|
||||
{
|
||||
int newsize;
|
||||
size_t newsize;
|
||||
|
||||
newsize = calculate_new_size(s, len);
|
||||
|
||||
if (s->debug > 1) {
|
||||
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
|
||||
(long)s, s->size, newsize);
|
||||
}
|
||||
|
||||
s->size = newsize;
|
||||
s->buf = realloc(s->buf, s->size);
|
||||
if (!s->buf)
|
||||
die("Out of memory");
|
||||
die("Out of memory, len: %zu", len);
|
||||
s->reallocs++;
|
||||
}
|
||||
|
||||
void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
{
|
||||
int space, i;
|
||||
int i;
|
||||
size_t space;
|
||||
|
||||
space = strbuf_empty_length(s);
|
||||
|
||||
@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
}
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt() should only be used when an upper bound
|
||||
* is known for the output string. */
|
||||
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len;
|
||||
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
|
||||
va_start(arg, fmt);
|
||||
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len < 0)
|
||||
die("BUG: Unable to convert number"); /* This should never happen.. */
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt_retry() can be used when the there is no known
|
||||
* upper bound for the output string. */
|
||||
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len, try;
|
||||
int empty_len;
|
||||
|
||||
/* If the first attempt to append fails, resize the buffer appropriately
|
||||
* and try again */
|
||||
for (try = 0; ; try++) {
|
||||
va_start(arg, fmt);
|
||||
/* Append the new formatted string */
|
||||
/* fmt_len is the length of the string required, excluding the
|
||||
* trailing NULL */
|
||||
empty_len = strbuf_empty_length(s);
|
||||
/* Add 1 since there is also space to store the terminating NULL. */
|
||||
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len <= empty_len)
|
||||
break; /* SUCCESS */
|
||||
if (try > 0)
|
||||
die("BUG: length of formatted string changed");
|
||||
|
||||
strbuf_resize(s, s->length + fmt_len);
|
||||
}
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
|
||||
Vendored
+19
-27
@@ -27,15 +27,13 @@
|
||||
|
||||
/* Size: Total bytes allocated to *buf
|
||||
* Length: String length, excluding optional NULL terminator.
|
||||
* Increment: Allocation increments when resizing the string buffer.
|
||||
* Dynamic: True if created via strbuf_new()
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int size;
|
||||
int length;
|
||||
int increment;
|
||||
size_t size;
|
||||
size_t length;
|
||||
int dynamic;
|
||||
int reallocs;
|
||||
int debug;
|
||||
@@ -44,32 +42,26 @@ typedef struct {
|
||||
#ifndef STRBUF_DEFAULT_SIZE
|
||||
#define STRBUF_DEFAULT_SIZE 1023
|
||||
#endif
|
||||
#ifndef STRBUF_DEFAULT_INCREMENT
|
||||
#define STRBUF_DEFAULT_INCREMENT -2
|
||||
#endif
|
||||
|
||||
/* Initialise */
|
||||
extern strbuf_t *strbuf_new(int len);
|
||||
extern void strbuf_init(strbuf_t *s, int len);
|
||||
extern void strbuf_set_increment(strbuf_t *s, int increment);
|
||||
extern strbuf_t *strbuf_new(size_t len);
|
||||
extern void strbuf_init(strbuf_t *s, size_t len);
|
||||
|
||||
/* Release */
|
||||
extern void strbuf_free(strbuf_t *s);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
|
||||
|
||||
/* Management */
|
||||
extern void strbuf_resize(strbuf_t *s, int len);
|
||||
static int strbuf_empty_length(strbuf_t *s);
|
||||
static int strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, int *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
|
||||
extern void strbuf_resize(strbuf_t *s, size_t len);
|
||||
static size_t strbuf_empty_length(strbuf_t *s);
|
||||
static size_t strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, size_t *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
|
||||
static char *strbuf_empty_ptr(strbuf_t *s);
|
||||
static void strbuf_extend_length(strbuf_t *s, int len);
|
||||
static void strbuf_extend_length(strbuf_t *s, size_t len);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
|
||||
extern void strbuf_append_string(strbuf_t *s, const char *str);
|
||||
static void strbuf_append_char(strbuf_t *s, const char c);
|
||||
static void strbuf_ensure_null(strbuf_t *s);
|
||||
@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s)
|
||||
|
||||
/* Return bytes remaining in the string buffer
|
||||
* Ensure there is space for a NULL terminator. */
|
||||
static inline int strbuf_empty_length(strbuf_t *s)
|
||||
static inline size_t strbuf_empty_length(strbuf_t *s)
|
||||
{
|
||||
return s->size - s->length - 1;
|
||||
}
|
||||
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
if (len > strbuf_empty_length(s))
|
||||
strbuf_resize(s, s->length + len);
|
||||
@@ -103,12 +95,12 @@ static inline char *strbuf_empty_ptr(strbuf_t *s)
|
||||
return s->buf + s->length;
|
||||
}
|
||||
|
||||
static inline void strbuf_extend_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline int strbuf_length(strbuf_t *s)
|
||||
static inline size_t strbuf_length(strbuf_t *s)
|
||||
{
|
||||
return s->length;
|
||||
}
|
||||
@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
|
||||
s->buf[s->length++] = c;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
|
||||
s->buf[s->length] = 0;
|
||||
}
|
||||
|
||||
static inline char *strbuf_string(strbuf_t *s, int *len)
|
||||
static inline char *strbuf_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
if (len)
|
||||
*len = s->length;
|
||||
|
||||
+3
-1
@@ -173,7 +173,9 @@ tcp-keepalive 300
|
||||
#
|
||||
# tls-client-key-file-pass secret
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange,
|
||||
# required by older versions of OpenSSL (<3.0). Newer versions do not require
|
||||
# this configuration and recommend against it.
|
||||
#
|
||||
# tls-dh-params-file redis.dh
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
[ -z "$MAKE" ] && MAKE=make
|
||||
|
||||
@@ -16,6 +16,7 @@ fi
|
||||
$MAKE -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/commandfilter \
|
||||
--single unit/moduleapi/basics \
|
||||
--single unit/moduleapi/fork \
|
||||
--single unit/moduleapi/testrdb \
|
||||
--single unit/moduleapi/infotest \
|
||||
@@ -35,4 +36,5 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/hash \
|
||||
--single unit/moduleapi/zset \
|
||||
--single unit/moduleapi/stream \
|
||||
--single unit/moduleapi/cluster \
|
||||
"${@}"
|
||||
|
||||
+21
-12
@@ -31,11 +31,13 @@ ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
endif
|
||||
endif
|
||||
WARN=-Wall -W -Wno-missing-field-initializers
|
||||
WARN=-Wall -W -Wno-missing-field-initializers -Wno-strict-prototypes
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
# Detect if the compiler supports C11 _Atomic
|
||||
C11_ATOMIC := $(shell sh -c 'echo "\#include <stdatomic.h>" > foo.c; \
|
||||
# Detect if the compiler supports C11 _Atomic.
|
||||
# NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions.
|
||||
NUMBER_SIGN_CHAR := \#
|
||||
C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include <stdatomic.h>" > foo.c; \
|
||||
$(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \
|
||||
if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
|
||||
ifeq ($(C11_ATOMIC),yes)
|
||||
@@ -93,14 +95,10 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM needs -latomic at linking time
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
else
|
||||
# Linux ARM32 needs -latomic at linking time
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
@@ -125,12 +123,10 @@ ifeq ($(uname_S),Darwin)
|
||||
# must be referenced explicitly during build.
|
||||
ifeq ($(uname_M),arm64)
|
||||
# Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX
|
||||
OPENSSL_CFLAGS=-I/opt/homebrew/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/opt/homebrew/opt/openssl/lib
|
||||
OPENSSL_PREFIX?=/opt/homebrew/opt/openssl
|
||||
else
|
||||
# Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX
|
||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
endif
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
@@ -192,6 +188,14 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef OPENSSL_PREFIX
|
||||
OPENSSL_CFLAGS=-I$(OPENSSL_PREFIX)/include
|
||||
OPENSSL_LDFLAGS=-L$(OPENSSL_PREFIX)/lib
|
||||
# Also export OPENSSL_PREFIX so it ends up in deps sub-Makefiles
|
||||
export OPENSSL_PREFIX
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
|
||||
|
||||
@@ -379,6 +383,8 @@ clean:
|
||||
|
||||
distclean: clean
|
||||
-(cd ../deps && $(MAKE) distclean)
|
||||
-(cd modules && $(MAKE) clean)
|
||||
-(cd ../tests/modules && $(MAKE) clean)
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@@ -386,6 +392,9 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-modules: $(REDIS_SERVER_NAME)
|
||||
@(cd ..; ./runtest-moduleapi)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
|
||||
@@ -1284,7 +1284,14 @@ void ACLKillPubsubClientsIfNeeded(user *u, list *upcoming) {
|
||||
listNode *ln, *lpn;
|
||||
robj *o;
|
||||
int kill = 0;
|
||||
|
||||
|
||||
/* If any of the original rule has the all-channels permission, but the new
|
||||
* one doesn't (verifed by the caller), then the new list is not a strict
|
||||
* superset of the original, and the next loop can be skipped. */
|
||||
if (u->flags & USER_FLAG_ALLCHANNELS) {
|
||||
kill = 1;
|
||||
}
|
||||
|
||||
/* Nothing to kill when the upcoming are a literal super set of the original
|
||||
* permissions. */
|
||||
listRewind(u->channels,&li);
|
||||
@@ -1892,10 +1899,12 @@ void addACLLogEntry(client *c, int reason, int argpos, sds username) {
|
||||
void aclCommand(client *c) {
|
||||
char *sub = c->argv[1]->ptr;
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
/* Consider information about passwords or permissions
|
||||
* to be sensitive, which will be the arguments for this
|
||||
* subcommand. */
|
||||
preventCommandLogging(c);
|
||||
/* Initially redact all of the arguments to not leak any information
|
||||
* about the user. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
redactClientCommandArgument(c, j);
|
||||
}
|
||||
|
||||
sds username = c->argv[2]->ptr;
|
||||
/* Check username validity. */
|
||||
if (ACLStringHasSpaces(username,sdslen(username))) {
|
||||
@@ -2245,6 +2254,8 @@ void authCommand(client *c) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
/* Always redact the second argument */
|
||||
redactClientCommandArgument(c, 1);
|
||||
|
||||
/* Handle the two different forms here. The form with two arguments
|
||||
* will just use "default" as username. */
|
||||
@@ -2264,6 +2275,7 @@ void authCommand(client *c) {
|
||||
} else {
|
||||
username = c->argv[1];
|
||||
password = c->argv[2];
|
||||
redactClientCommandArgument(c, 2);
|
||||
}
|
||||
|
||||
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
||||
|
||||
+55
-7
@@ -36,8 +36,29 @@
|
||||
typedef struct aeApiState {
|
||||
int kqfd;
|
||||
struct kevent *events;
|
||||
|
||||
/* Events mask for merge read and write event.
|
||||
* To reduce memory consumption, we use 2 bits to store the mask
|
||||
* of an event, so that 1 byte will store the mask of 4 events. */
|
||||
char *eventsMask;
|
||||
} aeApiState;
|
||||
|
||||
#define EVENT_MASK_MALLOC_SIZE(sz) (((sz) + 3) / 4)
|
||||
#define EVENT_MASK_OFFSET(fd) ((fd) % 4 * 2)
|
||||
#define EVENT_MASK_ENCODE(fd, mask) (((mask) & 0x3) << EVENT_MASK_OFFSET(fd))
|
||||
|
||||
static inline int getEventMask(const char *eventsMask, int fd) {
|
||||
return (eventsMask[fd/4] >> EVENT_MASK_OFFSET(fd)) & 0x3;
|
||||
}
|
||||
|
||||
static inline void addEventMask(char *eventsMask, int fd, int mask) {
|
||||
eventsMask[fd/4] |= EVENT_MASK_ENCODE(fd, mask);
|
||||
}
|
||||
|
||||
static inline void resetEventMask(char *eventsMask, int fd) {
|
||||
eventsMask[fd/4] &= ~EVENT_MASK_ENCODE(fd, 0x3);
|
||||
}
|
||||
|
||||
static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = zmalloc(sizeof(aeApiState));
|
||||
|
||||
@@ -54,6 +75,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return -1;
|
||||
}
|
||||
anetCloexec(state->kqfd);
|
||||
state->eventsMask = zmalloc(EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
}
|
||||
@@ -62,6 +85,8 @@ static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
state->events = zrealloc(state->events, sizeof(struct kevent)*setsize);
|
||||
state->eventsMask = zrealloc(state->eventsMask, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -70,6 +95,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
|
||||
close(state->kqfd);
|
||||
zfree(state->events);
|
||||
zfree(state->eventsMask);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
@@ -120,15 +146,37 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
if (retval > 0) {
|
||||
int j;
|
||||
|
||||
numevents = retval;
|
||||
for(j = 0; j < numevents; j++) {
|
||||
int mask = 0;
|
||||
/* Normally we execute the read event first and then the write event.
|
||||
* When the barrier is set, we will do it reverse.
|
||||
*
|
||||
* However, under kqueue, read and write events would be separate
|
||||
* events, which would make it impossible to control the order of
|
||||
* reads and writes. So we store the event's mask we've got and merge
|
||||
* the same fd events later. */
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = 0;
|
||||
|
||||
if (e->filter == EVFILT_READ) mask |= AE_READABLE;
|
||||
if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE;
|
||||
eventLoop->fired[j].fd = e->ident;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
if (e->filter == EVFILT_READ) mask = AE_READABLE;
|
||||
else if (e->filter == EVFILT_WRITE) mask = AE_WRITABLE;
|
||||
addEventMask(state->eventsMask, fd, mask);
|
||||
}
|
||||
|
||||
/* Re-traversal to merge read and write events, and set the fd's mask to
|
||||
* 0 so that events are not added again when the fd is encountered again. */
|
||||
numevents = 0;
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = getEventMask(state->eventsMask, fd);
|
||||
|
||||
if (mask) {
|
||||
eventLoop->fired[numevents].fd = fd;
|
||||
eventLoop->fired[numevents].mask = mask;
|
||||
resetEventMask(state->eventsMask, fd);
|
||||
numevents++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return numevents;
|
||||
|
||||
+30
-5
@@ -397,13 +397,16 @@ int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||
return s;
|
||||
}
|
||||
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog, mode_t perm) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
if (sa->sa_family == AF_LOCAL && perm)
|
||||
chmod(((struct sockaddr_un *) sa)->sun_path, perm);
|
||||
|
||||
if (listen(s, backlog) == -1) {
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
@@ -447,7 +450,7 @@ 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 (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog,0) == ANET_ERR) s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -484,10 +487,8 @@ int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog,perm) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -593,3 +594,27 @@ int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
|
||||
anetFdToString(fd,ip,sizeof(ip),&port,fd_to_str_type);
|
||||
return anetFormatAddr(buf, buf_len, ip, port);
|
||||
}
|
||||
|
||||
/* This function must be called after accept4() fails. It returns 1 if 'err'
|
||||
* indicates accepted connection faced an error, and it's okay to continue
|
||||
* accepting next connection by calling accept4() again. Other errors either
|
||||
* indicate programming errors, e.g. calling accept() on a closed fd or indicate
|
||||
* a resource limit has been reached, e.g. -EMFILE, open fd limit has been
|
||||
* reached. In the latter case, caller might wait until resources are available.
|
||||
* See accept4() documentation for details. */
|
||||
int anetAcceptFailureNeedsRetry(int err) {
|
||||
if (err == ECONNABORTED)
|
||||
return 1;
|
||||
|
||||
#if defined(__linux__)
|
||||
/* For details, see 'Error Handling' section on
|
||||
* https://man7.org/linux/man-pages/man2/accept.2.html */
|
||||
if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT ||
|
||||
err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH ||
|
||||
err == EOPNOTSUPP || err == ENETUNREACH)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -72,5 +72,5 @@ int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_typ
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
|
||||
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
|
||||
|
||||
int anetAcceptFailureNeedsRetry(int err);
|
||||
#endif
|
||||
|
||||
@@ -162,7 +162,9 @@ 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(server.el,server.aof_pipe_write_data_to_child) == 0) {
|
||||
if (!server.aof_stop_sending_diff &&
|
||||
aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0)
|
||||
{
|
||||
aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
|
||||
+19
-18
@@ -37,8 +37,8 @@
|
||||
/* 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 an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
long long redisPopcount(void *s, long count) {
|
||||
long long bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
@@ -98,11 +98,11 @@ 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 long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
long long pos = 0; /* Position of bit, to return to the caller. */
|
||||
unsigned long j;
|
||||
int found;
|
||||
|
||||
@@ -328,8 +328,9 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
|
||||
|
||||
/* Note that maxincr and minincr could overflow, but we use the values
|
||||
* only after checking 'value' range, so when we use it no overflow
|
||||
* happens. */
|
||||
int64_t maxincr = max-value;
|
||||
* happens. 'uint64_t' cast is there just to prevent undefined behavior on
|
||||
* overflow */
|
||||
int64_t maxincr = (uint64_t)max-value;
|
||||
int64_t minincr = min-value;
|
||||
|
||||
if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
|
||||
@@ -410,7 +411,7 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
* If the 'hash' argument is true, and 'bits is positive, then the command
|
||||
* will also parse bit offsets prefixed by "#". In such a case the offset
|
||||
* is multiplied by 'bits'. This is useful for the BITFIELD command. */
|
||||
int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int bits) {
|
||||
int getBitOffsetFromArgument(client *c, robj *o, uint64_t *offset, int hash, int bits) {
|
||||
long long loffset;
|
||||
char *err = "bit offset is not an integer or out of range";
|
||||
char *p = o->ptr;
|
||||
@@ -435,7 +436,7 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int b
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
*offset = (size_t)loffset;
|
||||
*offset = loffset;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -477,7 +478,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
|
||||
* so that the 'maxbit' bit can be addressed. The object is finally
|
||||
* returned. Otherwise if the key holds a wrong type NULL is returned and
|
||||
* an error is sent to the client. */
|
||||
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||
robj *lookupStringForBitCommand(client *c, uint64_t maxbit) {
|
||||
size_t byte = maxbit >> 3;
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||
@@ -527,7 +528,7 @@ unsigned char *getObjectReadOnlyString(robj *o, long *len, char *llbuf) {
|
||||
void setbitCommand(client *c) {
|
||||
robj *o;
|
||||
char *err = "bit is not an integer or out of range";
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
ssize_t byte, bit;
|
||||
int byteval, bitval;
|
||||
long on;
|
||||
@@ -566,7 +567,7 @@ void setbitCommand(client *c) {
|
||||
void getbitCommand(client *c) {
|
||||
robj *o;
|
||||
char llbuf[32];
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
size_t byte, bit;
|
||||
size_t bitval = 0;
|
||||
|
||||
@@ -888,7 +889,7 @@ void bitposCommand(client *c) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
long long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
@@ -897,11 +898,11 @@ void bitposCommand(client *c) {
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
if (end_given && bit == 0 && pos == (long long)bytes<<3) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
if (pos != -1) pos += (long long)start<<3; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
@@ -933,12 +934,12 @@ struct bitfieldOp {
|
||||
* GET subcommand is allowed, other subcommands will return an error. */
|
||||
void bitfieldGeneric(client *c, int flags) {
|
||||
robj *o;
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
size_t highest_write_offset = 0;
|
||||
uint64_t highest_write_offset = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
int remargs = c->argc-j-1; /* Remaining args other than current. */
|
||||
@@ -1128,9 +1129,9 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
* object boundaries. */
|
||||
memset(buf,0,9);
|
||||
int i;
|
||||
size_t byte = thisop->offset >> 3;
|
||||
uint64_t byte = thisop->offset >> 3;
|
||||
for (i = 0; i < 9; i++) {
|
||||
if (src == NULL || i+byte >= (size_t)strlen) break;
|
||||
if (src == NULL || i+byte >= (uint64_t)strlen) break;
|
||||
buf[i] = src[i+byte];
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ typedef struct bkinfo {
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
* and will be processed when the client is unblocked. */
|
||||
void blockClient(client *c, int btype) {
|
||||
/* Master client should never be blocked unless pause or module */
|
||||
serverAssert(!(c->flags & CLIENT_MASTER &&
|
||||
btype != BLOCKED_MODULE &&
|
||||
btype != BLOCKED_PAUSE));
|
||||
|
||||
c->flags |= CLIENT_BLOCKED;
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
@@ -289,6 +294,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
@@ -301,6 +308,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
}
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
@@ -344,11 +353,15 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
@@ -438,6 +451,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
}
|
||||
}
|
||||
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
/* Emit the two elements sub-array consisting of
|
||||
@@ -466,6 +481,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,12 +527,16 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
* different modules with different triggers to consider if a key
|
||||
* is ready or not. This means we can't exit the loop but need
|
||||
* to continue after the first failure. */
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
|
||||
moduleUnblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -93,7 +93,7 @@ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress,
|
||||
if (cow) {
|
||||
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
|
||||
"%s: %zu MB of memory used by copy-on-write",
|
||||
pname, data.cow / (1024 * 1024));
|
||||
pname, cow / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,9 @@ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress,
|
||||
ssize_t wlen = sizeof(data);
|
||||
|
||||
if (write(server.child_info_pipe[1], &data, wlen) != wlen) {
|
||||
/* Nothing to do on error, this will be detected by the other side. */
|
||||
/* Failed writing to parent, it could have been killed, exit. */
|
||||
serverLog(LL_WARNING,"Child failed reporting info to parent, exiting. %s", strerror(errno));
|
||||
exitFromChild(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-9
@@ -536,6 +536,8 @@ void clusterInit(void) {
|
||||
exit(1);
|
||||
}
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR, &server.cfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", port);
|
||||
exit(1);
|
||||
}
|
||||
if (createSocketAcceptHandler(&server.cfd, clusterAcceptHandler) != C_OK) {
|
||||
@@ -552,6 +554,7 @@ void clusterInit(void) {
|
||||
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
|
||||
|
||||
server.cluster->mf_end = 0;
|
||||
server.cluster->mf_slave = NULL;
|
||||
resetManualFailover();
|
||||
clusterUpdateMyselfFlags();
|
||||
}
|
||||
@@ -688,6 +691,8 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node: %s", server.neterr);
|
||||
@@ -707,6 +712,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
connNonBlock(conn);
|
||||
connEnableTcpNoDelay(conn);
|
||||
connKeepAlive(conn,server.cluster_node_timeout / 1000 * 2);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||
@@ -3448,8 +3454,10 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* The function can be used both to initialize the manual failover state at
|
||||
* startup or to abort a manual failover in progress. */
|
||||
void resetManualFailover(void) {
|
||||
if (server.cluster->mf_end) {
|
||||
checkClientPauseTimeoutAndReturnIfPaused();
|
||||
if (server.cluster->mf_slave) {
|
||||
/* We were a master failing over, so we paused clients. Regardless
|
||||
* of the outcome we unpause now to allow traffic again. */
|
||||
unpauseClients();
|
||||
}
|
||||
server.cluster->mf_end = 0; /* No manual failover in progress. */
|
||||
server.cluster->mf_can_start = 0;
|
||||
@@ -5176,7 +5184,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr,NULL)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -5361,13 +5369,16 @@ void migrateCommand(client *c) {
|
||||
}
|
||||
j++;
|
||||
password = c->argv[j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
|
||||
if (moreargs < 2) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
username = c->argv[++j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
password = c->argv[++j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
|
||||
if (sdslen(c->argv[3]->ptr) != 0) {
|
||||
addReplyError(c,
|
||||
@@ -5465,9 +5476,10 @@ try_again:
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
|
||||
/* Relocate valid (non expired) keys into the array in successive
|
||||
/* Relocate valid (non expired) keys and values into the array in successive
|
||||
* positions to remove holes created by the keys that were present
|
||||
* in the first lookup but are now expired after the second lookup. */
|
||||
ov[non_expired] = ov[j];
|
||||
kv[non_expired++] = kv[j];
|
||||
|
||||
serverAssertWithInfo(c,NULL,
|
||||
@@ -5658,10 +5670,9 @@ socket_err:
|
||||
|
||||
/* Cleanup we want to do if no retry is attempted. */
|
||||
zfree(ov); zfree(kv);
|
||||
addReplySds(c,
|
||||
sdscatprintf(sdsempty(),
|
||||
"-IOERR error or timeout %s to target instance\r\n",
|
||||
write_error ? "writing" : "reading"));
|
||||
addReplyErrorSds(c, sdscatprintf(sdsempty(),
|
||||
"-IOERR error or timeout %s to target instance",
|
||||
write_error ? "writing" : "reading"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5974,7 +5985,8 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
if (c->flags & CLIENT_BLOCKED &&
|
||||
(c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM))
|
||||
c->btype == BLOCKED_STREAM ||
|
||||
c->btype == BLOCKED_MODULE))
|
||||
{
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
@@ -5988,6 +6000,11 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If the client is blocked on module, but ont on a specific key,
|
||||
* don't unblock it (except for the CLSUTER_FAIL case above). */
|
||||
if (c->btype == BLOCKED_MODULE && !moduleClientIsBlockedOnKeys(c))
|
||||
return 0;
|
||||
|
||||
/* All keys must belong to the same slot, so check first key only. */
|
||||
di = dictGetIterator(c->bpop.keys);
|
||||
if ((de = dictNext(di)) != NULL) {
|
||||
|
||||
+2
-2
@@ -388,6 +388,7 @@ void initConfigValues() {
|
||||
}
|
||||
|
||||
void loadServerConfigFromString(char *config) {
|
||||
char buf[1024];
|
||||
const char *err = NULL;
|
||||
int linenum = 0, totlines, i;
|
||||
int slaveof_linenum = 0;
|
||||
@@ -588,7 +589,6 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
|
||||
int argc_err;
|
||||
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
|
||||
char buf[1024];
|
||||
const char *errmsg = ACLSetUserStringError();
|
||||
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
|
||||
argv[argc_err],errmsg);
|
||||
@@ -726,7 +726,7 @@ void configSetCommand(client *c) {
|
||||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
|
||||
{
|
||||
if (config->flags & SENSITIVE_CONFIG) {
|
||||
preventCommandLogging(c);
|
||||
redactClientCommandArgument(c,3);
|
||||
}
|
||||
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
|
||||
goto badfmt;
|
||||
|
||||
+8
-3
@@ -36,10 +36,15 @@
|
||||
|
||||
#ifdef __linux__
|
||||
#include <features.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
|
||||
#define MAC_OS_10_6_DETECTED
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_10_6_DETECTED)
|
||||
#define redis_fstat fstat64
|
||||
#define redis_stat stat64
|
||||
#else
|
||||
@@ -78,7 +83,7 @@
|
||||
#define HAVE_EPOLL 1
|
||||
#endif
|
||||
|
||||
#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
|
||||
#if (defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
|
||||
#define HAVE_KQUEUE 1
|
||||
#endif
|
||||
|
||||
@@ -247,7 +252,7 @@ void setproctitle(const char *fmt, ...);
|
||||
#include <kernel/OS.h>
|
||||
#define redis_set_thread_title(name) rename_thread(find_thread(0), name)
|
||||
#else
|
||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||
#if (defined __APPLE__ && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070)
|
||||
int pthread_setname_np(const char *name);
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(name)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "atomicvar.h"
|
||||
#include "latency.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
@@ -164,7 +165,7 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
}
|
||||
static void SentReplyOnKeyMiss(client *c, robj *reply){
|
||||
void SentReplyOnKeyMiss(client *c, robj *reply){
|
||||
serverAssert(sdsEncodedObject(reply));
|
||||
sds rep = reply->ptr;
|
||||
if (sdslen(rep) > 1 && rep[0] == '-'){
|
||||
@@ -576,7 +577,7 @@ long long dbTotalServerKeyCount() {
|
||||
* a context of a client. */
|
||||
void signalModifiedKey(client *c, redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(c,key);
|
||||
trackingInvalidateKey(c,key,1);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid, int async) {
|
||||
@@ -1437,6 +1438,22 @@ long long getExpire(redisDb *db, robj *key) {
|
||||
return dictGetSignedIntegerVal(de);
|
||||
}
|
||||
|
||||
/* Delete the specified expired key and propagate expire. */
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
|
||||
mstime_t expire_latency;
|
||||
latencyStartMonitor(expire_latency);
|
||||
if (server.lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(expire_latency);
|
||||
latencyAddSampleIfNeeded("expire-del",expire_latency);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
|
||||
server.stat_expiredkeys++;
|
||||
}
|
||||
|
||||
/* Propagate expires into slaves and the AOF file.
|
||||
* When a key expires in the master, a DEL operation for this key is sent
|
||||
* to all the slaves and the AOF file if enabled.
|
||||
@@ -1480,7 +1497,7 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
if (server.lua_caller) {
|
||||
now = server.lua_time_start;
|
||||
now = server.lua_time_snapshot;
|
||||
}
|
||||
/* If we are in the middle of a command execution, we still want to use
|
||||
* a reference time that does not change: in that case we just use the
|
||||
@@ -1541,14 +1558,8 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(NULL,db,key);
|
||||
return retval;
|
||||
deleteExpiredKeyAndPropagate(db,key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -1612,7 +1623,6 @@ int getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, get
|
||||
* return no keys and expect the command implementation to report
|
||||
* an arity or syntax error. */
|
||||
if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
|
||||
getKeysFreeResult(result);
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
+83
-32
@@ -467,6 +467,8 @@ void debugCommand(client *c) {
|
||||
" Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key>",
|
||||
" Show low level info about the ziplist encoding of <key>.",
|
||||
"PAUSE-CRON <0|1>",
|
||||
" Stop periodic cron job processing.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -721,7 +723,7 @@ NULL
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
addReplyBigNum(c,"1234567999999999999999999999999999999",37);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
@@ -737,18 +739,27 @@ NULL
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
if (c->resp >= 3) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
}
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
if (c->resp < 3) {
|
||||
addReplyError(c,"RESP2 is not supported by this command");
|
||||
return;
|
||||
}
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
@@ -885,6 +896,9 @@ NULL
|
||||
mallctl_string(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
#endif
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"pause-cron") && c->argc == 3) {
|
||||
server.pause_cron = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -1017,61 +1031,75 @@ void bugReportStart(void) {
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
static void *getMcontextEip(ucontext_t *uc) {
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
|
||||
/* Returns the current eip and set it to the given new value (if its not NULL) */
|
||||
static void* getAndSetMcontextEip(ucontext_t *uc, void *eip) {
|
||||
#define GET_SET_RETURN(target_var, new_val) do {\
|
||||
void *old_val = (void*)target_var; \
|
||||
if (new_val) { \
|
||||
void **temp = (void**)&target_var; \
|
||||
*temp = new_val; \
|
||||
} \
|
||||
return old_val; \
|
||||
} while(0)
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX < 10.6 */
|
||||
#if defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext->__ss.__rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__rip, eip);
|
||||
#elif defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__eip, eip);
|
||||
#else
|
||||
return (void*) uc->uc_mcontext->__ss.__srr0;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__srr0, eip);
|
||||
#endif
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX >= 10.6 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__rip, eip);
|
||||
#elif defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__eip, eip);
|
||||
#else
|
||||
/* OSX ARM64 */
|
||||
return (void*) arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
|
||||
void *old_val = (void*)arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
|
||||
if (eip) {
|
||||
arm_thread_state64_set_pc_fptr(uc->uc_mcontext->__ss, eip);
|
||||
}
|
||||
return old_val;
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__) || ((defined(__X86_64__) || defined(__x86_64__)) && defined(__ILP32__))
|
||||
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
GET_SET_RETURN(uc->uc_mcontext.gregs[14], eip);
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
GET_SET_RETURN(uc->uc_mcontext.gregs[16], eip);
|
||||
#elif defined(__ia64__) /* Linux IA64 */
|
||||
return (void*) uc->uc_mcontext.sc_ip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.sc_ip, eip);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
return (void*) uc->uc_mcontext.arm_pc;
|
||||
GET_SET_RETURN(uc->uc_mcontext.arm_pc, eip);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
return (void*) uc->uc_mcontext.pc;
|
||||
GET_SET_RETURN(uc->uc_mcontext.pc, eip);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
/* FreeBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.mc_eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_eip, eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_rip, eip);
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
/* OpenBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->sc_eip;
|
||||
GET_SET_RETURN(uc->sc_eip, eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->sc_rip;
|
||||
GET_SET_RETURN(uc->sc_rip, eip);
|
||||
#endif
|
||||
#elif defined(__NetBSD__)
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_EIP];
|
||||
GET_SET_RETURN(uc->uc_mcontext.__gregs[_REG_EIP], eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_RIP];
|
||||
GET_SET_RETURN(uc->uc_mcontext.__gregs[_REG_RIP], eip);
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_rip, eip);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
@@ -1095,7 +1123,7 @@ void logRegisters(ucontext_t *uc) {
|
||||
serverLog(LL_WARNING|LL_RAW, "\n------ REGISTERS ------\n");
|
||||
|
||||
/* OSX */
|
||||
#if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
#if defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX AMD64 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1798,6 +1826,10 @@ void dumpCodeAroundEIP(void *eip) {
|
||||
}
|
||||
}
|
||||
|
||||
void invalidFunctionWasCalled() {}
|
||||
|
||||
typedef void (*invalidFunctionWasCalledType)();
|
||||
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
UNUSED(secret);
|
||||
UNUSED(info);
|
||||
@@ -1809,19 +1841,36 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
serverLog(LL_WARNING,
|
||||
"Accessing address: %p", (void*)info->si_addr);
|
||||
}
|
||||
if (info->si_code <= SI_USER && info->si_pid != -1) {
|
||||
if (info->si_code == SI_USER && info->si_pid != -1) {
|
||||
serverLog(LL_WARNING, "Killed by PID: %ld, UID: %d", (long) info->si_pid, info->si_uid);
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
void *eip = getMcontextEip(uc);
|
||||
void *eip = getAndSetMcontextEip(uc, NULL);
|
||||
if (eip != NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Crashed running the instruction at: %p", eip);
|
||||
}
|
||||
|
||||
logStackTrace(getMcontextEip(uc), 1);
|
||||
if (eip == info->si_addr) {
|
||||
/* When eip matches the bad address, it's an indication that we crashed when calling a non-mapped
|
||||
* function pointer. In that case the call to backtrace will crash trying to access that address and we
|
||||
* won't get a crash report logged. Set it to a valid point to avoid that crash. */
|
||||
|
||||
/* This trick allow to avoid compiler warning */
|
||||
void *ptr;
|
||||
invalidFunctionWasCalledType *ptr_ptr = (invalidFunctionWasCalledType*)&ptr;
|
||||
*ptr_ptr = invalidFunctionWasCalled;
|
||||
getAndSetMcontextEip(uc, ptr);
|
||||
}
|
||||
|
||||
logStackTrace(eip, 1);
|
||||
|
||||
if (eip == info->si_addr) {
|
||||
/* Restore old eip */
|
||||
getAndSetMcontextEip(uc, eip);
|
||||
}
|
||||
|
||||
logRegisters(uc);
|
||||
#endif
|
||||
@@ -1857,7 +1906,9 @@ void bugReportEnd(int killViaSignal, int sig) {
|
||||
"\n=== REDIS 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/redis/redis/issues\n\n"
|
||||
" If a Redis module was involved, please open in the module's repo instead.\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
|
||||
" Some other issues could be detected by redis-server --check-system\n"
|
||||
);
|
||||
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
@@ -1916,7 +1967,7 @@ void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
|
||||
|
||||
serverLogFromHandler(LL_WARNING,"\n--- WATCHDOG TIMER EXPIRED ---");
|
||||
#ifdef HAVE_BACKTRACE
|
||||
logStackTrace(getMcontextEip(uc), 1);
|
||||
logStackTrace(getAndSetMcontextEip(uc, NULL), 1);
|
||||
#else
|
||||
serverLogFromHandler(LL_WARNING,"Sorry: no support for backtrace().");
|
||||
#endif
|
||||
|
||||
+30
-18
@@ -47,15 +47,15 @@
|
||||
#include "zmalloc.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to disable
|
||||
* resizing and rehashing of the hash table as needed. This is very important
|
||||
* for Redis, as we use copy-on-write and don't want to move too much memory
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: a hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
static dictResizeEnable dict_can_resize = DICT_RESIZE_ENABLE;
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
/* -------------------------- private prototypes ---------------------------- */
|
||||
@@ -132,7 +132,7 @@ int dictResize(dict *d)
|
||||
{
|
||||
unsigned long minimal;
|
||||
|
||||
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
|
||||
if (dict_can_resize != DICT_RESIZE_ENABLE || dictIsRehashing(d)) return DICT_ERR;
|
||||
minimal = d->ht[0].used;
|
||||
if (minimal < DICT_HT_INITIAL_SIZE)
|
||||
minimal = DICT_HT_INITIAL_SIZE;
|
||||
@@ -154,6 +154,10 @@ int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
|
||||
dictht n; /* the new hash table */
|
||||
unsigned long realsize = _dictNextPower(size);
|
||||
|
||||
/* Detect overflows */
|
||||
if (realsize < size || realsize * sizeof(dictEntry*) < realsize)
|
||||
return DICT_ERR;
|
||||
|
||||
/* Rehashing to the same table size is not useful. */
|
||||
if (realsize == d->ht[0].size) return DICT_ERR;
|
||||
|
||||
@@ -206,7 +210,15 @@ int dictTryExpand(dict *d, unsigned long size) {
|
||||
* work it does would be unbound and the function may block for a long time. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
int empty_visits = n*10; /* Max number of empty buckets to visit. */
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
unsigned long s0 = d->ht[0].size;
|
||||
unsigned long s1 = d->ht[1].size;
|
||||
if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
|
||||
if (dict_can_resize == DICT_RESIZE_AVOID &&
|
||||
((s1 > s0 && s1 / s0 < dict_force_resize_ratio) ||
|
||||
(s1 < s0 && s0 / s1 < dict_force_resize_ratio)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(n-- && d->ht[0].used != 0) {
|
||||
dictEntry *de, *nextde;
|
||||
@@ -530,8 +542,8 @@ void *dictFetchValue(dict *d, const void *key) {
|
||||
* the fingerprint again when the iterator is released.
|
||||
* If the two fingerprints are different it means that the user of the iterator
|
||||
* performed forbidden operations against the dictionary while iterating. */
|
||||
long long dictFingerprint(dict *d) {
|
||||
long long integers[6], hash = 0;
|
||||
unsigned long long dictFingerprint(dict *d) {
|
||||
unsigned long long integers[6], hash = 0;
|
||||
int j;
|
||||
|
||||
integers[0] = (long) d->ht[0].table;
|
||||
@@ -991,10 +1003,12 @@ static int _dictExpandIfNeeded(dict *d)
|
||||
* table (global setting) or we should avoid it but the ratio between
|
||||
* elements/buckets is over the "safe" threshold, we resize doubling
|
||||
* the number of buckets. */
|
||||
if (d->ht[0].used >= d->ht[0].size &&
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio) &&
|
||||
dictTypeExpandAllowed(d))
|
||||
if (!dictTypeExpandAllowed(d))
|
||||
return DICT_OK;
|
||||
if ((dict_can_resize == DICT_RESIZE_ENABLE &&
|
||||
d->ht[0].used >= d->ht[0].size) ||
|
||||
(dict_can_resize != DICT_RESIZE_FORBID &&
|
||||
d->ht[0].used / d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, d->ht[0].used + 1);
|
||||
}
|
||||
@@ -1053,12 +1067,8 @@ void dictEmpty(dict *d, void(callback)(void*)) {
|
||||
d->pauserehash = 0;
|
||||
}
|
||||
|
||||
void dictEnableResize(void) {
|
||||
dict_can_resize = 1;
|
||||
}
|
||||
|
||||
void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
void dictSetResizeEnabled(dictResizeEnable enable) {
|
||||
dict_can_resize = enable;
|
||||
}
|
||||
|
||||
uint64_t dictGetHash(dict *d, const void *key) {
|
||||
@@ -1101,7 +1111,9 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
||||
|
||||
if (ht->used == 0) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
"Hash table %d stats (%s):\n"
|
||||
"No stats available for empty dictionaries\n",
|
||||
tableid, (tableid == 0) ? "main hash table" : "rehashing target");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
|
||||
+8
-3
@@ -95,7 +95,7 @@ typedef struct dictIterator {
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
long long fingerprint;
|
||||
unsigned long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
@@ -160,6 +160,12 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
||||
#define randomULong() random()
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
DICT_RESIZE_ENABLE,
|
||||
DICT_RESIZE_AVOID,
|
||||
DICT_RESIZE_FORBID,
|
||||
} dictResizeEnable;
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
int dictExpand(dict *d, unsigned long size);
|
||||
@@ -186,8 +192,7 @@ void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
void dictSetResizeEnabled(dictResizeEnable enable);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
|
||||
+9
-3
@@ -549,7 +549,7 @@ int performEvictions(void) {
|
||||
{
|
||||
struct evictionPoolEntry *pool = EvictionPoolLRU;
|
||||
|
||||
while(bestkey == NULL) {
|
||||
while (bestkey == NULL) {
|
||||
unsigned long total_keys = 0, keys;
|
||||
|
||||
/* We don't want to make local-db choices when expiring keys,
|
||||
@@ -695,12 +695,18 @@ cant_free:
|
||||
/* At this point, we have run out of evictable items. It's possible
|
||||
* that some items are being freed in the lazyfree thread. Perform a
|
||||
* short wait here if such jobs exist, but don't wait long. */
|
||||
if (bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
usleep(eviction_time_limit_us);
|
||||
mstime_t lazyfree_latency;
|
||||
latencyStartMonitor(lazyfree_latency);
|
||||
while (bioPendingJobsOfType(BIO_LAZY_FREE) &&
|
||||
elapsedUs(evictionTimer) < eviction_time_limit_us) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
result = EVICT_OK;
|
||||
break;
|
||||
}
|
||||
usleep(eviction_time_limit_us < 1000 ? eviction_time_limit_us : 1000);
|
||||
}
|
||||
latencyEndMonitor(lazyfree_latency);
|
||||
latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
|
||||
}
|
||||
|
||||
latencyEndMonitor(latency);
|
||||
|
||||
+15
-20
@@ -53,24 +53,11 @@
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
mstime_t expire_latency;
|
||||
if (now > t) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
|
||||
latencyStartMonitor(expire_latency);
|
||||
if (server.lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(expire_latency);
|
||||
latencyAddSampleIfNeeded("expire-del",expire_latency);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
deleteExpiredKeyAndPropagate(db,keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -506,15 +493,23 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
|
||||
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
|
||||
return;
|
||||
int negative_when = when < 0;
|
||||
if (unit == UNIT_SECONDS) when *= 1000;
|
||||
when += basetime;
|
||||
if (((when < 0) && !negative_when) || ((when-basetime > 0) && negative_when)) {
|
||||
/* EXPIRE allows negative numbers, but we can at least detect an
|
||||
* overflow by either unit conversion or basetime addition. */
|
||||
|
||||
/* EXPIRE allows negative numbers, but we can at least detect an
|
||||
* overflow by either unit conversion or basetime addition. */
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) {
|
||||
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
|
||||
return;
|
||||
}
|
||||
when *= 1000;
|
||||
}
|
||||
|
||||
if (when > LLONG_MAX - basetime) {
|
||||
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
|
||||
return;
|
||||
}
|
||||
when += basetime;
|
||||
|
||||
/* No key, return zero. */
|
||||
if (lookupKeyWrite(c->db,key) == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
|
||||
@@ -509,7 +509,7 @@ void geoaddCommand(client *c) {
|
||||
* [COUNT count [ANY]] [STORE key] [STOREDIST key]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ...
|
||||
* GEOSEARCH key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC]
|
||||
* [BYBOX width height unit] [WITHCOORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC]
|
||||
* GEOSEARCHSTORE dest_key src_key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC] [STOREDIST]
|
||||
* */
|
||||
@@ -518,21 +518,24 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[srcKeyIndex], shared.emptyarray)) == NULL ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[srcKeyIndex]);
|
||||
if (checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Find long/lat to use for radius or box search based on inquiry type */
|
||||
int base_args;
|
||||
GeoShape shape = {0};
|
||||
if (flags & RADIUS_COORDS) {
|
||||
/* GEORADIUS or GEORADIUS_RO */
|
||||
base_args = 6;
|
||||
shape.type = CIRCULAR_TYPE;
|
||||
if (extractLongLatOrReply(c, c->argv + 2, shape.xy) == C_ERR) return;
|
||||
if (extractDistanceOrReply(c, c->argv+base_args-2, &shape.conversion, &shape.t.radius) != C_OK) return;
|
||||
} else if ((flags & RADIUS_MEMBER) && !zobj) {
|
||||
/* We don't have a source key, but we need to proceed with argument
|
||||
* parsing, so we know which reply to use depending on the STORE flag. */
|
||||
base_args = 5;
|
||||
} else if (flags & RADIUS_MEMBER) {
|
||||
/* GEORADIUSBYMEMBER or GEORADIUSBYMEMBER_RO */
|
||||
base_args = 5;
|
||||
shape.type = CIRCULAR_TYPE;
|
||||
robj *member = c->argv[2];
|
||||
@@ -542,6 +545,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
}
|
||||
if (extractDistanceOrReply(c, c->argv+base_args-2, &shape.conversion, &shape.t.radius) != C_OK) return;
|
||||
} else if (flags & GEOSEARCH) {
|
||||
/* GEOSEARCH or GEOSEARCHSTORE */
|
||||
base_args = 2;
|
||||
if (flags & GEOSEARCHSTORE) {
|
||||
base_args = 3;
|
||||
@@ -608,6 +612,13 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
flags & GEOSEARCH &&
|
||||
!fromloc)
|
||||
{
|
||||
/* No source key, proceed with argument parsing and return an error when done. */
|
||||
if (zobj == NULL) {
|
||||
frommember = 1;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (longLatFromMember(zobj, c->argv[base_args+i+1], shape.xy) == C_ERR) {
|
||||
addReplyError(c, "could not decode requested zset member");
|
||||
return;
|
||||
@@ -676,6 +687,23 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return ASAP when src key does not exist. */
|
||||
if (zobj == NULL) {
|
||||
if (storekey) {
|
||||
/* store key is not NULL, try to delete it and return 0. */
|
||||
if (dbDelete(c->db, storekey)) {
|
||||
signalModifiedKey(c, c->db, storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", storekey, c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c, shared.czero);
|
||||
} else {
|
||||
/* Otherwise we return an empty array. */
|
||||
addReply(c, shared.emptyarray);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* COUNT without ordering does not make much sense (we need to
|
||||
* sort in order to return the closest N entries),
|
||||
* force ASC ordering if COUNT was specified but no sorting was
|
||||
@@ -770,7 +798,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
robj *zobj;
|
||||
zset *zs;
|
||||
int i;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
|
||||
if (returned_items) {
|
||||
zobj = createZsetObject();
|
||||
@@ -785,13 +813,14 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
size_t elelen = sdslen(gp->member);
|
||||
|
||||
if (maxelelen < elelen) maxelelen = elelen;
|
||||
totelelen += elelen;
|
||||
znode = zslInsert(zs->zsl,score,gp->member);
|
||||
serverAssert(dictAdd(zs->dict,gp->member,&znode->score) == DICT_OK);
|
||||
gp->member = NULL;
|
||||
}
|
||||
|
||||
if (returned_items) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen,totelelen);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,flags & GEOSEARCH ? "geosearchstore" : "georadiusstore",storekey,
|
||||
|
||||
+10
-10
@@ -91,8 +91,8 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
* \-----------------/ -------- \-----------------/
|
||||
* \ / / \ \ /
|
||||
* \ (long,lat) / / (long,lat) \ \ (long,lat) /
|
||||
* \ / / \ / \
|
||||
* --------- /----------------\ /--------------\
|
||||
* \ / / \ / \
|
||||
* --------- /----------------\ /---------------\
|
||||
* Northern Hemisphere Southern Hemisphere Around the equator
|
||||
*/
|
||||
int geohashBoundingBox(GeoShape *shape, double *bounds) {
|
||||
@@ -164,14 +164,14 @@ GeoHashRadius geohashCalculateAreasByShapeWGS84(GeoShape *shape) {
|
||||
geohashDecode(long_range, lat_range, neighbors.east, &east);
|
||||
geohashDecode(long_range, lat_range, neighbors.west, &west);
|
||||
|
||||
if (geohashGetDistance(longitude,latitude,longitude,north.latitude.max)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,longitude,south.latitude.min)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,east.longitude.max,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,west.longitude.min,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (north.latitude.max < max_lat)
|
||||
decrease_step = 1;
|
||||
if (south.latitude.min > min_lat)
|
||||
decrease_step = 1;
|
||||
if (east.longitude.max < max_lon)
|
||||
decrease_step = 1;
|
||||
if (west.longitude.min > min_lon)
|
||||
decrease_step = 1;
|
||||
}
|
||||
|
||||
if (steps > 1 && decrease_step) {
|
||||
|
||||
+42
-5
@@ -586,6 +586,7 @@ int hllSparseToDense(robj *o) {
|
||||
struct hllhdr *hdr, *oldhdr = (struct hllhdr*)sparse;
|
||||
int idx = 0, runlen, regval;
|
||||
uint8_t *p = (uint8_t*)sparse, *end = p+sdslen(sparse);
|
||||
int valid = 1;
|
||||
|
||||
/* If the representation is already the right one return ASAP. */
|
||||
hdr = (struct hllhdr*) sparse;
|
||||
@@ -605,16 +606,27 @@ int hllSparseToDense(robj *o) {
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@@ -625,7 +637,7 @@ int hllSparseToDense(robj *o) {
|
||||
|
||||
/* If the sparse representation was valid, we expect to find idx
|
||||
* set to HLL_REGISTERS. */
|
||||
if (idx != HLL_REGISTERS) {
|
||||
if (!valid || idx != HLL_REGISTERS) {
|
||||
sdsfree(dense);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -911,27 +923,40 @@ int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
void hllSparseRegHisto(uint8_t *sparse, int sparselen, int *invalid, int* reghisto) {
|
||||
int idx = 0, runlen, regval;
|
||||
uint8_t *end = sparse+sparselen, *p = sparse;
|
||||
int valid = 1;
|
||||
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[0] += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[0] += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[regval] += runlen;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (idx != HLL_REGISTERS && invalid) *invalid = 1;
|
||||
if ((!valid || idx != HLL_REGISTERS) && invalid) *invalid = 1;
|
||||
}
|
||||
|
||||
/* ========================= HyperLogLog Count ==============================
|
||||
@@ -1079,22 +1104,34 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
uint8_t *p = hll->ptr, *end = p + sdslen(hll->ptr);
|
||||
long runlen, regval;
|
||||
int valid = 1;
|
||||
|
||||
p += HLL_HDR_SIZE;
|
||||
i = 0;
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
i += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
i += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
@@ -1102,7 +1139,7 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (i != HLL_REGISTERS) return C_ERR;
|
||||
if (!valid || i != HLL_REGISTERS) return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
+3
-2
@@ -104,7 +104,8 @@ intset *intsetNew(void) {
|
||||
|
||||
/* Resize the intset */
|
||||
static intset *intsetResize(intset *is, uint32_t len) {
|
||||
uint32_t size = len*intrev32ifbe(is->encoding);
|
||||
uint64_t size = (uint64_t)len*intrev32ifbe(is->encoding);
|
||||
assert(size <= SIZE_MAX - sizeof(intset));
|
||||
is = zrealloc(is,sizeof(intset)+size);
|
||||
return is;
|
||||
}
|
||||
@@ -281,7 +282,7 @@ uint32_t intsetLen(const intset *is) {
|
||||
|
||||
/* Return intset blob size in bytes. */
|
||||
size_t intsetBlobLen(intset *is) {
|
||||
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
return sizeof(intset)+(size_t)intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
}
|
||||
|
||||
/* Validate the integrity of the data structure.
|
||||
|
||||
+3
-4
@@ -39,12 +39,11 @@ void lazyfreeFreeSlotsMap(void *args[]) {
|
||||
atomicIncr(lazyfreed_objects,len);
|
||||
}
|
||||
|
||||
/* Release the rax mapping Redis Cluster keys to slots in the
|
||||
* lazyfree thread. */
|
||||
/* Release the key tracking table. */
|
||||
void lazyFreeTrackingTable(void *args[]) {
|
||||
rax *rt = args[0];
|
||||
size_t len = rt->numele;
|
||||
raxFree(rt);
|
||||
freeTrackingRadixTree(rt);
|
||||
atomicDecr(lazyfree_objects,len);
|
||||
atomicIncr(lazyfreed_objects,len);
|
||||
}
|
||||
@@ -110,7 +109,7 @@ size_t lazyfreeGetFreeEffort(robj *key, robj *obj) {
|
||||
/* Every consumer group is an allocation and so are the entries in its
|
||||
* PEL. We use size of the first group's PEL as an estimate for all
|
||||
* others. */
|
||||
if (s->cgroups) {
|
||||
if (s->cgroups && raxSize(s->cgroups)) {
|
||||
raxIterator ri;
|
||||
streamCG *cg;
|
||||
raxStart(&ri,s->cgroups);
|
||||
|
||||
+31
-8
@@ -131,6 +131,8 @@
|
||||
assert((p) >= (lp)+LP_HDR_SIZE && (p)+(len) < (lp)+lpGetTotalBytes((lp))); \
|
||||
} while (0)
|
||||
|
||||
static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p);
|
||||
|
||||
/* Convert a string into a signed 64 bit integer.
|
||||
* The function returns 1 if the string could be parsed into a (non-overflowing)
|
||||
* signed 64 bit int, 0 otherwise. The 'value' will be set to the parsed value
|
||||
@@ -313,7 +315,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
} else {
|
||||
if (size < 64) *enclen = 1+size;
|
||||
else if (size < 4096) *enclen = 2+size;
|
||||
else *enclen = 5+size;
|
||||
else *enclen = 5+(uint64_t)size;
|
||||
return LP_ENCODING_STRING;
|
||||
}
|
||||
}
|
||||
@@ -453,8 +455,8 @@ unsigned char *lpSkip(unsigned char *p) {
|
||||
unsigned char *lpNext(unsigned char *lp, unsigned char *p) {
|
||||
assert(p);
|
||||
p = lpSkip(p);
|
||||
ASSERT_INTEGRITY(lp, p);
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -468,16 +470,17 @@ unsigned char *lpPrev(unsigned char *lp, unsigned char *p) {
|
||||
uint64_t prevlen = lpDecodeBacklen(p);
|
||||
prevlen += lpEncodeBacklen(NULL,prevlen);
|
||||
p -= prevlen-1; /* Seek the first byte of the previous entry. */
|
||||
ASSERT_INTEGRITY(lp, p);
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Return a pointer to the first element of the listpack, or NULL if the
|
||||
* listpack has no elements. */
|
||||
unsigned char *lpFirst(unsigned char *lp) {
|
||||
lp += LP_HDR_SIZE; /* Skip the header. */
|
||||
if (lp[0] == LP_EOF) return NULL;
|
||||
return lp;
|
||||
unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Return a pointer to the last element of the listpack, or NULL if the
|
||||
@@ -861,6 +864,13 @@ unsigned char *lpSeek(unsigned char *lp, long index) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Same as lpFirst but without validation assert, to be used right before lpValidateNext. */
|
||||
unsigned char *lpValidateFirst(unsigned char *lp) {
|
||||
unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Validate the integrity of a single listpack entry and move to the next one.
|
||||
* The input argument 'pp' is a reference to the current record and is advanced on exit.
|
||||
* Returns 1 if valid, 0 if invalid. */
|
||||
@@ -872,6 +882,10 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
/* Before accessing p, make sure it's valid. */
|
||||
if (OUT_OF_RANGE(p))
|
||||
return 0;
|
||||
|
||||
if (*p == LP_EOF) {
|
||||
*pp = NULL;
|
||||
return 1;
|
||||
@@ -908,6 +922,11 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
|
||||
#undef OUT_OF_RANGE
|
||||
}
|
||||
|
||||
/* Validate that the entry doesn't reach outside the listpack allocation. */
|
||||
static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p) {
|
||||
assert(lpValidateNext(lp, &p, lpbytes));
|
||||
}
|
||||
|
||||
/* Validate the integrity of the data structure.
|
||||
* when `deep` is 0, only the integrity of the header is validated.
|
||||
* when `deep` is 1, we scan all the entries one by one. */
|
||||
@@ -930,13 +949,17 @@ int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){
|
||||
|
||||
/* Validate the invividual entries. */
|
||||
uint32_t count = 0;
|
||||
unsigned char *p = lpFirst(lp);
|
||||
while(p) {
|
||||
unsigned char *p = lp + LP_HDR_SIZE;
|
||||
while(p && p[0] != LP_EOF) {
|
||||
if (!lpValidateNext(lp, &p, bytes))
|
||||
return 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Make sure 'p' really does point to the end of the listpack. */
|
||||
if (p != lp + size - 1)
|
||||
return 0;
|
||||
|
||||
/* Check that the count in the header is correct */
|
||||
uint32_t numele = lpGetNumElements(lp);
|
||||
if (numele != LP_HDR_NUMELE_UNKNOWN && numele != count)
|
||||
|
||||
@@ -60,6 +60,7 @@ unsigned char *lpPrev(unsigned char *lp, unsigned char *p);
|
||||
uint32_t lpBytes(unsigned char *lp);
|
||||
unsigned char *lpSeek(unsigned char *lp, long index);
|
||||
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep);
|
||||
unsigned char *lpValidateFirst(unsigned char *lp);
|
||||
int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes);
|
||||
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@ lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,bgcolor,width*height);
|
||||
canvas->pixels = zmalloc((size_t)width*height);
|
||||
memset(canvas->pixels,bgcolor,(size_t)width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ void memtest_progress_start(char *title, int pass) {
|
||||
printf("\x1b[H\x1b[2K"); /* Cursor home, clear current line. */
|
||||
printf("%s [%d]\n", title, pass); /* Print title. */
|
||||
progress_printed = 0;
|
||||
progress_full = ws.ws_col*(ws.ws_row-3);
|
||||
progress_full = (size_t)ws.ws_col*(ws.ws_row-3);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
+68
-13
@@ -269,6 +269,7 @@ typedef struct RedisModuleCallReply {
|
||||
long long ll; /* Reply value for integer reply. */
|
||||
struct RedisModuleCallReply *array; /* Array of sub-reply elements. */
|
||||
} val;
|
||||
list *deferred_error_list; /* list of errors in sds form or NULL */
|
||||
} RedisModuleCallReply;
|
||||
|
||||
/* Structure representing a blocked client. We get a pointer to such
|
||||
@@ -1702,6 +1703,15 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
sds proto = sdsnewlen(reply->proto, reply->protolen);
|
||||
addReplySds(c,proto);
|
||||
|
||||
/* Propagate the error list from that reply to the other client, to do some
|
||||
* post error reply handling, like statistics.
|
||||
* Note that if the original reply had an array with errors, and the module
|
||||
* replied with just a portion of the original reply, and not the entire
|
||||
* reply, the errors are currently not propagated and the errors stats
|
||||
* will not get propagated. */
|
||||
if (reply->deferred_error_list)
|
||||
deferredAfterErrorReply(c, reply->deferred_error_list);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -2274,7 +2284,10 @@ static void moduleCloseKey(RedisModuleKey *key) {
|
||||
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
|
||||
if ((key->mode & REDISMODULE_WRITE) && signal)
|
||||
signalModifiedKey(key->ctx->client,key->db,key->key);
|
||||
if (key->iter) zfree(key->iter);
|
||||
if (key->iter) {
|
||||
streamIteratorStop(key->iter);
|
||||
zfree(key->iter);
|
||||
}
|
||||
RM_ZsetRangeStop(key);
|
||||
if (key && key->value && key->value->type == OBJ_STREAM &&
|
||||
key->u.stream.signalready) {
|
||||
@@ -2542,10 +2555,10 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
if (newlen > curlen) {
|
||||
key->value->ptr = sdsgrowzero(key->value->ptr,newlen);
|
||||
} else if (newlen < curlen) {
|
||||
sdsrange(key->value->ptr,0,newlen-1);
|
||||
sdssubstr(key->value->ptr,0,newlen);
|
||||
/* If the string is too wasteful, reallocate it. */
|
||||
if (sdslen(key->value->ptr) < sdsavail(key->value->ptr))
|
||||
key->value->ptr = sdsRemoveFreeSpace(key->value->ptr);
|
||||
key->value->ptr = sdsRemoveFreeSpace(key->value->ptr, 0);
|
||||
}
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
@@ -3319,6 +3332,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
|
||||
* - EDOM if the given ID was 0-0 or not greater than all other IDs in the
|
||||
* stream (only if the AUTOID flag is unset)
|
||||
* - EFBIG if the stream has reached the last possible ID
|
||||
* - ERANGE if the elements are too large to be stored.
|
||||
*/
|
||||
int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisModuleString **argv, long numfields) {
|
||||
/* Validate args */
|
||||
@@ -3362,8 +3376,9 @@ int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisM
|
||||
use_id_ptr = &use_id;
|
||||
}
|
||||
if (streamAppendItem(s, argv, numfields, &added_id, use_id_ptr) == C_ERR) {
|
||||
/* ID not greater than all existing IDs in the stream */
|
||||
errno = EDOM;
|
||||
/* Either the ID not greater than all existing IDs in the stream, or
|
||||
* the elements are too large to be stored. either way, errno is already
|
||||
* set by streamAppendItem. */
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
/* Postponed signalKeyAsReady(). Done implicitly by moduleCreateEmptyKey()
|
||||
@@ -3529,6 +3544,7 @@ int RM_StreamIteratorStop(RedisModuleKey *key) {
|
||||
errno = EBADF;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
streamIteratorStop(key->iter);
|
||||
zfree(key->iter);
|
||||
key->iter = NULL;
|
||||
return REDISMODULE_OK;
|
||||
@@ -3751,12 +3767,16 @@ long long RM_StreamTrimByID(RedisModuleKey *key, int flags, RedisModuleStreamID
|
||||
/* Create a new RedisModuleCallReply object. The processing of the reply
|
||||
* is lazy, the object is just populated with the raw protocol and later
|
||||
* is processed as needed. Initially we just make sure to set the right
|
||||
* reply type, which is extremely cheap to do. */
|
||||
RedisModuleCallReply *moduleCreateCallReplyFromProto(RedisModuleCtx *ctx, sds proto) {
|
||||
* reply type, which is extremely cheap to do.
|
||||
* The deferred_error_list is an optional list of errors that are present
|
||||
* in the reply blob, if given, this function will take ownership on it.
|
||||
*/
|
||||
RedisModuleCallReply *moduleCreateCallReplyFromProto(RedisModuleCtx *ctx, sds proto, list *deferred_error_list) {
|
||||
RedisModuleCallReply *reply = zmalloc(sizeof(*reply));
|
||||
reply->ctx = ctx;
|
||||
reply->proto = proto;
|
||||
reply->protolen = sdslen(proto);
|
||||
reply->deferred_error_list = deferred_error_list;
|
||||
reply->flags = REDISMODULE_REPLYFLAG_TOPARSE; /* Lazy parsing. */
|
||||
switch(proto[0]) {
|
||||
case '$':
|
||||
@@ -3874,11 +3894,14 @@ void moduleFreeCallReplyRec(RedisModuleCallReply *reply, int freenested){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* For nested replies, we don't free reply->proto (which if not NULL
|
||||
* references the parent reply->proto buffer), nor the structure
|
||||
* itself which is allocated as an array of structures, and is freed
|
||||
* when the array value is released. */
|
||||
if (!(reply->flags & REDISMODULE_REPLYFLAG_NESTED)) {
|
||||
if (reply->deferred_error_list)
|
||||
listRelease(reply->deferred_error_list);
|
||||
if (reply->proto) sdsfree(reply->proto);
|
||||
zfree(reply);
|
||||
}
|
||||
@@ -4200,7 +4223,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
proto = sdscatlen(proto,o->buf,o->used);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto);
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto,c->deferred_reply_errors);
|
||||
c->deferred_reply_errors = NULL; /* now the responsibility of the reply object. */
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
|
||||
|
||||
cleanup:
|
||||
@@ -5362,8 +5386,8 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
* reply_callback: called after a successful RedisModule_UnblockClient()
|
||||
* call in order to reply to the client and unblock it.
|
||||
*
|
||||
* timeout_callback: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
* timeout_callback: called when the timeout is reached or if `CLIENT UNBLOCK`
|
||||
* is invoked, in order to send an error to the client.
|
||||
*
|
||||
* free_privdata: called in order to free the private data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
@@ -5380,6 +5404,12 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
* In these cases, a call to RedisModule_BlockClient() will **not** block the
|
||||
* client, but instead produce a specific error reply.
|
||||
*
|
||||
* A module that registers a timeout_callback function can also be unblocked
|
||||
* using the `CLIENT UNBLOCK` command, which will trigger the timeout callback.
|
||||
* If a callback function is not registered, then the blocked client will be
|
||||
* treated as if it is not in a blocked state and `CLIENT UNBLOCK` will return
|
||||
* a zero value.
|
||||
*
|
||||
* Measuring background time: By default the time spent in the blocked command
|
||||
* is not account for the total command duration. To include such time you should
|
||||
* use RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() one,
|
||||
@@ -5649,6 +5679,17 @@ void moduleHandleBlockedClients(void) {
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
}
|
||||
|
||||
/* Check if the specified client can be safely timed out using
|
||||
* moduleBlockedClientTimedOut().
|
||||
*/
|
||||
int moduleBlockedClientMayTimeout(client *c) {
|
||||
if (c->btype != BLOCKED_MODULE)
|
||||
return 1;
|
||||
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
return (bc && bc->timeout_callback != NULL);
|
||||
}
|
||||
|
||||
/* Called when our client timed out. After this function unblockClient()
|
||||
* is called, and it will invalidate the blocked client. So this function
|
||||
* does not need to do any cleanup. Eventually the module will call the
|
||||
@@ -6168,7 +6209,7 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
/* If the information is not available, the function will set the
|
||||
* field to zero bytes, so that when the field can't be populated the
|
||||
* function kinda remains predictable. */
|
||||
if (node->flags & CLUSTER_NODE_MASTER && node->slaveof)
|
||||
if (node->flags & CLUSTER_NODE_SLAVE && node->slaveof)
|
||||
memcpy(master_id,node->slaveof->name,REDISMODULE_NODE_ID_LEN);
|
||||
else
|
||||
memset(master_id,0,REDISMODULE_NODE_ID_LEN);
|
||||
@@ -8280,7 +8321,8 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) {
|
||||
* up to the specific event setup to change it when it makes
|
||||
* sense. For instance for FLUSHDB events we select the correct
|
||||
* DB automatically. */
|
||||
selectDb(ctx.client, 0);
|
||||
if (!real_client_used)
|
||||
selectDb(ctx.client, 0);
|
||||
|
||||
/* Event specific context and data pointer setup. */
|
||||
if (eid == REDISMODULE_EVENT_CLIENT_CHANGE) {
|
||||
@@ -8679,8 +8721,9 @@ sds genModulesInfoStringRenderModulesList(list *l) {
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModule *module = ln->value;
|
||||
output = sdscat(output,module->name);
|
||||
if (ln != listLast(l))
|
||||
output = sdscat(output,"|");
|
||||
}
|
||||
output = sdstrim(output,"|");
|
||||
output = sdscat(output,"]");
|
||||
return output;
|
||||
}
|
||||
@@ -9006,6 +9049,14 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Return the name of the command currently running */
|
||||
const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) {
|
||||
if (!ctx || !ctx->client || !ctx->client->cmd)
|
||||
return NULL;
|
||||
|
||||
return (const char*)ctx->client->cmd->name;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* ## Defrag API
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -9205,6 +9256,7 @@ long moduleDefragGlobals(void) {
|
||||
module->defrag_cb(&defrag_ctx);
|
||||
defragged += defrag_ctx.defragged;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
return defragged;
|
||||
}
|
||||
@@ -9279,6 +9331,8 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(StringTruncate);
|
||||
REGISTER_API(SetExpire);
|
||||
REGISTER_API(GetExpire);
|
||||
REGISTER_API(SetAbsExpire);
|
||||
REGISTER_API(GetAbsExpire);
|
||||
REGISTER_API(ResetDataset);
|
||||
REGISTER_API(DbSize);
|
||||
REGISTER_API(RandomKey);
|
||||
@@ -9466,6 +9520,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(GetServerVersion);
|
||||
REGISTER_API(GetClientCertificate);
|
||||
REGISTER_API(GetCommandKeys);
|
||||
REGISTER_API(GetCurrentCommandName);
|
||||
REGISTER_API(GetTypeMethodVersion);
|
||||
REGISTER_API(RegisterDefragFunc);
|
||||
REGISTER_API(DefragAlloc);
|
||||
|
||||
@@ -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 hellohook.so helloacl.so
|
||||
all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -58,10 +58,5 @@ helloacl.xo: ../redismodule.h
|
||||
helloacl.so: helloacl.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
clean:
|
||||
rm -rf *.xo *.so
|
||||
|
||||
@@ -168,3 +168,13 @@ const char * monotonicInit() {
|
||||
|
||||
return monotonic_info_string;
|
||||
}
|
||||
|
||||
const char *monotonicInfoString() {
|
||||
return monotonic_info_string;
|
||||
}
|
||||
|
||||
monotonic_clock_type monotonicGetType() {
|
||||
if (getMonotonicUs == getMonotonicUs_posix)
|
||||
return MONOTONIC_CLOCK_POSIX;
|
||||
return MONOTONIC_CLOCK_HW;
|
||||
}
|
||||
|
||||
+10
-1
@@ -24,13 +24,22 @@ typedef uint64_t monotime;
|
||||
/* Retrieve counter of micro-seconds relative to an arbitrary point in time. */
|
||||
extern monotime (*getMonotonicUs)(void);
|
||||
|
||||
typedef enum monotonic_clock_type {
|
||||
MONOTONIC_CLOCK_POSIX,
|
||||
MONOTONIC_CLOCK_HW,
|
||||
} monotonic_clock_type;
|
||||
|
||||
/* Call once at startup to initialize the monotonic clock. Though this only
|
||||
* needs to be called once, it may be called additional times without impact.
|
||||
* Returns a printable string indicating the type of clock initialized.
|
||||
* (The returned string is static and doesn't need to be freed.) */
|
||||
const char * monotonicInit();
|
||||
const char *monotonicInit();
|
||||
|
||||
/* Return a string indicating the type of monotonic clock being used. */
|
||||
const char *monotonicInfoString();
|
||||
|
||||
/* Return the type of monotonic clock being used. */
|
||||
monotonic_clock_type monotonicGetType();
|
||||
|
||||
/* Functions to measure elapsed time. Example:
|
||||
* monotime myTimer;
|
||||
|
||||
+30
-15
@@ -153,8 +153,7 @@ void execCommandAbort(client *c, sds error) {
|
||||
/* Send EXEC to clients waiting data from MONITOR. We did send a MULTI
|
||||
* already, and didn't send any of the queued commands, now we'll just send
|
||||
* EXEC so it is clear that the transaction is over. */
|
||||
if (listLength(server.monitors) && !server.loading)
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
void execCommand(client *c) {
|
||||
@@ -169,17 +168,26 @@ void execCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXEC with expired watched key is disallowed*/
|
||||
if (isWatchedKeyExpired(c)) {
|
||||
c->flags |= (CLIENT_DIRTY_CAS);
|
||||
}
|
||||
|
||||
/* Check if we need to abort the EXEC because:
|
||||
* 1) Some WATCHed key was touched.
|
||||
* 2) There was a previous error while queueing commands.
|
||||
* A failed EXEC in the first case returns a multi bulk nil object
|
||||
* (technically it is not an error but a special behavior), while
|
||||
* in the second an EXECABORT error is returned. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullarray[c->resp]);
|
||||
if (c->flags & (CLIENT_DIRTY_CAS | CLIENT_DIRTY_EXEC)) {
|
||||
if (c->flags & CLIENT_DIRTY_EXEC) {
|
||||
addReplyErrorObject(c, shared.execaborterr);
|
||||
} else {
|
||||
addReply(c, shared.nullarray[c->resp]);
|
||||
}
|
||||
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t old_flags = c->flags;
|
||||
@@ -266,15 +274,6 @@ void execCommand(client *c) {
|
||||
}
|
||||
|
||||
server.in_exec = 0;
|
||||
|
||||
handle_monitor:
|
||||
/* Send EXEC to clients waiting data from MONITOR. We do it here
|
||||
* since the natural order of commands execution is actually:
|
||||
* 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);
|
||||
}
|
||||
|
||||
/* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
|
||||
@@ -352,6 +351,22 @@ void unwatchAllKeys(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* iterates over the watched_keys list and
|
||||
* look for an expired key . */
|
||||
int isWatchedKeyExpired(client *c) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
watchedKey *wk;
|
||||
if (listLength(c->watched_keys) == 0) return 0;
|
||||
listRewind(c->watched_keys,&li);
|
||||
while ((ln = listNext(&li))) {
|
||||
wk = listNodeValue(ln);
|
||||
if (keyIsExpired(wk->db, wk->key)) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* "Touch" a key, so that if this key is being WATCHed by some client the
|
||||
* next EXEC will fail. */
|
||||
void touchWatchedKey(redisDb *db, robj *key) {
|
||||
|
||||
+168
-38
@@ -107,6 +107,15 @@ static void clientSetDefaultAuth(client *c) {
|
||||
!(c->user->flags & USER_FLAG_DISABLED);
|
||||
}
|
||||
|
||||
int authRequired(client *c) {
|
||||
/* Check if the user is authenticated. This check is skipped in case
|
||||
* the default user is flagged as "nopass" and is active. */
|
||||
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
(DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
!c->authenticated;
|
||||
return auth_required;
|
||||
}
|
||||
|
||||
client *createClient(connection *conn) {
|
||||
client *c = zmalloc(sizeof(client));
|
||||
|
||||
@@ -159,6 +168,7 @@ client *createClient(connection *conn) {
|
||||
c->slave_addr = NULL;
|
||||
c->slave_capa = SLAVE_CAPA_NONE;
|
||||
c->reply = listCreate();
|
||||
c->deferred_reply_errors = NULL;
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
@@ -250,8 +260,10 @@ int prepareClientToWrite(client *c) {
|
||||
/* If CLIENT_CLOSE_ASAP flag is set, we need not write anything. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) return C_ERR;
|
||||
|
||||
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
|
||||
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
|
||||
/* CLIENT REPLY OFF / SKIP handling: don't send replies.
|
||||
* CLIENT_PUSHING handling: disables the reply silencing flags. */
|
||||
if ((c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) &&
|
||||
!(c->flags & CLIENT_PUSHING)) return C_ERR;
|
||||
|
||||
/* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag
|
||||
* is set. */
|
||||
@@ -333,7 +345,7 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
listAddNodeTail(c->reply, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +420,18 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
|
||||
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
|
||||
void afterErrorReply(client *c, const char *s, size_t len) {
|
||||
/* Module clients fall into two categories:
|
||||
* Calls to RM_Call, in which case the error isn't being returned to a client, so should not be counted.
|
||||
* Module thread safe context calls to RM_ReplyWithError, which will be added to a real client by the main thread later. */
|
||||
if (c->flags & CLIENT_MODULE) {
|
||||
if (!c->deferred_reply_errors) {
|
||||
c->deferred_reply_errors = listCreate();
|
||||
listSetFreeMethod(c->deferred_reply_errors, (void (*)(void*))sdsfree);
|
||||
}
|
||||
listAddNodeTail(c->deferred_reply_errors, sdsnewlen(s, len));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Increment the global error counter */
|
||||
server.stat_total_error_replies++;
|
||||
/* Increment the error stats
|
||||
@@ -616,7 +640,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +652,24 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
||||
* we return NULL in addReplyDeferredLen() */
|
||||
if (node == NULL) return;
|
||||
|
||||
/* Things like *2\r\n, %3\r\n or ~4\r\n are emitted very often by the protocol
|
||||
* so we have a few shared objects to use if the integer is small
|
||||
* like it is most of the times. */
|
||||
const size_t hdr_len = OBJ_SHARED_HDR_STRLEN(length);
|
||||
const int opt_hdr = length < OBJ_SHARED_BULKHDR_LEN;
|
||||
if (prefix == '*' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.mbulkhdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
if (prefix == '%' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.maphdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
if (prefix == '~' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.sethdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
|
||||
char lenstr[128];
|
||||
size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
|
||||
setDeferredReply(c, node, lenstr, lenstr_len);
|
||||
@@ -649,14 +691,13 @@ void setDeferredSetLen(client *c, void *node, long length) {
|
||||
}
|
||||
|
||||
void setDeferredAttributeLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
setDeferredAggregateLen(c,node,length,'|');
|
||||
}
|
||||
|
||||
void setDeferredPushLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
setDeferredAggregateLen(c,node,length,'>');
|
||||
}
|
||||
|
||||
/* Add a double as a bulk reply */
|
||||
@@ -685,6 +726,16 @@ void addReplyDouble(client *c, double d) {
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyBigNum(client *c, const char* num, size_t len) {
|
||||
if (c->resp == 2) {
|
||||
addReplyBulkCBuffer(c, num, len);
|
||||
} else {
|
||||
addReplyProto(c,"(",1);
|
||||
addReplyProto(c,num,len);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a long double as a bulk reply, but uses a human readable formatting
|
||||
* of the double instead of exposing the crude behavior of doubles to the
|
||||
* dear user. */
|
||||
@@ -711,11 +762,19 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
|
||||
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol
|
||||
* so we have a few shared objects to use if the integer is small
|
||||
* like it is most of the times. */
|
||||
if (prefix == '*' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.mbulkhdr[ll]);
|
||||
const int opt_hdr = ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0;
|
||||
const size_t hdr_len = OBJ_SHARED_HDR_STRLEN(ll);
|
||||
if (prefix == '*' && opt_hdr) {
|
||||
addReplyProto(c,shared.mbulkhdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '$' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.bulkhdr[ll]);
|
||||
} else if (prefix == '$' && opt_hdr) {
|
||||
addReplyProto(c,shared.bulkhdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '%' && opt_hdr) {
|
||||
addReplyProto(c,shared.maphdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '~' && opt_hdr) {
|
||||
addReplyProto(c,shared.sethdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -756,14 +815,14 @@ void addReplySetLen(client *c, long length) {
|
||||
}
|
||||
|
||||
void addReplyAttributeLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
addReplyAggregateLen(c,length,'|');
|
||||
}
|
||||
|
||||
void addReplyPushLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
serverAssertWithInfo(c, NULL, c->flags & CLIENT_PUSHING);
|
||||
addReplyAggregateLen(c,length,'>');
|
||||
}
|
||||
|
||||
void addReplyNull(client *c) {
|
||||
@@ -948,8 +1007,14 @@ void AddReplyFromClient(client *dst, client *src) {
|
||||
src->reply_bytes = 0;
|
||||
src->bufpos = 0;
|
||||
|
||||
if (src->deferred_reply_errors) {
|
||||
deferredAfterErrorReply(dst, src->deferred_reply_errors);
|
||||
listRelease(src->deferred_reply_errors);
|
||||
src->deferred_reply_errors = NULL;
|
||||
}
|
||||
|
||||
/* Check output buffer limits */
|
||||
asyncCloseClientOnOutputBufferLimitReached(dst);
|
||||
closeClientOnOutputBufferLimitReached(dst, 1);
|
||||
}
|
||||
|
||||
/* Copy 'src' client output buffers into 'dst' client output buffers.
|
||||
@@ -964,6 +1029,18 @@ void copyClientOutputBuffer(client *dst, client *src) {
|
||||
dst->reply_bytes = src->reply_bytes;
|
||||
}
|
||||
|
||||
/* Append the listed errors to the server error statistics. the input
|
||||
* list is not modified and remains the responsibility of the caller. */
|
||||
void deferredAfterErrorReply(client *c, list *errors) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(errors,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds err = ln->value;
|
||||
afterErrorReply(c, err, sdslen(err));
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if the specified client has pending reply buffers to write to
|
||||
* the socket. */
|
||||
int clientHasPendingReplies(client *c) {
|
||||
@@ -1113,6 +1190,8 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
@@ -1134,6 +1213,8 @@ void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
@@ -1154,6 +1235,8 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
@@ -1358,6 +1441,8 @@ void freeClient(client *c) {
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
freeClientOriginalArgv(c);
|
||||
if (c->deferred_reply_errors)
|
||||
listRelease(c->deferred_reply_errors);
|
||||
|
||||
/* Unlink the client: this will close the socket, remove the I/O
|
||||
* handlers, and remove references of the client from different
|
||||
@@ -1644,6 +1729,10 @@ void resetClient(client *c) {
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
|
||||
if (c->deferred_reply_errors)
|
||||
listRelease(c->deferred_reply_errors);
|
||||
c->deferred_reply_errors = NULL;
|
||||
|
||||
/* We clear the ASKING flag as well if we are not inside a MULTI, and
|
||||
* if what we just executed is not the ASKING command itself. */
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
|
||||
@@ -1663,9 +1752,6 @@ void resetClient(client *c) {
|
||||
c->flags |= CLIENT_REPLY_SKIP;
|
||||
c->flags &= ~CLIENT_REPLY_SKIP_NEXT;
|
||||
}
|
||||
|
||||
/* Always clear the prevent logging field. */
|
||||
c->flags &= ~CLIENT_PREVENT_LOGGING;
|
||||
}
|
||||
|
||||
/* This function is used when we want to re-enter the event loop but there
|
||||
@@ -1855,6 +1941,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError("invalid mbulk count",c);
|
||||
return C_ERR;
|
||||
} else if (ll > 10 && authRequired(c)) {
|
||||
addReplyError(c, "Protocol error: unauthenticated multibulk length");
|
||||
setProtocolError("unauth mbulk count", c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = (newline-c->querybuf)+2;
|
||||
@@ -1902,6 +1992,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c);
|
||||
return C_ERR;
|
||||
} else if (ll > 16384 && authRequired(c)) {
|
||||
addReplyError(c, "Protocol error: unauthenticated bulk length");
|
||||
setProtocolError("unauth bulk length", c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = newline-c->querybuf+2;
|
||||
@@ -1969,19 +2063,23 @@ int processMultibulkBuffer(client *c) {
|
||||
* 2. In the case of master clients, the replication offset is updated.
|
||||
* 3. Propagate commands we got from our master to replicas down the line. */
|
||||
void commandProcessed(client *c) {
|
||||
/* If client is blocked(including paused), just return avoid reset and replicate.
|
||||
*
|
||||
* 1. Don't reset the client structure for blocked clients, so that the reply
|
||||
* callback will still be able to access the client argv and argc fields.
|
||||
* The client will be reset in unblockClient().
|
||||
* 2. Don't update replication offset or propagate commands to replicas,
|
||||
* since we have not applied the command. */
|
||||
if (c->flags & CLIENT_BLOCKED) return;
|
||||
|
||||
resetClient(c);
|
||||
|
||||
long long prev_offset = c->reploff;
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for blocked clients, so that the reply
|
||||
* callback will still be able to access the client argv and argc fields.
|
||||
* The client will be reset in unblockClient(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
resetClient(c);
|
||||
}
|
||||
|
||||
/* If the client is a master we need to compute the difference
|
||||
* between the applied offset before and after processing the buffer,
|
||||
* to understand how much of the replication stream was actually
|
||||
@@ -2672,7 +2770,7 @@ NULL
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
|
||||
!= C_OK) return;
|
||||
struct client *target = lookupClientByID(id);
|
||||
if (target && target->flags & CLIENT_BLOCKED) {
|
||||
if (target && target->flags & CLIENT_BLOCKED && moduleBlockedClientMayTimeout(target)) {
|
||||
if (unblock_error)
|
||||
addReplyError(target,
|
||||
"-UNBLOCKED client unblocked via CLIENT UNBLOCK");
|
||||
@@ -2967,7 +3065,8 @@ void helloCommand(client *c) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
const char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
|
||||
preventCommandLogging(c);
|
||||
redactClientCommandArgument(c, j+1);
|
||||
redactClientCommandArgument(c, j+2);
|
||||
if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
|
||||
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
|
||||
return;
|
||||
@@ -3054,6 +3153,15 @@ static void retainOriginalCommandVector(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Redact a given argument to prevent it from being shown
|
||||
* in the slowlog. This information is stored in the
|
||||
* original_argv array. */
|
||||
void redactClientCommandArgument(client *c, int argc) {
|
||||
retainOriginalCommandVector(c);
|
||||
decrRefCount(c->argv[argc]);
|
||||
c->original_argv[argc] = shared.redacted;
|
||||
}
|
||||
|
||||
/* Rewrite the command vector of the client. All the new objects ref count
|
||||
* is incremented. The old command vector is freed, and the old objects
|
||||
* ref count is decremented. */
|
||||
@@ -3183,6 +3291,11 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
int soft = 0, hard = 0, class;
|
||||
unsigned long used_mem = getClientOutputBufferMemoryUsage(c);
|
||||
|
||||
/* For unauthenticated clients the output buffer is limited to prevent
|
||||
* them from abusing it by not reading the replies */
|
||||
if (used_mem > 1024 && authRequired(c))
|
||||
return 1;
|
||||
|
||||
class = getClientType(c);
|
||||
/* For the purpose of output buffer limiting, masters are handled
|
||||
* like normal clients. */
|
||||
@@ -3223,18 +3336,33 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
*
|
||||
* Note: we need to close the client asynchronously because this function is
|
||||
* called from contexts where the client can't be freed safely, i.e. from the
|
||||
* lower level functions pushing data inside the client output buffers. */
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
|
||||
if (!c->conn) return; /* It is unsafe to free fake clients. */
|
||||
* lower level functions pushing data inside the client output buffers.
|
||||
* When `async` is set to 0, we close the client immediately, this is
|
||||
* useful when called from cron.
|
||||
*
|
||||
* Returns 1 if client was (flagged) closed. */
|
||||
int closeClientOnOutputBufferLimitReached(client *c, int async) {
|
||||
if (!c->conn) return 0; /* It is unsafe to free fake clients. */
|
||||
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
|
||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return 0;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
freeClientAsync(c);
|
||||
serverLog(LL_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client);
|
||||
if (async) {
|
||||
freeClientAsync(c);
|
||||
serverLog(LL_WARNING,
|
||||
"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.",
|
||||
client);
|
||||
} else {
|
||||
freeClient(c);
|
||||
serverLog(LL_WARNING,
|
||||
"Client %s closed for overcoming of output buffer limits.",
|
||||
client);
|
||||
}
|
||||
sdsfree(client);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function used by performEvictions() in order to flush slaves
|
||||
@@ -3313,6 +3441,7 @@ void unpauseClients(void) {
|
||||
client *c;
|
||||
|
||||
server.client_pause_type = CLIENT_PAUSE_OFF;
|
||||
server.client_pause_end_time = 0;
|
||||
|
||||
/* Unblock all of the clients so they are reprocessed. */
|
||||
listRewind(server.paused_clients,&li);
|
||||
@@ -3632,7 +3761,7 @@ int postponeClientRead(client *c) {
|
||||
if (server.io_threads_active &&
|
||||
server.io_threads_do_reads &&
|
||||
!ProcessingEventsWhileBlocked &&
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ|CLIENT_BLOCKED)))
|
||||
{
|
||||
c->flags |= CLIENT_PENDING_READ;
|
||||
listAddNodeHead(server.clients_pending_read,c);
|
||||
@@ -3696,6 +3825,7 @@ int handleClientsWithPendingReadsUsingThreads(void) {
|
||||
c->flags &= ~CLIENT_PENDING_READ;
|
||||
listDelNode(server.clients_pending_read,ln);
|
||||
|
||||
serverAssert(!(c->flags & CLIENT_BLOCKED));
|
||||
if (processPendingCommandsAndResetClient(c) == C_ERR) {
|
||||
/* If the client is no longer valid, we avoid
|
||||
* processing the client later. So we just go
|
||||
|
||||
+23
-7
@@ -123,6 +123,21 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
return createRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* Same as CreateRawStringObject, can return NULL if allocation fails */
|
||||
robj *tryCreateRawStringObject(const char *ptr, size_t len) {
|
||||
sds str = sdstrynewlen(ptr,len);
|
||||
if (!str) return NULL;
|
||||
return createObject(OBJ_STRING, str);
|
||||
}
|
||||
|
||||
/* Same as createStringObject, can return NULL if allocation fails */
|
||||
robj *tryCreateStringObject(const char *ptr, size_t len) {
|
||||
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
else
|
||||
return tryCreateRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* Create a string object from a long long value. When possible returns a
|
||||
* shared integer object, or at least an integer encoded one.
|
||||
*
|
||||
@@ -415,7 +430,7 @@ void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,7 +851,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
if (samples) asize += (double)elesize/samples*dictSize(d);
|
||||
} else if (o->encoding == OBJ_ENCODING_INTSET) {
|
||||
intset *is = o->ptr;
|
||||
asize = sizeof(*o)+sizeof(*is)+is->encoding*is->length;
|
||||
asize = sizeof(*o)+sizeof(*is)+(size_t)is->encoding*is->length;
|
||||
} else {
|
||||
serverPanic("Unknown set encoding");
|
||||
}
|
||||
@@ -881,7 +896,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
}
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
stream *s = o->ptr;
|
||||
asize = sizeof(*o);
|
||||
asize = sizeof(*o)+sizeof(*s);
|
||||
asize += streamRadixTreeMemoryUsage(s->rax);
|
||||
|
||||
/* Now we have to add the listpacks. The last listpack is often non
|
||||
@@ -894,7 +909,8 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
size_t lpsize = 0, samples = 0;
|
||||
while(samples < sample_size && raxNext(&ri)) {
|
||||
unsigned char *lp = ri.data;
|
||||
lpsize += lpBytes(lp);
|
||||
/* Use the allocated size, since we overprovision the node initially. */
|
||||
lpsize += zmalloc_size(lp);
|
||||
samples++;
|
||||
}
|
||||
if (s->rax->numele <= samples) {
|
||||
@@ -906,7 +922,8 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
* if there are a few elements in the radix tree. */
|
||||
raxSeek(&ri,"$",NULL,0);
|
||||
raxNext(&ri);
|
||||
asize += lpBytes(ri.data);
|
||||
/* Use the allocated size, since we overprovision the node initially. */
|
||||
asize += zmalloc_size(ri.data);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
@@ -1231,8 +1248,7 @@ robj *objectCommandLookup(client *c, robj *key) {
|
||||
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = objectCommandLookup(c,key);
|
||||
|
||||
if (!o) addReply(c, reply);
|
||||
if (!o) SentReplyOnKeyMiss(c, reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -62,7 +62,7 @@ static inline void swapfunc (char *, char *, size_t, int);
|
||||
} while (--i > 0); \
|
||||
}
|
||||
|
||||
#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
|
||||
#define SWAPINIT(a, es) swaptype = (uintptr_t)a % sizeof(long) || \
|
||||
es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
|
||||
|
||||
static inline void
|
||||
|
||||
@@ -41,6 +41,8 @@ int clientSubscriptionsCount(client *c);
|
||||
* to send a special message (for instance an Array type) by using the
|
||||
* addReply*() API family. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -48,12 +50,15 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
if (msg) addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
@@ -62,10 +67,13 @@ void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -73,6 +81,7 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
@@ -80,6 +89,8 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -90,10 +101,13 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -101,6 +115,7 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
@@ -108,6 +123,8 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -118,6 +135,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
|
||||
+20
-6
@@ -45,11 +45,16 @@
|
||||
#define REDIS_STATIC static
|
||||
#endif
|
||||
|
||||
/* Optimization levels for size-based filling */
|
||||
/* Optimization levels for size-based filling.
|
||||
* Note that the largest possible limit is 16k, so even if each record takes
|
||||
* just one byte, it still won't overflow the 16 bit count field. */
|
||||
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
|
||||
/* Maximum size in bytes of any multi-element ziplist.
|
||||
* Larger values will live in their own isolated ziplists. */
|
||||
* Larger values will live in their own isolated ziplists.
|
||||
* This is used only if we're limited by record count. when we're limited by
|
||||
* size, the maximum limit is bigger, but still safe.
|
||||
* 8k is a recommended / default size limit */
|
||||
#define SIZE_SAFETY_LIMIT 8192
|
||||
|
||||
/* Minimum ziplist size in bytes for attempting compression. */
|
||||
@@ -444,6 +449,8 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
|
||||
unsigned int new_sz = node->sz + sz + ziplist_overhead;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(new_sz))
|
||||
return 0;
|
||||
else if ((int)node->count < fill)
|
||||
@@ -463,6 +470,8 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
unsigned int merge_sz = a->sz + b->sz - 11;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(merge_sz))
|
||||
return 0;
|
||||
else if ((int)(a->count + b->count) <= fill)
|
||||
@@ -482,6 +491,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
* Returns 1 if new head created. */
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_head = quicklist->head;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
|
||||
quicklist->head->zl =
|
||||
@@ -505,6 +515,7 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
* Returns 1 if new tail created. */
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_tail = quicklist->tail;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
|
||||
quicklist->tail->zl =
|
||||
@@ -847,6 +858,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
|
||||
int fill = quicklist->fill;
|
||||
quicklistNode *node = entry->node;
|
||||
quicklistNode *new_node = NULL;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
|
||||
if (!node) {
|
||||
/* we have no reference node, so let's create only node in the list */
|
||||
@@ -1852,7 +1864,7 @@ int quicklistTest(int argc, char *argv[], int accurate) {
|
||||
unsigned int sz;
|
||||
long long lv;
|
||||
ql_info(ql);
|
||||
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
|
||||
assert(data != NULL);
|
||||
assert(sz == 32);
|
||||
if (strcmp(populate, (char *)data))
|
||||
@@ -1870,7 +1882,7 @@ int quicklistTest(int argc, char *argv[], int accurate) {
|
||||
unsigned int sz;
|
||||
long long lv;
|
||||
ql_info(ql);
|
||||
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
|
||||
assert(data == NULL);
|
||||
assert(lv == 55513);
|
||||
ql_verify(ql, 0, 0, 0, 0);
|
||||
@@ -2669,9 +2681,11 @@ int quicklistTest(int argc, char *argv[], int accurate) {
|
||||
if (step == 1) {
|
||||
for (int i = 0; i < list_sizes[list] / 2; i++) {
|
||||
unsigned char *data;
|
||||
quicklistPop(ql, QUICKLIST_HEAD, &data, NULL, NULL);
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data,
|
||||
NULL, NULL));
|
||||
zfree(data);
|
||||
quicklistPop(ql, QUICKLIST_TAIL, &data, NULL, NULL);
|
||||
assert(quicklistPop(ql, QUICKLIST_TAIL, &data,
|
||||
NULL, NULL));
|
||||
zfree(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1270,6 +1270,7 @@ void raxStart(raxIterator *it, rax *rt) {
|
||||
* is a low level function used to implement the iterator, not callable by
|
||||
* the user. Returns 0 on out of memory, otherwise 1 is returned. */
|
||||
int raxIteratorAddChars(raxIterator *it, unsigned char *s, size_t len) {
|
||||
if (len == 0) return 1;
|
||||
if (it->key_max < it->key_len+len) {
|
||||
unsigned char *old = (it->key == it->key_static_string) ? NULL :
|
||||
it->key;
|
||||
|
||||
@@ -288,12 +288,16 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
} else if (enctype == RDB_ENC_INT16) {
|
||||
uint16_t v;
|
||||
if (rioRead(rdb,enc,2) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8);
|
||||
val = (int16_t)v;
|
||||
} else if (enctype == RDB_ENC_INT32) {
|
||||
uint32_t v;
|
||||
if (rioRead(rdb,enc,4) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8)|
|
||||
((uint32_t)enc[2]<<16)|
|
||||
((uint32_t)enc[3]<<24);
|
||||
val = (int32_t)v;
|
||||
} else {
|
||||
rdbReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
|
||||
@@ -545,8 +549,12 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
}
|
||||
return buf;
|
||||
} else {
|
||||
robj *o = encode ? createStringObject(SDS_NOINIT,len) :
|
||||
createRawStringObject(SDS_NOINIT,len);
|
||||
robj *o = encode ? tryCreateStringObject(SDS_NOINIT,len) :
|
||||
tryCreateRawStringObject(SDS_NOINIT,len);
|
||||
if (!o) {
|
||||
serverLog(server.loading? LL_WARNING: LL_VERBOSE, "rdbGenericLoadStringObject failed allocating %llu bytes", len);
|
||||
return NULL;
|
||||
}
|
||||
if (len && rioRead(rdb,o->ptr,len) == 0) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
@@ -1516,12 +1524,17 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
}
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
* On success a newly allocated object is returned, otherwise NULL.
|
||||
* When the function returns NULL and if 'error' is not NULL, the
|
||||
* integer pointed by 'error' is set to the type of error that occurred */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int *error) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
|
||||
/* Set default error of load object, it will be set to 0 on success. */
|
||||
if (error) *error = RDB_LOAD_ERR_OTHER;
|
||||
|
||||
int deep_integrity_validation = server.sanitize_dump_payload == SANITIZE_DUMP_YES;
|
||||
if (server.sanitize_dump_payload == SANITIZE_DUMP_CLIENTS) {
|
||||
/* Skip sanitization when loading (an RDB), or getting a RESTORE command
|
||||
@@ -1540,6 +1553,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_LIST) {
|
||||
/* Read list value */
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createQuicklistObject();
|
||||
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
|
||||
@@ -1560,9 +1574,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_SET) {
|
||||
/* Read Set value */
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
/* Use a regular set when there are too many entries. */
|
||||
if (len > server.set_max_intset_entries) {
|
||||
size_t max_entries = server.set_max_intset_entries;
|
||||
if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
if (len > max_entries) {
|
||||
o = createSetObject();
|
||||
/* It's faster to expand the dict to the right size asap in order
|
||||
* to avoid rehashing */
|
||||
@@ -1623,10 +1640,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_ZSET_2 || rdbtype == RDB_TYPE_ZSET) {
|
||||
/* Read list/set value. */
|
||||
uint64_t zsetlen;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
zset *zs;
|
||||
|
||||
if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (zsetlen == 0) goto emptykey;
|
||||
|
||||
o = createZsetObject();
|
||||
zs = o->ptr;
|
||||
|
||||
@@ -1663,6 +1682,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
if (sdslen(sdsele) > maxelelen) maxelelen = sdslen(sdsele);
|
||||
totelelen += sdslen(sdsele);
|
||||
|
||||
znode = zslInsert(zs->zsl,score,sdsele);
|
||||
if (dictAdd(zs->dict,sdsele,&znode->score) != DICT_OK) {
|
||||
@@ -1675,8 +1695,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Convert *after* loading, since sorted sets are not stored ordered. */
|
||||
if (zsetLength(o) <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
maxelelen <= server.zset_max_ziplist_value &&
|
||||
ziplistSafeToAdd(NULL, totelelen))
|
||||
{
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH) {
|
||||
uint64_t len;
|
||||
int ret;
|
||||
@@ -1685,6 +1708,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
len = rdbLoadLen(rdb, NULL);
|
||||
if (len == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createHashObject();
|
||||
|
||||
@@ -1729,21 +1753,30 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value ||
|
||||
!ziplistSafeToAdd(o->ptr, sdslen(field)+sdslen(value)))
|
||||
{
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
ret = dictAdd((dict*)o->ptr, field, value);
|
||||
if (ret == DICT_ERR) {
|
||||
rdbReportCorruptRDB("Duplicate hash fields detected");
|
||||
if (dupSearchDict) dictRelease(dupSearchDict);
|
||||
sdsfree(value);
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Add pair to ziplist */
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
||||
sdslen(field), ZIPLIST_TAIL);
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
||||
sdslen(value), ZIPLIST_TAIL);
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value)
|
||||
{
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
}
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
}
|
||||
@@ -1792,6 +1825,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
serverAssert(len == 0);
|
||||
} else if (rdbtype == RDB_TYPE_LIST_QUICKLIST) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createQuicklistObject();
|
||||
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
@@ -1811,7 +1846,19 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
zfree(zl);
|
||||
return NULL;
|
||||
}
|
||||
quicklistAppendZiplist(o->ptr, zl);
|
||||
|
||||
/* Silently skip empty ziplists, if we'll end up with empty quicklist we'll fail later. */
|
||||
if (ziplistLen(zl) == 0) {
|
||||
zfree(zl);
|
||||
continue;
|
||||
} else {
|
||||
quicklistAppendZiplist(o->ptr, zl);
|
||||
}
|
||||
}
|
||||
|
||||
if (quicklistCount(o->ptr) == 0) {
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH_ZIPMAP ||
|
||||
rdbtype == RDB_TYPE_LIST_ZIPLIST ||
|
||||
@@ -1856,12 +1903,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
||||
if (flen > maxlen) maxlen = flen;
|
||||
if (vlen > maxlen) maxlen = vlen;
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
|
||||
/* search for duplicate records */
|
||||
sds field = sdstrynewlen(fstr, flen);
|
||||
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK) {
|
||||
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK ||
|
||||
!ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
|
||||
rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen);
|
||||
dictRelease(dupSearchDict);
|
||||
sdsfree(field);
|
||||
@@ -1870,6 +1916,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
}
|
||||
|
||||
dictRelease(dupSearchDict);
|
||||
@@ -1894,6 +1943,14 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ziplistLen(encoded) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
o->type = OBJ_LIST;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
listTypeConvert(o,OBJ_ENCODING_QUICKLIST);
|
||||
@@ -1923,6 +1980,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
o->type = OBJ_ZSET;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
if (zsetLength(o) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
if (zsetLength(o) > server.zset_max_ziplist_entries)
|
||||
zsetConvert(o,OBJ_ENCODING_SKIPLIST);
|
||||
break;
|
||||
@@ -1937,6 +2001,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
o->type = OBJ_HASH;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
if (hashTypeLength(o) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
@@ -2005,7 +2076,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
|
||||
/* Insert the key in the radix tree. */
|
||||
int retval = raxInsert(s->rax,
|
||||
int retval = raxTryInsert(s->rax,
|
||||
(unsigned char*)nodekey,sizeof(streamID),lp,NULL);
|
||||
sdsfree(nodekey);
|
||||
if (!retval) {
|
||||
@@ -2094,7 +2165,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
streamFreeNACK(nack);
|
||||
return NULL;
|
||||
}
|
||||
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
if (!raxTryInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
rdbReportCorruptRDB("Duplicated global PEL entry "
|
||||
"loading stream consumer group");
|
||||
decrRefCount(o);
|
||||
@@ -2158,15 +2229,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
* loading the global PEL. Then set the same shared
|
||||
* NACK structure also in the consumer-specific PEL. */
|
||||
nack->consumer = consumer;
|
||||
if (!raxInsert(consumer->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
if (!raxTryInsert(consumer->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
rdbReportCorruptRDB("Duplicated consumer PEL entry "
|
||||
" loading a stream consumer "
|
||||
"group");
|
||||
decrRefCount(o);
|
||||
streamFreeNACK(nack);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify that each PEL eventually got a consumer assigned to it. */
|
||||
if (deep_integrity_validation) {
|
||||
raxIterator ri_cg_pel;
|
||||
raxStart(&ri_cg_pel,cgroup->pel);
|
||||
raxSeek(&ri_cg_pel,"^",NULL,0);
|
||||
while(raxNext(&ri_cg_pel)) {
|
||||
streamNACK *nack = ri_cg_pel.data;
|
||||
if (!nack->consumer) {
|
||||
raxStop(&ri_cg_pel);
|
||||
rdbReportCorruptRDB("Stream CG PEL entry without consumer");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
raxStop(&ri_cg_pel);
|
||||
}
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
@@ -2233,7 +2322,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
|
||||
return NULL;
|
||||
}
|
||||
if (error) *error = 0;
|
||||
return o;
|
||||
|
||||
emptykey:
|
||||
if (error) *error = RDB_LOAD_ERR_EMPTY_KEY;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
@@ -2334,6 +2428,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
int type, rdbver;
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
int error;
|
||||
long long empty_keys_skipped = 0, expired_keys_skipped = 0, keys_loaded = 0;
|
||||
|
||||
rdb->update_cksum = rdbLoadProgressCallback;
|
||||
rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
|
||||
@@ -2423,7 +2519,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* An AUX field is composed of two strings: key and value. */
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(rdb)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
if (((char*)auxkey->ptr)[0] == '%') {
|
||||
/* All the fields with a name staring with '%' are considered
|
||||
@@ -2484,8 +2583,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
int when_opcode = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT)
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
|
||||
rdbReportReadError("bad when_opcode");
|
||||
goto eoferr;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@@ -2506,19 +2607,20 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
}
|
||||
int rc = mt->aux_load(&io,moduleid&1023, when);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
if (rc != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
goto eoferr;
|
||||
}
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
goto eoferr;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
@@ -2533,10 +2635,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
val = rdbLoadObject(type,rdb,key,&error);
|
||||
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
@@ -2546,18 +2645,33 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* Similarly if the RDB is the preamble of an AOF file, we want to
|
||||
* load all the keys as they are, since the log of operations later
|
||||
* assume to work in an exact keyspace state. */
|
||||
if (iAmMaster() &&
|
||||
if (val == NULL) {
|
||||
/* Since we used to have bug that could lead to empty keys
|
||||
* (See #8453), we rather not fail when empty key is encountered
|
||||
* in an RDB file, instead we will silently discard it and
|
||||
* continue loading. */
|
||||
if (error == RDB_LOAD_ERR_EMPTY_KEY) {
|
||||
if(empty_keys_skipped++ < 10)
|
||||
serverLog(LL_WARNING, "rdbLoadObject skipping empty key: %s", key);
|
||||
sdsfree(key);
|
||||
} else {
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
} else if (iAmMaster() &&
|
||||
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
|
||||
expiretime != -1 && expiretime < now)
|
||||
{
|
||||
sdsfree(key);
|
||||
decrRefCount(val);
|
||||
expired_keys_skipped++;
|
||||
} else {
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
int added = dbAddRDBLoad(db,key,val);
|
||||
keys_loaded++;
|
||||
if (!added) {
|
||||
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
|
||||
/* This flag is useful for DEBUG RELOAD special modes.
|
||||
@@ -2614,6 +2728,16 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty_keys_skipped) {
|
||||
serverLog(LL_WARNING,
|
||||
"Done loading RDB, keys loaded: %lld, keys expired: %lld, empty keys skipped: %lld.",
|
||||
keys_loaded, expired_keys_skipped, empty_keys_skipped);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"Done loading RDB, keys loaded: %lld, keys expired: %lld.",
|
||||
keys_loaded, expired_keys_skipped);
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
/* Unexpected end of file is handled here calling rdbReportReadError():
|
||||
@@ -2691,6 +2815,7 @@ static void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
}
|
||||
if (server.rdb_child_exit_pipe!=-1)
|
||||
close(server.rdb_child_exit_pipe);
|
||||
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
|
||||
close(server.rdb_pipe_read);
|
||||
server.rdb_child_exit_pipe = -1;
|
||||
server.rdb_pipe_read = -1;
|
||||
@@ -2794,6 +2919,10 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
|
||||
rioInitWithFd(&rdb,rdb_pipe_write);
|
||||
|
||||
/* Close the reading part, so that if the parent crashes, the child will
|
||||
* get a write error and exit. */
|
||||
close(server.rdb_pipe_read);
|
||||
|
||||
redisSetProcTitle("redis-rdb-to-slaves");
|
||||
redisSetCpuAffinity(server.bgsave_cpulist);
|
||||
|
||||
|
||||
@@ -127,6 +127,11 @@
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
/* When rdbLoadObject() returns NULL, the err flag is
|
||||
* set to hold the type of error that occurred */
|
||||
#define RDB_LOAD_ERR_EMPTY_KEY 1 /* Error of empty key */
|
||||
#define RDB_LOAD_ERR_OTHER 2 /* Any other errors */
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
int rdbSaveTime(rio *rdb, time_t t);
|
||||
@@ -145,7 +150,7 @@ void rdbRemoveTempFile(pid_t childpid, int from_signal);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key, int *error);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
||||
+49
-44
@@ -99,7 +99,6 @@ static struct config {
|
||||
int randomkeys_keyspacelen;
|
||||
int keepalive;
|
||||
int pipeline;
|
||||
int showerrors;
|
||||
long long start;
|
||||
long long totlatency;
|
||||
const char *title;
|
||||
@@ -307,7 +306,9 @@ static redisContext *getRedisContext(const char *ip, int port,
|
||||
fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str);
|
||||
else
|
||||
fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str);
|
||||
goto cleanup;
|
||||
freeReplyObject(reply);
|
||||
redisFree(ctx);
|
||||
exit(1);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return ctx;
|
||||
@@ -366,9 +367,16 @@ fail:
|
||||
fprintf(stderr, "ERROR: failed to fetch CONFIG from ");
|
||||
if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port);
|
||||
else fprintf(stderr, "%s\n", hostsocket);
|
||||
int abort_test = 0;
|
||||
if (reply && reply->type == REDIS_REPLY_ERROR &&
|
||||
(!strncmp(reply->str,"NOAUTH",5) ||
|
||||
!strncmp(reply->str,"WRONGPASS",9) ||
|
||||
!strncmp(reply->str,"NOPERM",5)))
|
||||
abort_test = 1;
|
||||
freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
freeRedisConfig(cfg);
|
||||
if (abort_test) exit(1);
|
||||
return NULL;
|
||||
}
|
||||
static void freeRedisConfig(redisConfig *cfg) {
|
||||
@@ -513,44 +521,39 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
exit(1);
|
||||
}
|
||||
redisReply *r = reply;
|
||||
int is_err = (r->type == REDIS_REPLY_ERROR);
|
||||
|
||||
if (is_err && config.showerrors) {
|
||||
/* TODO: static lasterr_time not thread-safe */
|
||||
static time_t lasterr_time = 0;
|
||||
time_t now = time(NULL);
|
||||
if (lasterr_time != now) {
|
||||
lasterr_time = now;
|
||||
if (c->cluster_node) {
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
if (r->type == REDIS_REPLY_ERROR) {
|
||||
/* Try to update slots configuration if reply error is
|
||||
* MOVED/ASK/CLUSTERDOWN and the key(s) used by the command
|
||||
* contain(s) the slot hash tag.
|
||||
* If the error is not topology-update related then we
|
||||
* immediately exit to avoid false results. */
|
||||
if (c->cluster_node && c->staglen) {
|
||||
int fetch_slots = 0, do_wait = 0;
|
||||
if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3))
|
||||
fetch_slots = 1;
|
||||
else if (!strncmp(r->str,"CLUSTERDOWN",11)) {
|
||||
/* Usually the cluster is able to recover itself after
|
||||
* a CLUSTERDOWN error, so try to sleep one second
|
||||
* before requesting the new configuration. */
|
||||
fetch_slots = 1;
|
||||
do_wait = 1;
|
||||
printf("Error from server %s:%d: %s.\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
}
|
||||
if (do_wait) sleep(1);
|
||||
if (fetch_slots && !fetchClusterSlotsConfiguration(c))
|
||||
exit(1);
|
||||
} else {
|
||||
if (c->cluster_node) {
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
} else printf("Error from server: %s\n", r->str);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to update slots configuration if reply error is
|
||||
* MOVED/ASK/CLUSTERDOWN and the key(s) used by the command
|
||||
* contain(s) the slot hash tag. */
|
||||
if (is_err && c->cluster_node && c->staglen) {
|
||||
int fetch_slots = 0, do_wait = 0;
|
||||
if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3))
|
||||
fetch_slots = 1;
|
||||
else if (!strncmp(r->str,"CLUSTERDOWN",11)) {
|
||||
/* Usually the cluster is able to recover itself after
|
||||
* a CLUSTERDOWN error, so try to sleep one second
|
||||
* before requesting the new configuration. */
|
||||
fetch_slots = 1;
|
||||
do_wait = 1;
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
}
|
||||
if (do_wait) sleep(1);
|
||||
if (fetch_slots && !fetchClusterSlotsConfiguration(c))
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
@@ -640,6 +643,9 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
fprintf(stderr, "Error writing to the server: %s\n", strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
} else if (nwritten > 0) {
|
||||
c->written += nwritten;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
|
||||
@@ -1293,8 +1299,7 @@ static int fetchClusterSlotsConfiguration(client c) {
|
||||
atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1);
|
||||
if (is_fetching_slots) return -1; //TODO: use other codes || errno ?
|
||||
atomicSet(config.is_fetching_slots, 1);
|
||||
if (config.showerrors)
|
||||
printf("Cluster slots configuration changed, fetching new one...\n");
|
||||
printf("WARNING: Cluster slots configuration changed, fetching new one...\n");
|
||||
const char *errmsg = "Failed to update cluster slots configuration";
|
||||
static dictType dtype = {
|
||||
dictSdsHash, /* hash function */
|
||||
@@ -1470,7 +1475,8 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-I")) {
|
||||
config.idlemode = 1;
|
||||
} else if (!strcmp(argv[i],"-e")) {
|
||||
config.showerrors = 1;
|
||||
printf("WARNING: -e option has been deprecated. "
|
||||
"We now immediatly exit on error to avoid false results.\n");
|
||||
} else if (!strcmp(argv[i],"-t")) {
|
||||
if (lastarg) goto invalid;
|
||||
/* We get the list of tests to run as a string in the form
|
||||
@@ -1573,8 +1579,6 @@ usage:
|
||||
" is executed. Default tests use this to hit random keys in the\n"
|
||||
" specified range.\n"
|
||||
" -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n"
|
||||
" -e If server replies with errors, show them on stdout.\n"
|
||||
" (no more than 1 error per second is displayed)\n"
|
||||
" -q Quiet. Just show query/sec values\n"
|
||||
" --precision Number of decimal places to display in latency output (default 0)\n"
|
||||
" --csv Output in CSV format\n"
|
||||
@@ -1699,7 +1703,6 @@ int main(int argc, const char **argv) {
|
||||
config.keepalive = 1;
|
||||
config.datasize = 3;
|
||||
config.pipeline = 1;
|
||||
config.showerrors = 0;
|
||||
config.randomkeys = 0;
|
||||
config.randomkeys_keyspacelen = 0;
|
||||
config.quiet = 0;
|
||||
@@ -1782,8 +1785,9 @@ int main(int argc, const char **argv) {
|
||||
} else {
|
||||
config.redis_config =
|
||||
getRedisConfig(config.hostip, config.hostport, config.hostsocket);
|
||||
if (config.redis_config == NULL)
|
||||
if (config.redis_config == NULL) {
|
||||
fprintf(stderr, "WARN: could not fetch server CONFIG\n");
|
||||
}
|
||||
}
|
||||
if (config.num_threads > 0) {
|
||||
pthread_mutex_init(&(config.liveclients_mutex), NULL);
|
||||
@@ -1946,8 +1950,8 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_500")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag);
|
||||
benchmark("LRANGE_500 (first 450 elements)",cmd,len);
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 499",tag);
|
||||
benchmark("LRANGE_500 (first 500 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
@@ -1974,6 +1978,7 @@ int main(int argc, const char **argv) {
|
||||
if (!config.csv) printf("\n");
|
||||
} while(config.loop);
|
||||
|
||||
zfree(data);
|
||||
if (config.redis_config != NULL) freeRedisConfig(config.redis_config);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -39,12 +39,14 @@
|
||||
|
||||
static char error[1044];
|
||||
static off_t epos;
|
||||
static long long line = 1;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
if (strncmp(buf,"\r\n",2) != 0) {
|
||||
ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]);
|
||||
return 0;
|
||||
}
|
||||
line += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -201,8 +203,8 @@ int redis_check_aof_main(int argc, char **argv) {
|
||||
|
||||
off_t pos = process(fp);
|
||||
off_t diff = size-pos;
|
||||
printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
|
||||
(long long) size, (long long) pos, (long long) diff);
|
||||
printf("AOF analyzed: size=%lld, ok_up_to=%lld, ok_up_to_line=%lld, diff=%lld\n",
|
||||
(long long) size, (long long) pos, line, (long long) diff);
|
||||
if (diff > 0) {
|
||||
if (fix) {
|
||||
char buf[2];
|
||||
|
||||
@@ -250,7 +250,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
|
||||
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
|
||||
goto eoferr;
|
||||
rdbCheckInfo("Selecting DB ID %d", dbid);
|
||||
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_RESIZEDB) {
|
||||
/* RESIZEDB: Hint about the size of the keys in the currently
|
||||
@@ -308,7 +308,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr,NULL)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+130
-56
@@ -207,7 +207,8 @@ static struct config {
|
||||
cliSSLconfig sslconfig;
|
||||
long repeat;
|
||||
long interval;
|
||||
int dbnum;
|
||||
int dbnum; /* db num currently selected */
|
||||
int input_dbnum; /* db num user input */
|
||||
int interactive;
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
@@ -219,6 +220,7 @@ static struct config {
|
||||
long long lru_test_sample_size;
|
||||
int cluster_mode;
|
||||
int cluster_reissue_command;
|
||||
int cluster_send_asking;
|
||||
int slave_mode;
|
||||
int pipe_mode;
|
||||
int pipe_timeout;
|
||||
@@ -445,7 +447,7 @@ static void parseRedisUri(const char *uri) {
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract database number. */
|
||||
config.dbnum = atoi(curr);
|
||||
config.input_dbnum = atoi(curr);
|
||||
}
|
||||
|
||||
static uint64_t dictSdsHash(const void *key) {
|
||||
@@ -663,7 +665,7 @@ static void cliOutputHelp(int argc, char **argv) {
|
||||
help = entry->org;
|
||||
if (group == -1) {
|
||||
/* Compare all arguments */
|
||||
if (argc == entry->argc) {
|
||||
if (argc <= entry->argc) {
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
|
||||
}
|
||||
@@ -792,28 +794,42 @@ static int cliAuth(redisContext *ctx, char *user, char *auth) {
|
||||
reply = redisCommand(ctx,"AUTH %s",auth);
|
||||
else
|
||||
reply = redisCommand(ctx,"AUTH %s %s",user,auth);
|
||||
if (reply != NULL) {
|
||||
if (reply->type == REDIS_REPLY_ERROR)
|
||||
fprintf(stderr,"Warning: AUTH failed\n");
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr, "AUTH failed: %s\n", reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Send SELECT dbnum to the server */
|
||||
/* Send SELECT input_dbnum to the server */
|
||||
static int cliSelect(void) {
|
||||
redisReply *reply;
|
||||
if (config.dbnum == 0) return REDIS_OK;
|
||||
if (config.input_dbnum == config.dbnum) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"SELECT %d",config.dbnum);
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
reply = redisCommand(context,"SELECT %d",config.input_dbnum);
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"SELECT %d failed: %s\n",config.input_dbnum,reply->str);
|
||||
} else {
|
||||
config.dbnum = config.input_dbnum;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
||||
@@ -822,13 +838,18 @@ static int cliSwitchProto(void) {
|
||||
if (config.resp3 == 0) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"HELLO 3");
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"HELLO 3 failed: %s\n",reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Connect to the server. It is possible to pass certain flags to the function:
|
||||
@@ -844,7 +865,9 @@ static int cliConnect(int flags) {
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
|
||||
if (config.hostsocket == NULL) {
|
||||
/* Do not use hostsocket when we got redirected in cluster mode */
|
||||
if (config.hostsocket == NULL ||
|
||||
(config.cluster_mode && config.cluster_reissue_command)) {
|
||||
context = redisConnect(config.hostip,config.hostport);
|
||||
} else {
|
||||
context = redisConnectUnix(config.hostsocket);
|
||||
@@ -863,12 +886,15 @@ static int cliConnect(int flags) {
|
||||
if (context->err) {
|
||||
if (!(flags & CC_QUIET)) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
if (config.hostsocket == NULL)
|
||||
fprintf(stderr,"%s:%d: %s\n",
|
||||
if (config.hostsocket == NULL ||
|
||||
(config.cluster_mode && config.cluster_reissue_command))
|
||||
{
|
||||
fprintf(stderr, "%s:%d: %s\n",
|
||||
config.hostip,config.hostport,context->errstr);
|
||||
else
|
||||
} else {
|
||||
fprintf(stderr,"%s: %s\n",
|
||||
config.hostsocket,context->errstr);
|
||||
}
|
||||
}
|
||||
redisFree(context);
|
||||
context = NULL;
|
||||
@@ -899,6 +925,29 @@ static int cliConnect(int flags) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* In cluster, if server replies ASK, we will redirect to a different node.
|
||||
* Before sending the real command, we need to send ASKING command first. */
|
||||
static int cliSendAsking() {
|
||||
redisReply *reply;
|
||||
|
||||
config.cluster_send_asking = 0;
|
||||
if (context == NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
reply = redisCommand(context,"ASKING");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"ASKING failed: %s\n",reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cliPrintContextError(void) {
|
||||
if (context == NULL) return;
|
||||
fprintf(stderr,"Error: %s\n",context->errstr);
|
||||
@@ -1127,6 +1176,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
out = sdscatprintf(out,"%s",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_PUSH:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -1185,6 +1235,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
||||
out = sdscat(out,r->integer ? "true" : "false");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_PUSH:
|
||||
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -1272,7 +1323,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
/* Check if we need to connect to a different node and reissue the
|
||||
* request. */
|
||||
if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
|
||||
(!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
|
||||
(!strncmp(reply->str,"MOVED ",6) || !strncmp(reply->str,"ASK ",4)))
|
||||
{
|
||||
char *p = reply->str, *s;
|
||||
int slot;
|
||||
@@ -1296,6 +1347,9 @@ static int cliReadReply(int output_raw_strings) {
|
||||
printf("-> Redirected to slot [%d] located at %s:%d\n",
|
||||
slot, config.hostip, config.hostport);
|
||||
config.cluster_reissue_command = 1;
|
||||
if (!strncmp(reply->str,"ASK ",4)) {
|
||||
config.cluster_send_asking = 1;
|
||||
}
|
||||
cliRefreshPrompt();
|
||||
} else if (!config.interactive && config.set_errcode &&
|
||||
reply->type == REDIS_REPLY_ERROR)
|
||||
@@ -1308,6 +1362,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
if (output) {
|
||||
out = cliFormatReply(reply, config.output, output_raw_strings);
|
||||
fwrite(out,sdslen(out),1,stdout);
|
||||
fflush(stdout);
|
||||
sdsfree(out);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
@@ -1429,7 +1484,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
if (!strcasecmp(command,"select") && argc == 2 &&
|
||||
config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
{
|
||||
config.dbnum = atoi(argv[1]);
|
||||
config.input_dbnum = config.dbnum = atoi(argv[1]);
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) {
|
||||
cliSelect();
|
||||
@@ -1441,21 +1496,23 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"exec") && argc == 1 && config.in_multi) {
|
||||
config.in_multi = 0;
|
||||
if (config.last_cmd_type == REDIS_REPLY_ERROR) {
|
||||
config.dbnum = config.pre_multi_dbnum;
|
||||
if (config.last_cmd_type == REDIS_REPLY_ERROR ||
|
||||
config.last_cmd_type == REDIS_REPLY_NIL)
|
||||
{
|
||||
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
|
||||
}
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"discard") && argc == 1 &&
|
||||
config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
{
|
||||
config.in_multi = 0;
|
||||
config.dbnum = config.pre_multi_dbnum;
|
||||
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
}
|
||||
if (config.cluster_reissue_command){
|
||||
/* If we need to reissue the command, break to prevent a
|
||||
further 'repeat' number of dud interations */
|
||||
further 'repeat' number of dud interactions */
|
||||
break;
|
||||
}
|
||||
if (config.interval) usleep(config.interval);
|
||||
@@ -1536,7 +1593,7 @@ static int parseOptions(int argc, char **argv) {
|
||||
double seconds = atof(argv[++i]);
|
||||
config.interval = seconds*1000000;
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
config.input_dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
config.no_auth_warning = 1;
|
||||
} else if (!strcmp(argv[i], "--askpass")) {
|
||||
@@ -1774,6 +1831,11 @@ static int parseOptions(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hostsocket && config.cluster_mode) {
|
||||
fprintf(stderr,"Options -c and -s are mutually exclusive.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* --ldb requires --eval. */
|
||||
if (config.eval_ldb && config.eval == NULL) {
|
||||
fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
|
||||
@@ -1892,6 +1954,7 @@ static void usage(void) {
|
||||
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
|
||||
" --replica Simulate a replica showing commands received from the master.\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" Use filename of \"-\" to write to stdout.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
@@ -1990,23 +2053,32 @@ static sds *getSdsArrayFromArgv(int argc, char **argv, int quoted) {
|
||||
|
||||
static int issueCommandRepeat(int argc, char **argv, long repeat) {
|
||||
while (1) {
|
||||
if (config.cluster_reissue_command || context == NULL ||
|
||||
context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF)
|
||||
{
|
||||
if (cliConnect(CC_FORCE) != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
config.cluster_reissue_command = 0;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
config.cluster_reissue_command = 0;
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliConnect(CC_FORCE);
|
||||
|
||||
/* If we still cannot send the command print error.
|
||||
* We'll try to reconnect the next time. */
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
if (config.cluster_send_asking) {
|
||||
if (cliSendAsking() != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
cliConnect(CC_FORCE);
|
||||
} else {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
@@ -5481,7 +5553,7 @@ static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) {
|
||||
static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
|
||||
clusterManagerNode **nodeptr)
|
||||
{
|
||||
assert(array->nodes < (array->nodes + array->len));
|
||||
assert(array->len > 0);
|
||||
/* If the first node to be shifted is not NULL, decrement count. */
|
||||
if (*array->nodes != NULL) array->count--;
|
||||
/* Store the first node to be shifted into 'nodeptr'. */
|
||||
@@ -5494,7 +5566,7 @@ static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
|
||||
static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
|
||||
clusterManagerNode *node)
|
||||
{
|
||||
assert(array->nodes < (array->nodes + array->len));
|
||||
assert(array->len > 0);
|
||||
assert(node != NULL);
|
||||
assert(array->count < array->len);
|
||||
array->nodes[array->count++] = node;
|
||||
@@ -6534,9 +6606,9 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
}
|
||||
|
||||
if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
|
||||
strcat(cmdfmt, " %s");
|
||||
cmdfmt = sdscat(cmdfmt," COPY");
|
||||
if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_REPLACE)
|
||||
strcat(cmdfmt, " %s");
|
||||
cmdfmt = sdscat(cmdfmt," REPLACE");
|
||||
|
||||
/* Use SCAN to iterate over the keys, migrating to the
|
||||
* right node as needed. */
|
||||
@@ -6568,8 +6640,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
|
||||
redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
|
||||
target->ip, target->port,
|
||||
key, 0, timeout,
|
||||
"COPY", "REPLACE");
|
||||
key, 0, timeout);
|
||||
if (!r || r->type == REDIS_REPLY_ERROR) {
|
||||
if (r && r->str) {
|
||||
clusterManagerLogErr("Source %s:%d replied with "
|
||||
@@ -6871,7 +6942,7 @@ void showLatencyDistSamples(struct distsamples *samples, long long tot) {
|
||||
printf("\033[38;5;0m"); /* Set foreground color to black. */
|
||||
for (j = 0; ; j++) {
|
||||
int coloridx =
|
||||
ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
|
||||
ceil((double) samples[j].count / tot * (spectrum_palette_size-1));
|
||||
int color = spectrum_palette[coloridx];
|
||||
printf("\033[48;5;%dm%c", (int)color, samples[j].character);
|
||||
samples[j].count = 0;
|
||||
@@ -6985,7 +7056,7 @@ static void latencyDistMode(void) {
|
||||
#define RDB_EOF_MARK_SIZE 40
|
||||
|
||||
void sendReplconf(const char* arg1, const char* arg2) {
|
||||
printf("sending REPLCONF %s %s\n", arg1, arg2);
|
||||
fprintf(stderr, "sending REPLCONF %s %s\n", arg1, arg2);
|
||||
redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
|
||||
|
||||
/* Handle any error conditions */
|
||||
@@ -7045,7 +7116,7 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
|
||||
}
|
||||
*p = '\0';
|
||||
if (buf[0] == '-') {
|
||||
printf("SYNC with master failed: %s\n", buf);
|
||||
fprintf(stderr, "SYNC with master failed: %s\n", buf);
|
||||
exit(1);
|
||||
}
|
||||
if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
|
||||
@@ -7150,8 +7221,9 @@ static void getRDB(clusterManagerNode *node) {
|
||||
payload, filename);
|
||||
}
|
||||
|
||||
int write_to_stdout = !strcmp(filename,"-");
|
||||
/* Write to file. */
|
||||
if (!strcmp(filename,"-")) {
|
||||
if (write_to_stdout) {
|
||||
fd = STDOUT_FILENO;
|
||||
} else {
|
||||
fd = open(filename, O_CREAT|O_WRONLY, 0644);
|
||||
@@ -7193,7 +7265,7 @@ static void getRDB(clusterManagerNode *node) {
|
||||
}
|
||||
if (usemark) {
|
||||
payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
|
||||
if (ftruncate(fd, payload) == -1)
|
||||
if (!write_to_stdout && ftruncate(fd, payload) == -1)
|
||||
fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
|
||||
fprintf(stderr,"Transfer finished with success after %llu bytes\n", payload);
|
||||
} else {
|
||||
@@ -7202,7 +7274,7 @@ static void getRDB(clusterManagerNode *node) {
|
||||
redisFree(s); /* Close the connection ASAP as fsync() may take time. */
|
||||
if (node)
|
||||
node->context = NULL;
|
||||
if (fsync(fd) == -1) {
|
||||
if (!write_to_stdout && fsync(fd) == -1) {
|
||||
fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
@@ -8203,6 +8275,7 @@ int main(int argc, char **argv) {
|
||||
config.repeat = 1;
|
||||
config.interval = 0;
|
||||
config.dbnum = 0;
|
||||
config.input_dbnum = 0;
|
||||
config.interactive = 0;
|
||||
config.shutdown = 0;
|
||||
config.monitor_mode = 0;
|
||||
@@ -8213,6 +8286,7 @@ int main(int argc, char **argv) {
|
||||
config.lru_test_mode = 0;
|
||||
config.lru_test_sample_size = 0;
|
||||
config.cluster_mode = 0;
|
||||
config.cluster_send_asking = 0;
|
||||
config.slave_mode = 0;
|
||||
config.getrdb_mode = 0;
|
||||
config.stat_mode = 0;
|
||||
|
||||
@@ -836,6 +836,7 @@ REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ct
|
||||
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
@@ -1108,6 +1109,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(AuthenticateClientWithUser);
|
||||
REDISMODULE_GET_API(GetClientCertificate);
|
||||
REDISMODULE_GET_API(GetCommandKeys);
|
||||
REDISMODULE_GET_API(GetCurrentCommandName);
|
||||
REDISMODULE_GET_API(RegisterDefragFunc);
|
||||
REDISMODULE_GET_API(DefragAlloc);
|
||||
REDISMODULE_GET_API(DefragRedisModuleString);
|
||||
|
||||
+17
-12
@@ -377,6 +377,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
|
||||
}
|
||||
|
||||
void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc) {
|
||||
if (!(listLength(server.monitors) && !server.loading)) return;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int j;
|
||||
@@ -520,18 +521,12 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
|
||||
*
|
||||
* On success return C_OK, otherwise C_ERR is returned and we proceed
|
||||
* with the usual full resync. */
|
||||
int masterTryPartialResynchronization(client *c) {
|
||||
long long psync_offset, psync_len;
|
||||
int masterTryPartialResynchronization(client *c, long long psync_offset) {
|
||||
long long psync_len;
|
||||
char *master_replid = c->argv[1]->ptr;
|
||||
char buf[128];
|
||||
int buflen;
|
||||
|
||||
/* Parse the replication offset asked by the slave. Go to full sync
|
||||
* on parse error: this should never happen but we try to handle
|
||||
* it in a robust way compared to aborting. */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&psync_offset,NULL) !=
|
||||
C_OK) goto need_full_resync;
|
||||
|
||||
/* Is the replication ID of this master the same advertised by the wannabe
|
||||
* slave via PSYNC? If the replication ID changed this master has a
|
||||
* different replication history, and there is no way to continue.
|
||||
@@ -778,7 +773,14 @@ void syncCommand(client *c) {
|
||||
* So the slave knows the new replid and offset to try a PSYNC later
|
||||
* if the connection with the master is lost. */
|
||||
if (!strcasecmp(c->argv[0]->ptr,"psync")) {
|
||||
if (masterTryPartialResynchronization(c) == C_OK) {
|
||||
long long psync_offset;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[2], &psync_offset, NULL) != C_OK) {
|
||||
serverLog(LL_WARNING, "Replica %s asks for synchronization but with a wrong offset",
|
||||
replicationGetSlaveName(c));
|
||||
return;
|
||||
}
|
||||
|
||||
if (masterTryPartialResynchronization(c, psync_offset) == C_OK) {
|
||||
server.stat_sync_partial_ok++;
|
||||
return; /* No full resync needed, return. */
|
||||
} else {
|
||||
@@ -1303,6 +1305,9 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
/* Note: there's a chance we got here from within the REPLCONF ACK command
|
||||
* so we must avoid using freeClient, otherwise we'll crash on our way up. */
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = ln->value;
|
||||
@@ -1311,7 +1316,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
struct redis_stat buf;
|
||||
|
||||
if (bgsaveerr != C_OK) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
serverLog(LL_WARNING,"SYNC failed. BGSAVE child returned an error");
|
||||
continue;
|
||||
}
|
||||
@@ -1356,7 +1361,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
} else {
|
||||
if ((slave->repldbfd = open(server.rdb_filename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(slave->repldbfd,&buf) == -1) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
serverLog(LL_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
@@ -1368,7 +1373,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
|
||||
connSetWriteHandler(slave->conn,NULL);
|
||||
if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,9 @@ static size_t rioConnRead(rio *r, void *buf, size_t len) {
|
||||
int retval = connRead(r->io.conn.conn,
|
||||
(char*)r->io.conn.buf + sdslen(r->io.conn.buf),
|
||||
toread);
|
||||
if (retval <= 0) {
|
||||
if (retval == 0) {
|
||||
return 0;
|
||||
} else if (retval < 0) {
|
||||
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
+317
-108
@@ -31,6 +31,7 @@
|
||||
#include "sha1.h"
|
||||
#include "rand.h"
|
||||
#include "cluster.h"
|
||||
#include "monotonic.h"
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
@@ -38,6 +39,97 @@
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Globals that are added by the Lua libraries */
|
||||
static char *libraries_allow_list[] = {
|
||||
"string",
|
||||
"cjson",
|
||||
"bit",
|
||||
"cmsgpack",
|
||||
"math",
|
||||
"table",
|
||||
"struct",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Redis Lua API globals */
|
||||
static char *redis_api_allow_list[] = {
|
||||
"redis",
|
||||
"__redis__err__handler", /* error handler for eval, currently located on globals.
|
||||
Should move to registry. */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins */
|
||||
static char *lua_builtins_allow_list[] = {
|
||||
"xpcall",
|
||||
"tostring",
|
||||
"getfenv",
|
||||
"setmetatable",
|
||||
"next",
|
||||
"assert",
|
||||
"tonumber",
|
||||
"rawequal",
|
||||
"collectgarbage",
|
||||
"getmetatable",
|
||||
"rawset",
|
||||
"pcall",
|
||||
"coroutine",
|
||||
"type",
|
||||
"_G",
|
||||
"select",
|
||||
"unpack",
|
||||
"gcinfo",
|
||||
"pairs",
|
||||
"rawget",
|
||||
"loadstring",
|
||||
"ipairs",
|
||||
"_VERSION",
|
||||
"setfenv",
|
||||
"load",
|
||||
"error",
|
||||
"print", /* should be blocked, but kept in old releases to avoid breaking change. */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins which are not documented on the Lua documentation */
|
||||
static char *lua_builtins_not_documented_allow_list[] = {
|
||||
"newproxy",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins which are allowed on initialization but will be removed right after */
|
||||
static char *lua_builtins_removed_after_initialization_allow_list[] = {
|
||||
"debug", /* debug will be set to nil after the error handler will be created */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Those allow lists was created from the globals that was
|
||||
* available to the user when the allow lists was first introduce.
|
||||
* Because we do not want to break backward compatibility we keep
|
||||
* all the globals. The allow lists will prevent us from accidentally
|
||||
* creating unwanted globals in the future.
|
||||
*
|
||||
* Also notice that the allow list is only checked on start time,
|
||||
* after that the global table is locked so not need to check anything.*/
|
||||
static char **allow_lists[] = {
|
||||
libraries_allow_list,
|
||||
redis_api_allow_list,
|
||||
lua_builtins_allow_list,
|
||||
lua_builtins_not_documented_allow_list,
|
||||
lua_builtins_removed_after_initialization_allow_list,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Deny list contains elements which we know we do not want to add to globals
|
||||
* and there is no need to print a warning message form them. We will print a
|
||||
* log message only if an element was added to the globals and the element is
|
||||
* not on the allow list nor on the back list. */
|
||||
static char *deny_list[] = {
|
||||
"dofile",
|
||||
"loadfile",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
||||
@@ -128,6 +220,16 @@ void sha1hex(char *digest, char *script, size_t len) {
|
||||
*/
|
||||
|
||||
char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
|
||||
if (!lua_checkstack(lua, 5)) {
|
||||
/*
|
||||
* Increase the Lua stack if needed, to make sure there is enough room
|
||||
* to push 5 elements to the stack. On failure, exit with panic.
|
||||
* Notice that we need, in the worst case, 5 elements because redisProtocolToLuaType_Aggregate
|
||||
* might push 5 elements to the Lua stack.*/
|
||||
serverPanic("lua stack limit reach when parsing redis.call reply");
|
||||
}
|
||||
|
||||
char *p = reply;
|
||||
|
||||
switch(*p) {
|
||||
@@ -220,6 +322,11 @@ char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
|
||||
if (atype == '%') {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
} else {
|
||||
if (!lua_checkstack(lua, 1)) {
|
||||
/* Notice that here we need to check the stack again because the recursive
|
||||
* call to redisProtocolToLuaType might have use the room allocated in the stack */
|
||||
serverPanic("lua stack limit reach when parsing redis.call reply");
|
||||
}
|
||||
lua_pushboolean(lua,1);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
@@ -313,21 +420,7 @@ void luaSortArray(lua_State *lua) {
|
||||
lua_pushstring(lua,"sort");
|
||||
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
|
||||
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
|
||||
if (lua_pcall(lua,1,0,0)) {
|
||||
/* Stack: array, table, error */
|
||||
|
||||
/* We are not interested in the error, we assume that the problem is
|
||||
* that there are 'false' elements inside the array, so we try
|
||||
* again with a slower function but able to handle this case, that
|
||||
* is: table.sort(table, __redis__compare_helper) */
|
||||
lua_pop(lua,1); /* Stack: array, table */
|
||||
lua_pushstring(lua,"sort"); /* Stack: array, table, sort */
|
||||
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
|
||||
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
|
||||
lua_getglobal(lua,"__redis__compare_helper");
|
||||
/* Stack: array, table, table.sort, array, __redis__compare_helper */
|
||||
lua_call(lua,2,0);
|
||||
}
|
||||
lua_call(lua,1,0); /* Stack: array (sorted), table */
|
||||
/* Stack: array (sorted), table */
|
||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||
}
|
||||
@@ -339,6 +432,17 @@ void luaSortArray(lua_State *lua) {
|
||||
/* Reply to client 'c' converting the top element in the Lua stack to a
|
||||
* Redis reply. As a side effect the element is consumed from the stack. */
|
||||
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
if (!lua_checkstack(lua, 4)) {
|
||||
/* Increase the Lua stack if needed to make sure there is enough room
|
||||
* to push 4 elements to the stack. On failure, return error.
|
||||
* Notice that we need, in the worst case, 4 elements because returning a map might
|
||||
* require push 4 elements to the Lua stack.*/
|
||||
addReplyErrorFormat(c, "reached lua stack limit");
|
||||
lua_pop(lua,1); /* pop the element from the stack */
|
||||
return;
|
||||
}
|
||||
|
||||
int t = lua_type(lua,-1);
|
||||
|
||||
switch(t) {
|
||||
@@ -362,8 +466,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
* field. */
|
||||
|
||||
/* Handle error reply. */
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushstring(lua,"err");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TSTRING) {
|
||||
addReplyErrorFormat(c,"-%s",lua_tostring(lua,-1));
|
||||
@@ -374,7 +479,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle status reply. */
|
||||
lua_pushstring(lua,"ok");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TSTRING) {
|
||||
sds ok = sdsnew(lua_tostring(lua,-1));
|
||||
@@ -388,7 +493,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle double reply. */
|
||||
lua_pushstring(lua,"double");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNUMBER) {
|
||||
addReplyDouble(c,lua_tonumber(lua,-1));
|
||||
@@ -399,11 +504,12 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle map reply. */
|
||||
lua_pushstring(lua,"map");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TTABLE) {
|
||||
int maplen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, value */
|
||||
@@ -421,11 +527,12 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle set reply. */
|
||||
lua_pushstring(lua,"set");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TTABLE) {
|
||||
int setlen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, true */
|
||||
@@ -445,8 +552,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
while(1) {
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnumber(lua,j++);
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNIL) {
|
||||
lua_pop(lua,1);
|
||||
@@ -556,6 +664,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
return raise_error ? luaRaiseError(lua) : 1;
|
||||
}
|
||||
|
||||
/* Pop all arguments from the stack, we do not need them anymore
|
||||
* and this way we guaranty we will have room on the stack for the result. */
|
||||
lua_pop(lua, argc);
|
||||
|
||||
/* Setup our fake client for command execution */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
@@ -655,6 +767,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
goto cleanup;
|
||||
} else if (server.masterhost == NULL &&
|
||||
server.repl_min_slaves_max_lag &&
|
||||
server.repl_min_slaves_to_write &&
|
||||
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
|
||||
{
|
||||
luaPushError(lua, shared.noreplicaserr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,52 +1158,120 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Remove a functions that we don't want to expose to the Redis scripting
|
||||
* environment. */
|
||||
void luaRemoveUnsupportedFunctions(lua_State *lua) {
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"loadfile");
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"dofile");
|
||||
static int luaProtectedTableError(lua_State *lua) {
|
||||
int argc = lua_gettop(lua);
|
||||
if (argc != 2) {
|
||||
serverLog(LL_WARNING, "malicious code trying to call luaProtectedTableError with wrong arguments");
|
||||
luaL_error(lua, "Wrong number of arguments to luaProtectedTableError");
|
||||
}
|
||||
if (!lua_isstring(lua, -1) && !lua_isnumber(lua, -1)) {
|
||||
luaL_error(lua, "Second argument to luaProtectedTableError must be a string or number");
|
||||
}
|
||||
const char *variable_name = lua_tostring(lua, -1);
|
||||
luaL_error(lua, "Script attempted to access nonexistent global variable '%s'", variable_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function installs metamethods in the global table _G that prevent
|
||||
* the creation of globals accidentally.
|
||||
/* Set a special metatable on the table on the top of the stack.
|
||||
* The metatable will raise an error if the user tries to fetch
|
||||
* an un-existing value.
|
||||
*
|
||||
* It should be the last to be called in the scripting engine initialization
|
||||
* sequence, because it may interact with creation of globals. */
|
||||
void scriptingEnableGlobalsProtection(lua_State *lua) {
|
||||
char *s[32];
|
||||
sds code = sdsempty();
|
||||
int j = 0;
|
||||
* The function assumes the Lua stack have a least enough
|
||||
* space to push 2 element, its up to the caller to verify
|
||||
* this before calling this function. */
|
||||
void luaSetErrorMetatable(lua_State *lua) {
|
||||
lua_newtable(lua); /* push metatable */
|
||||
lua_pushcfunction(lua, luaProtectedTableError); /* push get error handler */
|
||||
lua_setfield(lua, -2, "__index");
|
||||
lua_setmetatable(lua, -2);
|
||||
}
|
||||
|
||||
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
|
||||
* Modified to be adapted to Redis. */
|
||||
s[j++]="local dbg=debug\n";
|
||||
s[j++]="local mt = {}\n";
|
||||
s[j++]="setmetatable(_G, mt)\n";
|
||||
s[j++]="mt.__newindex = function (t, n, v)\n";
|
||||
s[j++]=" if dbg.getinfo(2) then\n";
|
||||
s[j++]=" local w = dbg.getinfo(2, \"S\").what\n";
|
||||
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" rawset(t, n, v)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="mt.__index = function (t, n)\n";
|
||||
s[j++]=" if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to access nonexistent global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" return rawget(t, n)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="debug = nil\n";
|
||||
s[j++]=NULL;
|
||||
/* Set the readonly flag on the table located on the top of the stack
|
||||
* and recursively call this function on each table located on the original
|
||||
* table. Also, recursively call this function on the metatables.*/
|
||||
void luaSetTableProtectionRecursively(lua_State *lua) {
|
||||
/* This protect us from a loop in case we already visited the table
|
||||
* For example, globals has '_G' key which is pointing back to globals. */
|
||||
if (lua_isreadonlytable(lua, -1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
|
||||
luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua");
|
||||
lua_pcall(lua,0,0,0);
|
||||
sdsfree(code);
|
||||
/* protect the current table */
|
||||
lua_enablereadonlytable(lua, -1, 1);
|
||||
|
||||
lua_checkstack(lua, 2);
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, value */
|
||||
if (lua_istable(lua, -1)) {
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
}
|
||||
lua_pop(lua, 1);
|
||||
}
|
||||
|
||||
/* protect the metatable if exists */
|
||||
if (lua_getmetatable(lua, -1)) {
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
lua_pop(lua, 1); /* pop the metatable */
|
||||
}
|
||||
}
|
||||
|
||||
static int luaNewIndexAllowList(lua_State *lua) {
|
||||
int argc = lua_gettop(lua);
|
||||
if (argc != 3) {
|
||||
serverLog(LL_WARNING, "malicious code trying to call luaNewIndexAllowList with wrong arguments");
|
||||
luaL_error(lua, "Wrong number of arguments to luaNewIndexAllowList");
|
||||
}
|
||||
if (!lua_istable(lua, -3)) {
|
||||
luaL_error(lua, "first argument to luaNewIndexAllowList must be a table");
|
||||
}
|
||||
if (!lua_isstring(lua, -2) && !lua_isnumber(lua, -2)) {
|
||||
luaL_error(lua, "Second argument to luaNewIndexAllowList must be a string or number");
|
||||
}
|
||||
const char *variable_name = lua_tostring(lua, -2);
|
||||
/* check if the key is in our allow list */
|
||||
|
||||
char ***allow_l = allow_lists;
|
||||
for (; *allow_l ; ++allow_l){
|
||||
char **c = *allow_l;
|
||||
for (; *c ; ++c) {
|
||||
if (strcmp(*c, variable_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!*allow_l) {
|
||||
/* Search the value on the back list, if its there we know that it was removed
|
||||
* on purpose and there is no need to print a warning. */
|
||||
char **c = deny_list;
|
||||
for ( ; *c ; ++c) {
|
||||
if (strcmp(*c, variable_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!*c) {
|
||||
serverLog(LL_WARNING, "A key '%s' was added to Lua globals which is not on the globals allow list nor listed on the deny list.", variable_name);
|
||||
}
|
||||
} else {
|
||||
lua_rawset(lua, -3);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set a metatable with '__newindex' function that verify that
|
||||
* the new index appears on our globals while list.
|
||||
*
|
||||
* The metatable is set on the table which located on the top
|
||||
* of the stack.
|
||||
*/
|
||||
void luaSetAllowListProtection(lua_State *lua) {
|
||||
lua_newtable(lua); /* push metatable */
|
||||
lua_pushcfunction(lua, luaNewIndexAllowList); /* push get error handler */
|
||||
lua_setfield(lua, -2, "__newindex");
|
||||
lua_setmetatable(lua, -2);
|
||||
}
|
||||
|
||||
/* Initialize the scripting environment.
|
||||
@@ -1108,8 +1295,11 @@ void scriptingInit(int setup) {
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
lua_pushvalue(lua, LUA_GLOBALSINDEX);
|
||||
luaSetAllowListProtection(lua);
|
||||
lua_pop(lua, 1);
|
||||
|
||||
luaLoadLibraries(lua);
|
||||
luaRemoveUnsupportedFunctions(lua);
|
||||
|
||||
/* Initialize a dictionary we use to map SHAs to scripts.
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
@@ -1225,24 +1415,13 @@ void scriptingInit(int setup) {
|
||||
|
||||
lua_setglobal(lua,"math");
|
||||
|
||||
/* Add a helper function that we use to sort the multi bulk output of non
|
||||
* deterministic commands, when containing 'false' elements. */
|
||||
{
|
||||
char *compare_func = "function __redis__compare_helper(a,b)\n"
|
||||
" if a == false then a = '' end\n"
|
||||
" if b == false then b = '' end\n"
|
||||
" return a<b\n"
|
||||
"end\n";
|
||||
luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def");
|
||||
lua_pcall(lua,0,0,0);
|
||||
}
|
||||
|
||||
/* Add a helper function we use for pcall error reporting.
|
||||
* Note that when the error is in the C function we want to report the
|
||||
* information about the caller, that's what makes sense from the point
|
||||
* of view of the user debugging a script. */
|
||||
{
|
||||
char *errh_func = "local dbg = debug\n"
|
||||
"debug = nil\n"
|
||||
"function __redis__err__handler(err)\n"
|
||||
" local i = dbg.getinfo(2,'nSl')\n"
|
||||
" if i and i.what == 'C' then\n"
|
||||
@@ -1270,10 +1449,12 @@ void scriptingInit(int setup) {
|
||||
server.lua_client->flags |= CLIENT_DENY_BLOCKING;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* Lock the global table from any changes */
|
||||
lua_pushvalue(lua, LUA_GLOBALSINDEX);
|
||||
luaSetErrorMetatable(lua);
|
||||
/* Recursively lock all tables that can be reached from the global table */
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
lua_pop(lua, 1);
|
||||
|
||||
server.lua = lua;
|
||||
}
|
||||
@@ -1286,6 +1467,7 @@ void scriptingRelease(int async) {
|
||||
else
|
||||
dictRelease(server.lua_scripts);
|
||||
server.lua_scripts_mem = 0;
|
||||
lua_gc(server.lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(server.lua);
|
||||
}
|
||||
|
||||
@@ -1304,7 +1486,9 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
||||
lua_pushlstring(lua,(char*)elev[j]->ptr,sdslen(elev[j]->ptr));
|
||||
lua_rawseti(lua,-2,j+1);
|
||||
}
|
||||
lua_enablereadonlytable(lua, LUA_GLOBALSINDEX, 0);
|
||||
lua_setglobal(lua,var);
|
||||
lua_enablereadonlytable(lua, LUA_GLOBALSINDEX, 1);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
@@ -1385,14 +1569,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
return dictGetKey(de);
|
||||
}
|
||||
|
||||
sds funcdef = sdsempty();
|
||||
funcdef = sdscat(funcdef,"function ");
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
if (luaL_loadbuffer(lua,body->ptr,sdslen(body->ptr),"@user_script")) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,
|
||||
"Error compiling script (new function): %s\n",
|
||||
@@ -1400,20 +1577,12 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
sdsfree(funcdef);
|
||||
return NULL;
|
||||
}
|
||||
sdsfree(funcdef);
|
||||
|
||||
if (lua_pcall(lua,0,0,0)) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
return NULL;
|
||||
}
|
||||
serverAssert(lua_isfunction(lua, -1));
|
||||
|
||||
lua_setfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
|
||||
/* We also save a SHA1 -> Original script map in a dictionary
|
||||
* so that we can replicate / write in the AOF all the
|
||||
@@ -1427,7 +1596,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
|
||||
/* 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 = elapsedMs(server.lua_time_start);
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
@@ -1541,7 +1710,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_getglobal(lua, "__redis__err__handler");
|
||||
|
||||
/* Try to lookup the Lua function */
|
||||
lua_getglobal(lua, funcname);
|
||||
lua_getfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
if (lua_isnil(lua,-1)) {
|
||||
lua_pop(lua,1); /* remove the nil from the stack */
|
||||
/* Function not defined... let's define it if we have the
|
||||
@@ -1559,7 +1728,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
return;
|
||||
}
|
||||
/* Now the following is guaranteed to return non nil */
|
||||
lua_getglobal(lua, funcname);
|
||||
lua_getfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
serverAssert(!lua_isnil(lua,-1));
|
||||
}
|
||||
|
||||
@@ -1578,7 +1747,8 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
server.in_eval = 1;
|
||||
server.lua_caller = c;
|
||||
server.lua_cur_script = funcname + 2;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_time_start = getMonotonicUs();
|
||||
server.lua_time_snapshot = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
@@ -1688,6 +1858,9 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
}
|
||||
|
||||
void evalCommand(client *c) {
|
||||
/* Explicitly feed monitor here so that lua commands appear after their
|
||||
* script command. */
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
if (!(c->flags & CLIENT_LUA_DEBUG))
|
||||
evalGenericCommand(c,0);
|
||||
else
|
||||
@@ -1695,6 +1868,9 @@ void evalCommand(client *c) {
|
||||
}
|
||||
|
||||
void evalShaCommand(client *c) {
|
||||
/* Explicitly feed monitor here so that lua commands appear after their
|
||||
* script command. */
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
if (sdslen(c->argv[1]->ptr) != 40) {
|
||||
/* We know that a match is not possible if the provided SHA is
|
||||
* not the right length. So we return an error ASAP, this way
|
||||
@@ -2073,7 +2249,8 @@ int ldbDelBreakpoint(int line) {
|
||||
/* Expect a valid multi-bulk command in the debugging client query buffer.
|
||||
* On success the command is parsed and returned as an array of SDS strings,
|
||||
* otherwise NULL is returned and there is to read more buffer. */
|
||||
sds *ldbReplParseCommand(int *argcp) {
|
||||
sds *ldbReplParseCommand(int *argcp, char** err) {
|
||||
static char* protocol_error = "protocol error";
|
||||
sds *argv = NULL;
|
||||
int argc = 0;
|
||||
if (sdslen(ldb.cbuf) == 0) return NULL;
|
||||
@@ -2090,7 +2267,7 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
/* Seek and parse *<count>\r\n. */
|
||||
p = strchr(p,'*'); if (!p) goto protoerr;
|
||||
char *plen = p+1; /* Multi bulk len pointer. */
|
||||
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||
p = strstr(p,"\r\n"); if (!p) goto keep_reading;
|
||||
*p = '\0'; p += 2;
|
||||
*argcp = atoi(plen);
|
||||
if (*argcp <= 0 || *argcp > 1024) goto protoerr;
|
||||
@@ -2099,12 +2276,16 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
argv = zmalloc(sizeof(sds)*(*argcp));
|
||||
argc = 0;
|
||||
while(argc < *argcp) {
|
||||
/* reached the end but there should be more data to read */
|
||||
if (*p == '\0') goto keep_reading;
|
||||
|
||||
if (*p != '$') goto protoerr;
|
||||
plen = p+1; /* Bulk string len pointer. */
|
||||
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||
p = strstr(p,"\r\n"); if (!p) goto keep_reading;
|
||||
*p = '\0'; p += 2;
|
||||
int slen = atoi(plen); /* Length of this arg. */
|
||||
if (slen <= 0 || slen > 1024) goto protoerr;
|
||||
if ((size_t)(p + slen + 2 - copy) > sdslen(copy) ) goto keep_reading;
|
||||
argv[argc++] = sdsnewlen(p,slen);
|
||||
p += slen; /* Skip the already parsed argument. */
|
||||
if (p[0] != '\r' || p[1] != '\n') goto protoerr;
|
||||
@@ -2114,6 +2295,8 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
return argv;
|
||||
|
||||
protoerr:
|
||||
*err = protocol_error;
|
||||
keep_reading:
|
||||
sdsfreesplitres(argv,argc);
|
||||
sdsfree(copy);
|
||||
return NULL;
|
||||
@@ -2546,6 +2729,17 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
void ldbRedis(lua_State *lua, sds *argv, int argc) {
|
||||
int j, saved_rc = server.lua_replicate_commands;
|
||||
|
||||
if (!lua_checkstack(lua, argc + 1)) {
|
||||
/* Increase the Lua stack if needed to make sure there is enough room
|
||||
* to push 'argc + 1' elements to the stack. On failure, return error.
|
||||
* Notice that we need, in worst case, 'argc + 1' elements because we push all the arguments
|
||||
* given by the user (without the first argument) and we also push the 'redis' global table and
|
||||
* 'redis.call' function so:
|
||||
* (1 (redis table)) + (1 (redis.call function)) + (argc - 1 (all arguments without the first)) = argc + 1*/
|
||||
ldbLogRedisReply("max lua stack reached");
|
||||
return;
|
||||
}
|
||||
|
||||
lua_getglobal(lua,"redis");
|
||||
lua_pushstring(lua,"call");
|
||||
lua_gettable(lua,-2); /* Stack: redis, redis.call */
|
||||
@@ -2602,12 +2796,17 @@ void ldbMaxlen(sds *argv, int argc) {
|
||||
int ldbRepl(lua_State *lua) {
|
||||
sds *argv;
|
||||
int argc;
|
||||
char* err = NULL;
|
||||
|
||||
/* We continue processing commands until a command that should return
|
||||
* to the Lua interpreter is found. */
|
||||
while(1) {
|
||||
while((argv = ldbReplParseCommand(&argc)) == NULL) {
|
||||
while((argv = ldbReplParseCommand(&argc, &err)) == NULL) {
|
||||
char buf[1024];
|
||||
if (err) {
|
||||
lua_pushstring(lua, err);
|
||||
lua_error(lua);
|
||||
}
|
||||
int nread = connRead(ldb.conn,buf,sizeof(buf));
|
||||
if (nread <= 0) {
|
||||
/* Make sure the script runs without user input since the
|
||||
@@ -2617,6 +2816,15 @@ int ldbRepl(lua_State *lua) {
|
||||
return C_ERR;
|
||||
}
|
||||
ldb.cbuf = sdscatlen(ldb.cbuf,buf,nread);
|
||||
/* after 1M we will exit with an error
|
||||
* so that the client will not blow the memory
|
||||
*/
|
||||
if (sdslen(ldb.cbuf) > 1<<20) {
|
||||
sdsfree(ldb.cbuf);
|
||||
ldb.cbuf = sdsempty();
|
||||
lua_pushstring(lua, "max client buffer reached");
|
||||
lua_error(lua);
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the old buffer. */
|
||||
@@ -2729,7 +2937,7 @@ 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 elapsed = elapsedMs(server.lua_time_start);
|
||||
mstime_t timelimit = server.lua_time_limit ?
|
||||
server.lua_time_limit : 5000;
|
||||
if (elapsed >= timelimit) {
|
||||
@@ -2759,6 +2967,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();
|
||||
server.lua_time_start = getMonotonicUs();
|
||||
server.lua_time_snapshot = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ void sdsclear(sds s) {
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
void *sh, *newsh;
|
||||
size_t avail = sdsavail(s);
|
||||
size_t len, newlen;
|
||||
size_t len, newlen, reqlen;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen;
|
||||
size_t usable;
|
||||
@@ -243,7 +243,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
|
||||
len = sdslen(s);
|
||||
sh = (char*)s-sdsHdrSize(oldtype);
|
||||
newlen = (len+addlen);
|
||||
reqlen = newlen = (len+addlen);
|
||||
assert(newlen > len); /* Catch size_t overflow */
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
@@ -258,7 +258,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
|
||||
hdrlen = sdsHdrSize(type);
|
||||
assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
|
||||
assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
|
||||
if (newsh == NULL) return NULL;
|
||||
@@ -287,40 +287,72 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdsRemoveFreeSpace(sds s) {
|
||||
sds sdsRemoveFreeSpace(sds s, int would_regrow) {
|
||||
return sdsResize(s, sdslen(s), would_regrow);
|
||||
}
|
||||
|
||||
/* Resize the allocation, this can make the allocation bigger or smaller,
|
||||
* if the size is smaller than currently used len, the data will be truncated.
|
||||
*
|
||||
* The when the would_regrow argument is set to 1, it prevents the use of
|
||||
* SDS_TYPE_5, which is desired when the sds is likely to be changed again.
|
||||
*
|
||||
* The sdsAlloc size will be set to the requested size regardless of the actual
|
||||
* allocation size, this is done in order to avoid repeated calls to this
|
||||
* function when the caller detects that it has excess space. */
|
||||
sds sdsResize(sds s, size_t size, int would_regrow) {
|
||||
void *sh, *newsh;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
/* Return ASAP if the size is already good. */
|
||||
if (sdsalloc(s) == size) return s;
|
||||
|
||||
/* Truncate len if needed. */
|
||||
if (size < len) len = size;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
type = sdsReqType(size);
|
||||
if (would_regrow) {
|
||||
/* Don't use type 5, it is not good for strings that are expected to grow back. */
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
}
|
||||
hdrlen = sdsHdrSize(type);
|
||||
|
||||
/* If the type is the same, or at least a large enough type is still
|
||||
* required, we just realloc(), letting the allocator to do the copy
|
||||
* only if really needed. Otherwise if the change is huge, we manually
|
||||
* reallocate the string to use the different header type. */
|
||||
if (oldtype==type || type > SDS_TYPE_8) {
|
||||
newsh = s_realloc(sh, oldhdrlen+len+1);
|
||||
/* If the type is the same, or can hold the size in it with low overhead
|
||||
* (larger than SDS_TYPE_8), we just realloc(), letting the allocator
|
||||
* to do the copy only if really needed. Otherwise if the change is
|
||||
* huge, we manually reallocate the string to use the different header
|
||||
* type. */
|
||||
int use_realloc = (oldtype==type || (type < oldtype && type > SDS_TYPE_8));
|
||||
size_t newlen = use_realloc ? oldhdrlen+size+1 : hdrlen+size+1;
|
||||
int alloc_already_optimal = 0;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* je_nallocx returns the expected allocation size for the newlen.
|
||||
* We aim to avoid calling realloc() when using Jemalloc if there is no
|
||||
* change in the allocation size, as it incurs a cost even if the
|
||||
* allocation size stays the same. */
|
||||
alloc_already_optimal = (je_nallocx(newlen, 0) == zmalloc_size(sh));
|
||||
#endif
|
||||
|
||||
if (use_realloc && !alloc_already_optimal) {
|
||||
newsh = s_realloc(sh, newlen);
|
||||
if (newsh == NULL) return NULL;
|
||||
s = (char*)newsh+oldhdrlen;
|
||||
} else {
|
||||
newsh = s_malloc(hdrlen+len+1);
|
||||
} else if (!alloc_already_optimal) {
|
||||
newsh = s_malloc(newlen);
|
||||
if (newsh == NULL) return NULL;
|
||||
memcpy((char*)newsh+hdrlen, s, len+1);
|
||||
memcpy((char*)newsh+hdrlen, s, len);
|
||||
s_free(sh);
|
||||
s = (char*)newsh+hdrlen;
|
||||
s[-1] = type;
|
||||
sdssetlen(s, len);
|
||||
}
|
||||
sdssetalloc(s, len);
|
||||
s[len] = 0;
|
||||
sdssetlen(s, len);
|
||||
sdssetalloc(s, size);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -759,6 +791,21 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Changes the input string to be a subset of the original.
|
||||
* It does not release the free space in the string, so a call to
|
||||
* sdsRemoveFreeSpace may be wise after. */
|
||||
void sdssubstr(sds s, size_t start, size_t len) {
|
||||
/* Clamp out of range input */
|
||||
size_t oldlen = sdslen(s);
|
||||
if (start >= oldlen) start = len = 0;
|
||||
if (len > oldlen-start) len = oldlen-start;
|
||||
|
||||
/* Move the data */
|
||||
if (len) memmove(s, s+start, len);
|
||||
s[len] = 0;
|
||||
sdssetlen(s,len);
|
||||
}
|
||||
|
||||
/* Turn the string into a smaller (or equal) string containing only the
|
||||
* substring specified by the 'start' and 'end' indexes.
|
||||
*
|
||||
@@ -770,6 +817,11 @@ sds sdstrim(sds s, const char *cset) {
|
||||
*
|
||||
* The string is modified in-place.
|
||||
*
|
||||
* NOTE: this function can be misleading and can have unexpected behaviour,
|
||||
* specifically when you want the length of the new string to be 0.
|
||||
* Having start==end will result in a string with one character.
|
||||
* please consider using sdssubstr instead.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
@@ -777,28 +829,13 @@ sds sdstrim(sds s, const char *cset) {
|
||||
*/
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = len+end;
|
||||
if (end < 0) end = 0;
|
||||
}
|
||||
if (start < 0)
|
||||
start = len + start;
|
||||
if (end < 0)
|
||||
end = len + end;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
if (newlen != 0) {
|
||||
if (start >= (ssize_t)len) {
|
||||
newlen = 0;
|
||||
} else if (end >= (ssize_t)len) {
|
||||
end = len-1;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
}
|
||||
}
|
||||
if (start && newlen) memmove(s, s+start, newlen);
|
||||
s[newlen] = 0;
|
||||
sdssetlen(s,newlen);
|
||||
sdssubstr(s, start, newlen);
|
||||
}
|
||||
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
@@ -1353,6 +1390,18 @@ int sdsTest(int argc, char **argv, int accurate) {
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,4,6);
|
||||
test_cond("sdsrange(...,4,6)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,3,6);
|
||||
test_cond("sdsrange(...,3,6)",
|
||||
sdslen(y) == 1 && memcmp(y,"o\0",2) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnew("foo");
|
||||
@@ -1438,6 +1487,34 @@ int sdsTest(int argc, char **argv, int accurate) {
|
||||
test_cond("sdstemplate() with quoting",
|
||||
memcmp(x,"v1={value1} {} v2=value2",24) == 0);
|
||||
sdsfree(x);
|
||||
|
||||
/* Test sdsresize - extend */
|
||||
x = sdsnew("1234567890123456789012345678901234567890");
|
||||
x = sdsResize(x, 200, 1);
|
||||
test_cond("sdsrezie() expand len", sdslen(x) == 40);
|
||||
test_cond("sdsrezie() expand strlen", strlen(x) == 40);
|
||||
test_cond("sdsrezie() expand alloc", sdsalloc(x) == 200);
|
||||
/* Test sdsresize - trim free space */
|
||||
x = sdsResize(x, 80, 1);
|
||||
test_cond("sdsrezie() shrink len", sdslen(x) == 40);
|
||||
test_cond("sdsrezie() shrink strlen", strlen(x) == 40);
|
||||
test_cond("sdsrezie() shrink alloc", sdsalloc(x) == 80);
|
||||
/* Test sdsresize - crop used space */
|
||||
x = sdsResize(x, 30, 1);
|
||||
test_cond("sdsrezie() crop len", sdslen(x) == 30);
|
||||
test_cond("sdsrezie() crop strlen", strlen(x) == 30);
|
||||
test_cond("sdsrezie() crop alloc", sdsalloc(x) == 30);
|
||||
/* Test sdsresize - extend to different class */
|
||||
x = sdsResize(x, 400, 1);
|
||||
test_cond("sdsrezie() expand len", sdslen(x) == 30);
|
||||
test_cond("sdsrezie() expand strlen", strlen(x) == 30);
|
||||
test_cond("sdsrezie() expand alloc", sdsalloc(x) == 400);
|
||||
/* Test sdsresize - shrink to different class */
|
||||
x = sdsResize(x, 4, 1);
|
||||
test_cond("sdsrezie() crop len", sdslen(x) == 4);
|
||||
test_cond("sdsrezie() crop strlen", strlen(x) == 4);
|
||||
test_cond("sdsrezie() crop alloc", sdsalloc(x) == 4);
|
||||
sdsfree(x);
|
||||
}
|
||||
test_report();
|
||||
return 0;
|
||||
|
||||
@@ -238,6 +238,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdssubstr(sds s, size_t start, size_t len);
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
@@ -264,7 +265,8 @@ sds sdstemplate(const char *template, sdstemplate_callback_t cb_func, void *cb_a
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
void sdsIncrLen(sds s, ssize_t incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
sds sdsRemoveFreeSpace(sds s, int would_regrow);
|
||||
sds sdsResize(sds s, size_t size, int would_regrow);
|
||||
size_t sdsAllocSize(sds s);
|
||||
void *sdsAllocPtr(sds s);
|
||||
|
||||
|
||||
+73
-33
@@ -593,10 +593,10 @@ void sentinelIsRunning(void) {
|
||||
|
||||
/* Create a sentinelAddr object and return it on success.
|
||||
* On error NULL is returned and errno is set to:
|
||||
* ENOENT: Can't resolve the hostname.
|
||||
* ENOENT: Can't resolve the hostname, unless accept_unresolved is non-zero.
|
||||
* EINVAL: Invalid port number.
|
||||
*/
|
||||
sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
sentinelAddr *createSentinelAddr(char *hostname, int port, int is_accept_unresolved) {
|
||||
char ip[NET_IP_STR_LEN];
|
||||
sentinelAddr *sa;
|
||||
|
||||
@@ -606,8 +606,14 @@ sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
}
|
||||
if (anetResolve(NULL,hostname,ip,sizeof(ip),
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
serverLog(LL_WARNING, "Failed to resolve hostname '%s'", hostname);
|
||||
if (sentinel.resolve_hostnames && is_accept_unresolved) {
|
||||
ip[0] = '\0';
|
||||
}
|
||||
else {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
sa = zmalloc(sizeof(*sa));
|
||||
sa->hostname = sdsnew(hostname);
|
||||
@@ -634,19 +640,29 @@ void releaseSentinelAddr(sentinelAddr *sa) {
|
||||
zfree(sa);
|
||||
}
|
||||
|
||||
/* Return non-zero if two addresses are equal. */
|
||||
int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
return a->port == b->port && !strcasecmp(a->ip,b->ip);
|
||||
/* Return non-zero if the two addresses are equal, either by address
|
||||
* or by hostname if they could not have been resolved.
|
||||
*/
|
||||
int sentinelAddrOrHostnameEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
return a->port == b->port &&
|
||||
(!strcmp(a->ip, b->ip) ||
|
||||
!strcasecmp(a->hostname, b->hostname));
|
||||
}
|
||||
|
||||
/* Return non-zero if a hostname matches an address. */
|
||||
int sentinelAddrEqualsHostname(sentinelAddr *a, char *hostname) {
|
||||
char ip[NET_IP_STR_LEN];
|
||||
|
||||
/* We always resolve the hostname and compare it to the address */
|
||||
/* Try resolve the hostname and compare it to the address */
|
||||
if (anetResolve(NULL, hostname, ip, sizeof(ip),
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR)
|
||||
return 0;
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {
|
||||
|
||||
/* If failed resolve then compare based on hostnames. That is our best effort as
|
||||
* long as the server is unavailable for some reason. It is fine since Redis
|
||||
* instance cannot have multiple hostnames for a given setup */
|
||||
return !strcasecmp(sentinel.resolve_hostnames ? a->hostname : a->ip, hostname);
|
||||
}
|
||||
/* Compare based on address */
|
||||
return !strcasecmp(a->ip, ip);
|
||||
}
|
||||
|
||||
@@ -1296,7 +1312,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
serverAssert((flags & SRI_MASTER) || master != NULL);
|
||||
|
||||
/* Check address validity. */
|
||||
addr = createSentinelAddr(hostname,port);
|
||||
addr = createSentinelAddr(hostname,port,1);
|
||||
if (addr == NULL) return NULL;
|
||||
|
||||
/* For slaves use ip/host:port as name. */
|
||||
@@ -1424,7 +1440,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
* If that is the case, depending on configuration we either resolve
|
||||
* it and use the IP addres or fail.
|
||||
*/
|
||||
addr = createSentinelAddr(slave_addr, port);
|
||||
addr = createSentinelAddr(slave_addr, port, 0);
|
||||
if (!addr) return NULL;
|
||||
key = announceSentinelAddrAndPort(addr);
|
||||
releaseSentinelAddr(addr);
|
||||
@@ -1487,8 +1503,10 @@ sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, c
|
||||
|
||||
serverAssert(addr || runid); /* User must pass at least one search param. */
|
||||
if (addr != NULL) {
|
||||
/* Resolve addr, we use the IP as a key even if a hostname is used */
|
||||
ri_addr = createSentinelAddr(addr, port);
|
||||
/* Try to resolve addr. If hostnames are used, we're accepting an ri_addr
|
||||
* that contains an hostname only and can still be matched based on that.
|
||||
*/
|
||||
ri_addr = createSentinelAddr(addr,port,1);
|
||||
if (!ri_addr) return NULL;
|
||||
}
|
||||
di = dictGetIterator(instances);
|
||||
@@ -1497,8 +1515,7 @@ sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, c
|
||||
|
||||
if (runid && !ri->runid) continue;
|
||||
if ((runid == NULL || strcmp(ri->runid, runid) == 0) &&
|
||||
(addr == NULL || (strcmp(ri->addr->ip, ri_addr->ip) == 0 &&
|
||||
ri->addr->port == port)))
|
||||
(addr == NULL || sentinelAddrOrHostnameEqual(ri->addr, ri_addr)))
|
||||
{
|
||||
instance = ri;
|
||||
break;
|
||||
@@ -1629,7 +1646,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
newaddr = createSentinelAddr(hostname,port);
|
||||
newaddr = createSentinelAddr(hostname,port,0);
|
||||
if (newaddr == NULL) return C_ERR;
|
||||
|
||||
/* There can be only 0 or 1 slave that has the newaddr.
|
||||
@@ -1642,7 +1659,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
|
||||
if (sentinelAddrIsEqual(slave->addr,newaddr)) continue;
|
||||
if (sentinelAddrOrHostnameEqual(slave->addr,newaddr)) continue;
|
||||
slaves[numslaves++] = dupSentinelAddr(slave->addr);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
@@ -1650,7 +1667,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
/* If we are switching to a different address, include the old address
|
||||
* as a slave as well, so that we'll be able to sense / reconfigure
|
||||
* the old master. */
|
||||
if (!sentinelAddrIsEqual(newaddr,master->addr)) {
|
||||
if (!sentinelAddrOrHostnameEqual(newaddr,master->addr)) {
|
||||
slaves[numslaves++] = dupSentinelAddr(master->addr);
|
||||
}
|
||||
|
||||
@@ -2199,7 +2216,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
* slave's address, a failover is in progress and the slave was
|
||||
* already successfully promoted. So as the address of this slave
|
||||
* we use the old master address instead. */
|
||||
if (sentinelAddrIsEqual(slave_addr,master_addr))
|
||||
if (sentinelAddrOrHostnameEqual(slave_addr,master_addr))
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-replica %s %s %d",
|
||||
@@ -2402,7 +2419,10 @@ static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
|
||||
SSL *ssl = SSL_new(redis_tls_client_ctx ? redis_tls_client_ctx : redis_tls_ctx);
|
||||
if (!ssl) return C_ERR;
|
||||
|
||||
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
|
||||
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) {
|
||||
SSL_free(ssl);
|
||||
return C_ERR;
|
||||
}
|
||||
#endif
|
||||
return C_OK;
|
||||
}
|
||||
@@ -2411,6 +2431,7 @@ static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
|
||||
* is disconnected. Note that link->disconnected is true even if just
|
||||
* one of the two links (commands and pub/sub) is missing. */
|
||||
void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
|
||||
if (ri->link->disconnected == 0) return;
|
||||
if (ri->addr->port == 0) return; /* port == 0 means invalid address. */
|
||||
instanceLink *link = ri->link;
|
||||
@@ -2421,7 +2442,21 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
|
||||
/* Commands connection. */
|
||||
if (link->cc == NULL) {
|
||||
|
||||
/* It might be that the instance is disconnected because it wasn't available earlier when the instance
|
||||
* allocated, say during failover, and therefore we failed to resolve its ip.
|
||||
* Another scenario is that the instance restarted with new ip, and we should resolve its new ip based on
|
||||
* its hostname */
|
||||
if (sentinel.resolve_hostnames) {
|
||||
sentinelAddr *tryResolveAddr = createSentinelAddr(ri->addr->hostname, ri->addr->port, 0);
|
||||
if (tryResolveAddr != NULL) {
|
||||
releaseSentinelAddr(ri->addr);
|
||||
ri->addr = tryResolveAddr;
|
||||
}
|
||||
}
|
||||
|
||||
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
||||
|
||||
if (link->cc && !link->cc->err) anetCloexec(link->cc->c.fd);
|
||||
if (!link->cc) {
|
||||
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to establish connection");
|
||||
@@ -3175,11 +3210,13 @@ void sentinelConfigSetCommand(client *c) {
|
||||
sentinel.announce_port = numval;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-user")) {
|
||||
sdsfree(sentinel.sentinel_auth_user);
|
||||
sentinel.sentinel_auth_user = sdsnew(val->ptr);
|
||||
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-pass")) {
|
||||
sdsfree(sentinel.sentinel_auth_pass);
|
||||
sentinel.sentinel_auth_pass = sdsnew(val->ptr);
|
||||
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET",
|
||||
@@ -3682,11 +3719,11 @@ NULL
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2])) == NULL)
|
||||
return;
|
||||
if (ri->flags & SRI_FAILOVER_IN_PROGRESS) {
|
||||
addReplySds(c,sdsnew("-INPROG Failover already in progress\r\n"));
|
||||
addReplyError(c,"-INPROG Failover already in progress");
|
||||
return;
|
||||
}
|
||||
if (sentinelSelectSlave(ri) == NULL) {
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable replica to promote\r\n"));
|
||||
addReplyError(c,"-NOGOODSLAVE No suitable replica to promote");
|
||||
return;
|
||||
}
|
||||
serverLog(LL_WARNING,"Executing user requested FAILOVER of '%s'",
|
||||
@@ -3775,8 +3812,7 @@ NULL
|
||||
e = sdscat(e, "Not enough available Sentinels to reach the"
|
||||
" majority and authorize a failover");
|
||||
}
|
||||
e = sdscat(e,"\r\n");
|
||||
addReplySds(c,e);
|
||||
addReplyErrorSds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3) goto numargserr;
|
||||
@@ -3989,6 +4025,7 @@ void sentinelSetCommand(client *c) {
|
||||
int j, changes = 0;
|
||||
int badarg = 0; /* Bad argument position for error reporting. */
|
||||
char *option;
|
||||
int redacted;
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
@@ -3999,6 +4036,7 @@ void sentinelSetCommand(client *c) {
|
||||
option = c->argv[j]->ptr;
|
||||
long long ll;
|
||||
int old_j = j; /* Used to know what to log as an event. */
|
||||
redacted = 0;
|
||||
|
||||
if (!strcasecmp(option,"down-after-milliseconds") && moreargs > 0) {
|
||||
/* down-after-millisecodns <milliseconds> */
|
||||
@@ -4073,6 +4111,7 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
redacted = 1;
|
||||
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
|
||||
/* auth-user <username> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
@@ -4119,16 +4158,16 @@ void sentinelSetCommand(client *c) {
|
||||
int numargs = j-old_j+1;
|
||||
switch(numargs) {
|
||||
case 2:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",(char*)c->argv[old_j]->ptr,
|
||||
redacted ? "******" : (char*)c->argv[old_j+1]->ptr);
|
||||
break;
|
||||
case 3:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr,
|
||||
c->argv[old_j+2]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",(char*)c->argv[old_j]->ptr,
|
||||
(char*)c->argv[old_j+1]->ptr,
|
||||
(char*)c->argv[old_j+2]->ptr);
|
||||
break;
|
||||
default:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",(char*)c->argv[old_j]->ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4700,6 +4739,7 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
|
||||
max_master_down_time += master->down_after_period * 10;
|
||||
|
||||
di = dictGetIterator(master->slaves);
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
mstime_t info_validity_time;
|
||||
|
||||
+152
-61
@@ -706,7 +706,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"auth",authCommand,-2,
|
||||
"no-auth no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
|
||||
"no-auth no-script ok-loading ok-stale fast @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* We don't allow PING during loading since in Redis PING is used as
|
||||
@@ -749,7 +749,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"exec",execCommand,1,
|
||||
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
|
||||
"no-script no-slowlog ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"discard",discardCommand,1,
|
||||
@@ -870,7 +870,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"migrate",migrateCommand,-6,
|
||||
"write random @keyspace @dangerous",
|
||||
0,migrateGetKeys,0,0,0,0,0,0},
|
||||
0,migrateGetKeys,3,3,1,0,0,0},
|
||||
|
||||
{"asking",askingCommand,1,
|
||||
"fast @keyspace",
|
||||
@@ -901,17 +901,21 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"hello",helloCommand,-1,
|
||||
"no-auth no-script fast no-monitor ok-loading ok-stale @connection",
|
||||
"no-auth no-script fast ok-loading ok-stale @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* EVAL can modify the dataset, however it is not flagged as a write
|
||||
* command since we do the check while running commands from Lua. */
|
||||
* command since we do the check while running commands from Lua.
|
||||
*
|
||||
* EVAL and EVALSHA also feed monitors before the commands are executed,
|
||||
* as opposed to after.
|
||||
*/
|
||||
{"eval",evalCommand,-3,
|
||||
"no-script may-replicate @scripting",
|
||||
"no-script no-monitor may-replicate @scripting",
|
||||
0,evalGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"evalsha",evalShaCommand,-3,
|
||||
"no-script may-replicate @scripting",
|
||||
"no-script no-monitor may-replicate @scripting",
|
||||
0,evalGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"slowlog",slowlogCommand,-2,
|
||||
@@ -1583,13 +1587,15 @@ int incrementallyRehash(int dbid) {
|
||||
* as we want to avoid resizing the hash tables when there is a child in order
|
||||
* to play well with copy-on-write (otherwise when a resize happens lots of
|
||||
* memory pages are copied). The goal of this function is to update the ability
|
||||
* for dict.c to resize the hash tables accordingly to the fact we have an
|
||||
* for dict.c to resize or rehash the tables accordingly to the fact we have an
|
||||
* active fork child running. */
|
||||
void updateDictResizePolicy(void) {
|
||||
if (!hasActiveChildProcess())
|
||||
dictEnableResize();
|
||||
if (server.in_fork_child != CHILD_TYPE_NONE)
|
||||
dictSetResizeEnabled(DICT_RESIZE_FORBID);
|
||||
else if (hasActiveChildProcess())
|
||||
dictSetResizeEnabled(DICT_RESIZE_AVOID);
|
||||
else
|
||||
dictDisableResize();
|
||||
dictSetResizeEnabled(DICT_RESIZE_ENABLE);
|
||||
}
|
||||
|
||||
const char *strChildType(int type) {
|
||||
@@ -1682,7 +1688,7 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
/* Only resize the query buffer if it is actually wasting
|
||||
* at least a few kbytes. */
|
||||
if (sdsavail(c->querybuf) > 1024*4) {
|
||||
c->querybuf = sdsRemoveFreeSpace(c->querybuf);
|
||||
c->querybuf = sdsRemoveFreeSpace(c->querybuf,1);
|
||||
}
|
||||
}
|
||||
/* Reset the peak again to capture the peak memory usage in the next
|
||||
@@ -1702,7 +1708,7 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
if(pending_querybuf_size > LIMIT_PENDING_QUERYBUF &&
|
||||
sdslen(c->pending_querybuf) < (pending_querybuf_size/2))
|
||||
{
|
||||
c->pending_querybuf = sdsRemoveFreeSpace(c->pending_querybuf);
|
||||
c->pending_querybuf = sdsRemoveFreeSpace(c->pending_querybuf,1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -1839,6 +1845,7 @@ void clientsCron(void) {
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c, curr_peak_mem_usage_slot)) continue;
|
||||
if (clientsCronTrackClientsMemUsage(c)) continue;
|
||||
if (closeClientOnOutputBufferLimitReached(c, 0)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,18 +1905,8 @@ void databasesCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
server.ustime = ustime();
|
||||
static inline void updateCachedTimeWithUs(int update_daylight_info, const long long ustime) {
|
||||
server.ustime = ustime;
|
||||
server.mstime = server.ustime / 1000;
|
||||
time_t unixtime = server.mstime / 1000;
|
||||
atomicSet(server.unixtime, unixtime);
|
||||
@@ -1927,6 +1924,21 @@ void updateCachedTime(int update_daylight_info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
const long long us = ustime();
|
||||
updateCachedTimeWithUs(update_daylight_info, us);
|
||||
}
|
||||
|
||||
void checkChildrenDone(void) {
|
||||
int statloc = 0;
|
||||
pid_t pid;
|
||||
@@ -2060,6 +2072,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
}
|
||||
|
||||
/* for debug purposes: skip actual cron work if pause_cron is on */
|
||||
if (server.pause_cron) return 1000/server.hz;
|
||||
|
||||
run_with_period(100) {
|
||||
long long stat_net_input_bytes, stat_net_output_bytes;
|
||||
atomicGet(server.stat_net_input_bytes, stat_net_input_bytes);
|
||||
@@ -2426,6 +2441,11 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
* our clients. */
|
||||
updateFailoverStatus();
|
||||
|
||||
/* Since we rely on current_client to send scheduled invalidation messages
|
||||
* we have to flush them after each command, so when we get here, the list
|
||||
* must be empty. */
|
||||
serverAssert(listLength(server.tracking_pending_keys) == 0);
|
||||
|
||||
/* Send the invalidation messages to clients participating to the
|
||||
* client side caching protocol in broadcasting (BCAST) mode. */
|
||||
trackingBroadcastInvalidationMessages();
|
||||
@@ -2603,6 +2623,7 @@ void createSharedObjects(void) {
|
||||
shared.getack = createStringObject("GETACK",6);
|
||||
shared.special_asterick = createStringObject("*",1);
|
||||
shared.special_equals = createStringObject("=",1);
|
||||
shared.redacted = makeObjectShared(createStringObject("(redacted)",10));
|
||||
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
shared.integers[j] =
|
||||
@@ -2614,6 +2635,10 @@ void createSharedObjects(void) {
|
||||
sdscatprintf(sdsempty(),"*%d\r\n",j));
|
||||
shared.bulkhdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"$%d\r\n",j));
|
||||
shared.maphdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"%%%d\r\n",j));
|
||||
shared.sethdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"~%d\r\n",j));
|
||||
}
|
||||
/* The following two shared objects, minstring and maxstrings, are not
|
||||
* actually used for their value but as a special object meaning
|
||||
@@ -2675,6 +2700,7 @@ void initServerConfig(void) {
|
||||
server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
|
||||
server.next_client_id = 1; /* Client IDs, start from 1 .*/
|
||||
server.loading_process_events_interval_bytes = (1024*1024*2);
|
||||
server.pause_cron = 0;
|
||||
|
||||
unsigned int lruclock = getLRUClock();
|
||||
atomicSet(server.lruclock,lruclock);
|
||||
@@ -2758,6 +2784,10 @@ void initServerConfig(void) {
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
server.lua_always_replicate_commands = 1;
|
||||
|
||||
/* Client Pause related */
|
||||
server.client_pause_type = CLIENT_PAUSE_OFF;
|
||||
server.client_pause_end_time = 0;
|
||||
|
||||
initConfigValues();
|
||||
}
|
||||
|
||||
@@ -3150,6 +3180,7 @@ void initServer(void) {
|
||||
server.current_client = NULL;
|
||||
server.errors = raxNew();
|
||||
server.fixed_time_expire = 0;
|
||||
server.in_nested_call = 0;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -3162,6 +3193,7 @@ void initServer(void) {
|
||||
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
server.unblocked_clients = listCreate();
|
||||
server.ready_keys = listCreate();
|
||||
server.tracking_pending_keys = listCreate();
|
||||
server.clients_waiting_acks = listCreate();
|
||||
server.get_ack_from_slaves = 0;
|
||||
server.client_pause_type = 0;
|
||||
@@ -3193,11 +3225,13 @@ void initServer(void) {
|
||||
/* Open the TCP listening socket for the user commands. */
|
||||
if (server.port != 0 &&
|
||||
listenToPort(server.port,&server.ipfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", server.port);
|
||||
exit(1);
|
||||
}
|
||||
if (server.tls_port != 0 &&
|
||||
listenToPort(server.tls_port,&server.tlsfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (TLS), aborting.", server.tls_port);
|
||||
exit(1);
|
||||
}
|
||||
@@ -3624,12 +3658,6 @@ void preventCommandPropagation(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_PROP;
|
||||
}
|
||||
|
||||
/* Avoid logging any information about this client's arguments
|
||||
* since they contain sensitive information. */
|
||||
void preventCommandLogging(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_LOGGING;
|
||||
}
|
||||
|
||||
/* AOF specific version of preventCommandPropagation(). */
|
||||
void preventCommandAOF(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_AOF_PROP;
|
||||
@@ -3643,7 +3671,7 @@ void preventCommandReplication(client *c) {
|
||||
/* Log the last command a client executed into the slowlog. */
|
||||
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration) {
|
||||
/* Some commands may contain sensitive data that should not be available in the slowlog. */
|
||||
if ((c->flags & CLIENT_PREVENT_LOGGING) || (cmd->flags & CMD_SKIP_SLOWLOG))
|
||||
if (cmd->flags & CMD_SKIP_SLOWLOG)
|
||||
return;
|
||||
|
||||
/* If command argument vector was rewritten, use the original
|
||||
@@ -3692,22 +3720,10 @@ void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t dur
|
||||
*/
|
||||
void call(client *c, int flags) {
|
||||
long long dirty;
|
||||
monotime call_timer;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
static long long prev_err_count;
|
||||
|
||||
server.fixed_time_expire++;
|
||||
|
||||
/* Send the command to clients in MONITOR mode if applicable.
|
||||
* Administrative commands are considered too dangerous to be shown. */
|
||||
if (listLength(server.monitors) &&
|
||||
!server.loading &&
|
||||
!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
|
||||
{
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
/* Initialization: clear the flags that must be set by the command on
|
||||
* demand, and initialize the array for additional commands propagation. */
|
||||
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
|
||||
@@ -3717,10 +3733,31 @@ void call(client *c, int flags) {
|
||||
/* Call the command. */
|
||||
dirty = server.dirty;
|
||||
prev_err_count = server.stat_total_error_replies;
|
||||
updateCachedTime(0);
|
||||
elapsedStart(&call_timer);
|
||||
|
||||
const long long call_timer = ustime();
|
||||
|
||||
/* Update cache time, in case we have nested calls we want to
|
||||
* update only on the first call*/
|
||||
if (server.fixed_time_expire++ == 0) {
|
||||
updateCachedTimeWithUs(0,call_timer);
|
||||
}
|
||||
|
||||
monotime monotonic_start = 0;
|
||||
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
|
||||
monotonic_start = getMonotonicUs();
|
||||
|
||||
server.in_nested_call++;
|
||||
c->cmd->proc(c);
|
||||
const long duration = elapsedUs(call_timer);
|
||||
server.in_nested_call--;
|
||||
|
||||
/* In order to avoid performance implication due to querying the clock using a system call 3 times,
|
||||
* we use a monotonic clock, when we are sure its cost is very low, and fall back to non-monotonic call otherwise. */
|
||||
ustime_t duration;
|
||||
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
|
||||
duration = getMonotonicUs() - monotonic_start;
|
||||
else
|
||||
duration = ustime() - call_timer;
|
||||
|
||||
c->duration = duration;
|
||||
dirty = server.dirty-dirty;
|
||||
if (dirty < 0) dirty = 0;
|
||||
@@ -3773,6 +3810,14 @@ void call(client *c, int flags) {
|
||||
if ((flags & CMD_CALL_SLOWLOG) && !(c->flags & CLIENT_BLOCKED))
|
||||
slowlogPushCurrentCommand(c, real_cmd, duration);
|
||||
|
||||
/* Send the command to clients in MONITOR mode if applicable.
|
||||
* Administrative commands are considered too dangerous to be shown. */
|
||||
if (!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN))) {
|
||||
robj **argv = c->original_argv ? c->original_argv : c->argv;
|
||||
int argc = c->original_argv ? c->original_argc : c->argc;
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,argv,argc);
|
||||
}
|
||||
|
||||
/* Clear the original argv.
|
||||
* If the client is blocked we will handle slowlog when it is unblocked. */
|
||||
if (!(c->flags & CLIENT_BLOCKED))
|
||||
@@ -3891,6 +3936,9 @@ void call(client *c, int flags) {
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
if (zmalloc_used > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used;
|
||||
|
||||
/* Do some maintenance job and cleanup */
|
||||
afterCommand(c);
|
||||
}
|
||||
|
||||
/* Used when a command that is ready for execution needs to be rejected, due to
|
||||
@@ -3935,6 +3983,14 @@ static int cmdHasMovableKeys(struct redisCommand *cmd) {
|
||||
cmd->flags & CMD_MODULE_GETKEYS;
|
||||
}
|
||||
|
||||
/* This is called after a command in call, we can do some maintenance job in it. */
|
||||
void afterCommand(client *c) {
|
||||
UNUSED(c);
|
||||
/* Flush pending invalidation messages only when we are not in nested call.
|
||||
* So the messages are not interleaved with transaction response. */
|
||||
if (!server.in_nested_call) trackingHandlePendingKeyInvalidations();
|
||||
}
|
||||
|
||||
/* If this function gets called we already read a whole
|
||||
* command, arguments are in the client argv/argc fields.
|
||||
* processCommand() execute the command or prepare the
|
||||
@@ -3985,6 +4041,8 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int is_read_command = (c->cmd->flags & CMD_READONLY) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_READONLY));
|
||||
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||
int is_denyoom_command = (c->cmd->flags & CMD_DENYOOM) ||
|
||||
@@ -3996,13 +4054,8 @@ int processCommand(client *c) {
|
||||
int is_may_replicate_command = (c->cmd->flags & (CMD_WRITE | CMD_MAY_REPLICATE)) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & (CMD_WRITE | CMD_MAY_REPLICATE)));
|
||||
|
||||
/* Check if the user is authenticated. This check is skipped in case
|
||||
* the default user is flagged as "nopass" and is active. */
|
||||
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
(DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
!c->authenticated;
|
||||
if (auth_required) {
|
||||
/* AUTH and HELLO and no auth modules are valid even in
|
||||
if (authRequired(c)) {
|
||||
/* AUTH and HELLO and no auth commands are valid even in
|
||||
* non-authenticated state. */
|
||||
if (!(c->cmd->flags & CMD_NO_AUTH)) {
|
||||
rejectCommand(c,shared.noautherr);
|
||||
@@ -4074,6 +4127,13 @@ int processCommand(client *c) {
|
||||
* propagation of DELs due to eviction. */
|
||||
if (server.maxmemory && !server.lua_timedout) {
|
||||
int out_of_memory = (performEvictions() == EVICT_FAIL);
|
||||
|
||||
/* performEvictions may evict keys, so we need flush pending tracking
|
||||
* invalidation keys. If we don't do this, we may get an invalidation
|
||||
* message after we perform operation on the key, where in fact this
|
||||
* message belongs to the old value of the key before it gets evicted.*/
|
||||
trackingHandlePendingKeyInvalidations();
|
||||
|
||||
/* performEvictions may flush slave output buffers. This may result
|
||||
* in a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return C_ERR;
|
||||
@@ -4194,7 +4254,7 @@ int processCommand(client *c) {
|
||||
c->cmd->proc != discardCommand &&
|
||||
c->cmd->proc != watchCommand &&
|
||||
c->cmd->proc != unwatchCommand &&
|
||||
c->cmd->proc != resetCommand &&
|
||||
c->cmd->proc != resetCommand &&
|
||||
!(c->cmd->proc == shutdownCommand &&
|
||||
c->argc == 2 &&
|
||||
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
|
||||
@@ -4206,6 +4266,14 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Prevent a replica from sending commands that access the keyspace.
|
||||
* The main objective here is to prevent abuse of client pause check
|
||||
* from which replicas are exempt. */
|
||||
if ((c->flags & CLIENT_SLAVE) && (is_may_replicate_command || is_write_command || is_read_command)) {
|
||||
rejectCommandFormat(c, "Replica can't interract with the keyspace");
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* If the server is paused, block the client until
|
||||
* the pause has ended. Replicas are never paused. */
|
||||
if (!(c->flags & CLIENT_SLAVE) &&
|
||||
@@ -4662,6 +4730,7 @@ sds genRedisInfoString(const char *section) {
|
||||
"redis_mode:%s\r\n"
|
||||
"os:%s %s %s\r\n"
|
||||
"arch_bits:%i\r\n"
|
||||
"monotonic_clock:%s\r\n"
|
||||
"multiplexing_api:%s\r\n"
|
||||
"atomicvar_api:%s\r\n"
|
||||
"gcc_version:%i.%i.%i\r\n"
|
||||
@@ -4685,6 +4754,7 @@ sds genRedisInfoString(const char *section) {
|
||||
mode,
|
||||
name.sysname, name.release, name.machine,
|
||||
server.arch_bits,
|
||||
monotonicInfoString(),
|
||||
aeGetApiName(),
|
||||
REDIS_ATOMIC_API,
|
||||
#ifdef __GNUC__
|
||||
@@ -5075,11 +5145,15 @@ sds genRedisInfoString(const char *section) {
|
||||
server.masterhost == NULL ? "master" : "slave");
|
||||
if (server.masterhost) {
|
||||
long long slave_repl_offset = 1;
|
||||
long long slave_read_repl_offset = 1;
|
||||
|
||||
if (server.master)
|
||||
if (server.master) {
|
||||
slave_repl_offset = server.master->reploff;
|
||||
else if (server.cached_master)
|
||||
slave_read_repl_offset = server.master->read_reploff;
|
||||
} else if (server.cached_master) {
|
||||
slave_repl_offset = server.cached_master->reploff;
|
||||
slave_read_repl_offset = server.cached_master->read_reploff;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"master_host:%s\r\n"
|
||||
@@ -5087,6 +5161,7 @@ sds genRedisInfoString(const char *section) {
|
||||
"master_link_status:%s\r\n"
|
||||
"master_last_io_seconds_ago:%d\r\n"
|
||||
"master_sync_in_progress:%d\r\n"
|
||||
"slave_read_repl_offset:%lld\r\n"
|
||||
"slave_repl_offset:%lld\r\n"
|
||||
,server.masterhost,
|
||||
server.masterport,
|
||||
@@ -5095,6 +5170,7 @@ sds genRedisInfoString(const char *section) {
|
||||
server.master ?
|
||||
((int)(server.unixtime-server.master->lastinteraction)) : -1,
|
||||
server.repl_state == REPL_STATE_TRANSFER,
|
||||
slave_read_repl_offset,
|
||||
slave_repl_offset
|
||||
);
|
||||
|
||||
@@ -5387,8 +5463,15 @@ int linuxOvercommitMemoryValue(void) {
|
||||
}
|
||||
|
||||
void linuxMemoryWarnings(void) {
|
||||
if (linuxOvercommitMemoryValue() == 0) {
|
||||
serverLog(LL_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.");
|
||||
if (linuxOvercommitMemoryValue() != 1) {
|
||||
const char *msg =
|
||||
"WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. "
|
||||
#if defined(USE_JEMALLOC)
|
||||
"Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. "
|
||||
#endif
|
||||
"To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the "
|
||||
"command 'sysctl vm.overcommit_memory=1' for this to take effect.";
|
||||
serverLog(LL_WARNING, "%s", msg);
|
||||
}
|
||||
if (THPIsEnabled() && THPDisable()) {
|
||||
serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').");
|
||||
@@ -5846,7 +5929,12 @@ int redisFork(int purpose) {
|
||||
server.in_fork_child = purpose;
|
||||
setOOMScoreAdj(CONFIG_OOM_BGCHILD);
|
||||
setupChildSignalHandlers();
|
||||
updateDictResizePolicy();
|
||||
closeChildUnusedResourceAfterFork();
|
||||
/* Close the reading part, so that if the parent crashes, the child will
|
||||
* get a write error and exit. */
|
||||
if (server.child_info_pipe[0] != -1)
|
||||
close(server.child_info_pipe[0]);
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_total_forks++;
|
||||
@@ -6187,9 +6275,12 @@ int main(int argc, char **argv) {
|
||||
setlocale(LC_COLLATE,"");
|
||||
tzset(); /* Populates 'timezone' global. */
|
||||
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
|
||||
srand(time(NULL)^getpid());
|
||||
srandom(time(NULL)^getpid());
|
||||
|
||||
/* To achieve entropy, in case of containers, their time() and getpid() can
|
||||
* be the same. But value of tv_usec is fast enough to make the difference */
|
||||
gettimeofday(&tv,NULL);
|
||||
srand(time(NULL)^getpid()^tv.tv_usec);
|
||||
srandom(time(NULL)^getpid()^tv.tv_usec);
|
||||
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||
crc64_init();
|
||||
|
||||
|
||||
+38
-9
@@ -84,6 +84,12 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#include "endianconv.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* min/max */
|
||||
#undef min
|
||||
#undef max
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
/* Error codes */
|
||||
#define C_OK 0
|
||||
#define C_ERR -1
|
||||
@@ -99,6 +105,7 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define PROTO_SHARED_SELECT_CMDS 10
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define OBJ_SHARED_HDR_STRLEN(_len_) (((_len_) < 10) ? 4 : 5) /* see shared.mbulkhdr etc. */
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
#define AOF_READ_DIFF_INTERVAL_BYTES (1024*10)
|
||||
@@ -279,7 +286,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
||||
and AOF client */
|
||||
#define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants
|
||||
RDB without replication buffer. */
|
||||
#define CLIENT_PREVENT_LOGGING (1ULL<<43) /* Prevent logging of command to slowlog */
|
||||
#define CLIENT_PUSHING (1ULL<<43) /* This client is pushing notifications. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -883,6 +890,7 @@ typedef struct client {
|
||||
long bulklen; /* Length of bulk argument in multi bulk request. */
|
||||
list *reply; /* List of reply objects to send to the client. */
|
||||
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
|
||||
list *deferred_reply_errors; /* Used for module thread safe contexts. */
|
||||
size_t sentlen; /* Amount of bytes already sent in the current
|
||||
buffer or object being sent. */
|
||||
time_t ctime; /* Client creation time. */
|
||||
@@ -986,11 +994,13 @@ struct sharedObjectsStruct {
|
||||
*script, *replconf, *eval, *persist, *set, *pexpireat, *pexpire,
|
||||
*time, *pxat, *px, *retrycount, *force, *justid,
|
||||
*lastid, *ping, *setid, *keepttl, *load, *createconsumer,
|
||||
*getack, *special_asterick, *special_equals, *default_username,
|
||||
*getack, *special_asterick, *special_equals, *default_username, *redacted,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
*bulkhdr[OBJ_SHARED_BULKHDR_LEN]; /* "$<value>\r\n" */
|
||||
*bulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "$<value>\r\n" */
|
||||
*maphdr[OBJ_SHARED_BULKHDR_LEN], /* "%<value>\r\n" */
|
||||
*sethdr[OBJ_SHARED_BULKHDR_LEN]; /* "~<value>\r\n" */
|
||||
sds minstring, maxstring;
|
||||
};
|
||||
|
||||
@@ -1228,6 +1238,7 @@ struct redisServer {
|
||||
client *current_client; /* Current client executing the command. */
|
||||
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
int in_nested_call; /* If > 0, in a nested call of a call */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
pause_type client_pause_type; /* True if clients are currently paused */
|
||||
list *paused_clients; /* List of pause clients */
|
||||
@@ -1335,6 +1346,7 @@ struct redisServer {
|
||||
int set_proc_title; /* True if change proc title */
|
||||
char *proc_title_template; /* Process title template format */
|
||||
clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
|
||||
int pause_cron; /* Don't run cron tasks (debug) */
|
||||
/* AOF persistence */
|
||||
int aof_enabled; /* AOF configuration */
|
||||
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
|
||||
@@ -1509,6 +1521,7 @@ struct redisServer {
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
list *tracking_pending_keys; /* tracking invalidation keys pending to flush */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1571,7 +1584,8 @@ struct redisServer {
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
monotime lua_time_start; /* monotonic timer to detect timed-out script */
|
||||
mstime_t lua_time_snapshot; /* Snapshot of mstime when script is started */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
execution of the current script. */
|
||||
int lua_random_dirty; /* True if a random command was called during the
|
||||
@@ -1779,6 +1793,7 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
||||
void processModuleLoadingProgressEvent(int is_aof);
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||
void moduleUnblockClient(client *c);
|
||||
int moduleBlockedClientMayTimeout(client *c);
|
||||
int moduleClientIsBlockedOnKeys(client *c);
|
||||
void moduleNotifyUserChanged(client *c);
|
||||
void moduleNotifyKeyUnlink(robj *key, robj *val);
|
||||
@@ -1794,7 +1809,7 @@ void getRandomHexChars(char *p, size_t len);
|
||||
void getRandomBytes(unsigned char *p, size_t len);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
void exitFromChild(int retcode);
|
||||
size_t redisPopcount(void *s, long count);
|
||||
long long redisPopcount(void *s, long count);
|
||||
int redisSetProcTitle(char *title);
|
||||
int validateProcTitleTemplate(const char *template);
|
||||
int redisCommunicateSystemd(const char *sd_notify_msg);
|
||||
@@ -1840,6 +1855,7 @@ void addReplyErrorSds(client *c, sds err);
|
||||
void addReplyError(client *c, const char *err);
|
||||
void addReplyStatus(client *c, const char *status);
|
||||
void addReplyDouble(client *c, double d);
|
||||
void addReplyBigNum(client *c, const char* num, size_t len);
|
||||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
@@ -1851,6 +1867,7 @@ void addReplyHelp(client *c, const char **help);
|
||||
void addReplySubcommandSyntaxError(client *c);
|
||||
void addReplyLoadedModules(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
void deferredAfterErrorReply(client *c, list *errors);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
void freeClientReplyValue(void *o);
|
||||
@@ -1864,9 +1881,10 @@ sds getAllClientsInfoString(int type);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
void redactClientCommandArgument(client *c, int argc);
|
||||
unsigned long getClientOutputBufferMemoryUsage(client *c);
|
||||
int freeClientsInAsyncFreeQueue(void);
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c);
|
||||
int closeClientOnOutputBufferLimitReached(client *c, int async);
|
||||
int getClientType(client *c);
|
||||
int getClientTypeByName(char *name);
|
||||
char *getClientTypeName(int class);
|
||||
@@ -1894,6 +1912,7 @@ void protectClient(client *c);
|
||||
void unprotectClient(client *c);
|
||||
void initThreadedIO(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
int authRequired(client *c);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
@@ -1909,8 +1928,11 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(client *c, robj *keyobj);
|
||||
void trackingInvalidateKey(client *c, robj *keyobj, int bcast);
|
||||
void trackingScheduleKeyInvalidation(uint64_t client_id, robj *keyobj);
|
||||
void trackingHandlePendingKeyInvalidations(void);
|
||||
void trackingInvalidateKeysOnFlush(int async);
|
||||
void freeTrackingRadixTree(rax *rt);
|
||||
void freeTrackingRadixTreeAsync(rax *rt);
|
||||
void trackingLimitUsedSlots(void);
|
||||
uint64_t trackingGetTotalItems(void);
|
||||
@@ -1943,6 +1965,7 @@ void initClientMultiState(client *c);
|
||||
void freeClientMultiState(client *c);
|
||||
void queueMultiCommand(client *c);
|
||||
void touchWatchedKey(redisDb *db, robj *key);
|
||||
int isWatchedKeyExpired(client *c);
|
||||
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with);
|
||||
void discardTransaction(client *c);
|
||||
void flagTransaction(client *c);
|
||||
@@ -1967,6 +1990,8 @@ robj *createObject(int type, void *ptr);
|
||||
robj *createStringObject(const char *ptr, size_t len);
|
||||
robj *createRawStringObject(const char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(const char *ptr, size_t len);
|
||||
robj *tryCreateRawStringObject(const char *ptr, size_t len);
|
||||
robj *tryCreateStringObject(const char *ptr, size_t len);
|
||||
robj *dupStringObject(const robj *o);
|
||||
int isSdsRepresentableAsLongLong(sds s, long long *llval);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
@@ -2167,7 +2192,7 @@ unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned long zsetLength(const robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen);
|
||||
int zsetScore(robj *zobj, sds member, double *score);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
|
||||
int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
|
||||
@@ -2211,11 +2236,12 @@ void redisOpArrayInit(redisOpArray *oa);
|
||||
void redisOpArrayFree(redisOpArray *oa);
|
||||
void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandLogging(client *c);
|
||||
void preventCommandAOF(client *c);
|
||||
void preventCommandReplication(client *c);
|
||||
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration);
|
||||
int prepareForShutdown(int flags);
|
||||
void afterCommand(client *c);
|
||||
int inNestedCall(void);
|
||||
#ifdef __GNUC__
|
||||
void _serverLog(int level, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
@@ -2316,7 +2342,9 @@ void initConfigValues();
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj *key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
@@ -2330,6 +2358,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
void SentReplyOnKeyMiss(client *c, robj *reply);
|
||||
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock, int lru_multiplier);
|
||||
#define LOOKUP_NONE 0
|
||||
|
||||
+7
-2
@@ -45,8 +45,13 @@ void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
|
||||
{
|
||||
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
|
||||
|
||||
for (i = 0, j = 0; i < 16; ++i, j += 4)
|
||||
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
|
||||
for (i = 0, j = 0; i < 16; ++i, j += 4) {
|
||||
m[i] = ((WORD) data[j + 0] << 24) |
|
||||
((WORD) data[j + 1] << 16) |
|
||||
((WORD) data[j + 2] << 8) |
|
||||
((WORD) data[j + 3]);
|
||||
}
|
||||
|
||||
for ( ; i < 64; ++i)
|
||||
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
|
||||
|
||||
|
||||
+4
-2
@@ -320,8 +320,10 @@ void sortCommand(client *c) {
|
||||
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
|
||||
}
|
||||
|
||||
/* Perform LIMIT start,count sanity checking. */
|
||||
start = (limit_start < 0) ? 0 : limit_start;
|
||||
/* Perform LIMIT start,count sanity checking.
|
||||
* And avoid integer overflow by limiting inputs to object sizes. */
|
||||
start = min(max(limit_start, 0), vectorlen);
|
||||
limit_count = min(max(limit_count, -1), vectorlen);
|
||||
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
|
||||
if (start >= vectorlen) {
|
||||
start = vectorlen-1;
|
||||
|
||||
+26
-8
@@ -39,17 +39,22 @@
|
||||
* as their string length can be queried in constant time. */
|
||||
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
int i;
|
||||
size_t sum = 0;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
if (sdsEncodedObject(argv[i]) &&
|
||||
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
||||
{
|
||||
if (!sdsEncodedObject(argv[i]))
|
||||
continue;
|
||||
size_t len = sdslen(argv[i]->ptr);
|
||||
if (len > server.hash_max_ziplist_value) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
sum += len;
|
||||
}
|
||||
if (!ziplistSafeToAdd(o->ptr, sum))
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
}
|
||||
|
||||
/* Get the value from a ziplist encoded hash, identified by field.
|
||||
@@ -724,6 +729,10 @@ void hincrbyfloatCommand(client *c) {
|
||||
unsigned int vlen;
|
||||
|
||||
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
|
||||
if (isnan(incr) || isinf(incr)) {
|
||||
addReplyError(c,"value is NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) {
|
||||
if (vstr) {
|
||||
@@ -986,7 +995,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
int uniq = 1;
|
||||
robj *hash;
|
||||
|
||||
if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray))
|
||||
== NULL || checkType(c,hash,OBJ_HASH)) return;
|
||||
size = hashTypeLength(hash);
|
||||
|
||||
@@ -1024,6 +1033,8 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
addReplyBulkCBuffer(c, key, sdslen(key));
|
||||
if (withvalues)
|
||||
addReplyBulkCBuffer(c, value, sdslen(value));
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
break;
|
||||
}
|
||||
} else if (hash->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
ziplistEntry *keys, *vals = NULL;
|
||||
@@ -1037,6 +1048,8 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
count -= sample_count;
|
||||
ziplistRandomPairs(hash->ptr, sample_count, keys, vals);
|
||||
harndfieldReplyWithZiplist(c, sample_count, keys, vals);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
break;
|
||||
}
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
@@ -1175,7 +1188,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
}
|
||||
}
|
||||
|
||||
/* HRANDFIELD [<count> WITHVALUES] */
|
||||
/* HRANDFIELD key [<count> [WITHVALUES]] */
|
||||
void hrandfieldCommand(client *c) {
|
||||
long l;
|
||||
int withvalues = 0;
|
||||
@@ -1183,12 +1196,17 @@ void hrandfieldCommand(client *c) {
|
||||
ziplistEntry ele;
|
||||
|
||||
if (c->argc >= 3) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||
if (getRangeLongFromObjectOrReply(c,c->argv[2],-LONG_MAX,LONG_MAX,&l,NULL) != C_OK) return;
|
||||
if (c->argc > 4 || (c->argc == 4 && strcasecmp(c->argv[3]->ptr,"withvalues"))) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
} else if (c->argc == 4)
|
||||
} else if (c->argc == 4) {
|
||||
withvalues = 1;
|
||||
if (l < -LONG_MAX/2 || l > LONG_MAX/2) {
|
||||
addReplyError(c,"value is out of range");
|
||||
return;
|
||||
}
|
||||
}
|
||||
hrandfieldWithCountCommand(c, l, withvalues);
|
||||
return;
|
||||
}
|
||||
|
||||
+38
-7
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#define LIST_MAX_ITEM_SIZE ((1ull<<32)-1024)
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* List API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -224,6 +226,13 @@ robj *listTypeDup(robj *o) {
|
||||
void pushGenericCommand(client *c, int where, int xx) {
|
||||
int j;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
|
||||
if (checkType(c,lobj,OBJ_LIST)) return;
|
||||
if (!lobj) {
|
||||
@@ -287,6 +296,11 @@ void linsertCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sdslen(c->argv[4]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,subject,OBJ_LIST)) return;
|
||||
|
||||
@@ -354,6 +368,11 @@ void lsetCommand(client *c) {
|
||||
long index;
|
||||
robj *value = c->argv[3];
|
||||
|
||||
if (sdslen(value->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -437,6 +456,7 @@ void listElementsRemoved(client *c, robj *key, int where, robj *o, long count) {
|
||||
* optional count may be provided as the third argument of the client's
|
||||
* command. */
|
||||
void popGenericCommand(client *c, int where) {
|
||||
int hascount = (c->argc == 3);
|
||||
long count = 0;
|
||||
robj *value;
|
||||
|
||||
@@ -444,21 +464,22 @@ void popGenericCommand(client *c, int where) {
|
||||
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
|
||||
c->cmd->name);
|
||||
return;
|
||||
} else if (c->argc == 3) {
|
||||
} else if (hascount) {
|
||||
/* Parse the optional count argument. */
|
||||
if (getPositiveLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
|
||||
return;
|
||||
if (count == 0) {
|
||||
/* Fast exit path. */
|
||||
addReplyNullArray(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
robj *o = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp]);
|
||||
robj *o = lookupKeyWriteOrReply(c, c->argv[1], hascount ? shared.nullarray[c->resp]: shared.null[c->resp]);
|
||||
if (o == NULL || checkType(c, o, OBJ_LIST))
|
||||
return;
|
||||
|
||||
if (hascount && !count) {
|
||||
/* Fast exit path. */
|
||||
addReply(c,shared.emptyarray);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
/* Pop a single element. This is POP's original behavior that replies
|
||||
* with a bulk string. */
|
||||
@@ -576,6 +597,11 @@ void lposCommand(client *c) {
|
||||
int direction = LIST_TAIL;
|
||||
long rank = 1, count = -1, maxlen = 0; /* Count -1: option not given. */
|
||||
|
||||
if (sdslen(ele->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the optional arguments. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
@@ -671,6 +697,11 @@ void lremCommand(client *c) {
|
||||
long toremove;
|
||||
long removed = 0;
|
||||
|
||||
if (sdslen(obj->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
|
||||
+37
-15
@@ -66,7 +66,10 @@ int setTypeAdd(robj *subject, sds value) {
|
||||
if (success) {
|
||||
/* Convert to regular set when the intset contains
|
||||
* too many entries. */
|
||||
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
|
||||
size_t max_entries = server.set_max_intset_entries;
|
||||
/* limit to 1G entries due to intset internals. */
|
||||
if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
if (intsetLen(subject->ptr) > max_entries)
|
||||
setTypeConvert(subject,OBJ_ENCODING_HT);
|
||||
return 1;
|
||||
}
|
||||
@@ -392,12 +395,12 @@ void smoveCommand(client *c) {
|
||||
}
|
||||
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,ele->ptr)) {
|
||||
server.dirty++;
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
}
|
||||
addReply(c,shared.cone);
|
||||
@@ -664,7 +667,7 @@ void srandmemberWithCountCommand(client *c) {
|
||||
|
||||
dict *d;
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||
if (getRangeLongFromObjectOrReply(c,c->argv[2],-LONG_MAX,LONG_MAX,&l,NULL) != C_OK) return;
|
||||
if (l >= 0) {
|
||||
count = (unsigned long) l;
|
||||
} else {
|
||||
@@ -698,6 +701,8 @@ void srandmemberWithCountCommand(client *c) {
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
||||
}
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -858,24 +863,17 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
int64_t intobj;
|
||||
void *replylen = NULL;
|
||||
unsigned long j, cardinality = 0;
|
||||
int encoding;
|
||||
int encoding, empty = 0;
|
||||
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
lookupKeyWrite(c->db,setkeys[j]) :
|
||||
lookupKeyRead(c->db,setkeys[j]);
|
||||
if (!setobj) {
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
addReply(c,shared.emptyset[c->resp]);
|
||||
}
|
||||
return;
|
||||
/* A NULL is considered an empty set */
|
||||
empty += 1;
|
||||
sets[j] = NULL;
|
||||
continue;
|
||||
}
|
||||
if (checkType(c,setobj,OBJ_SET)) {
|
||||
zfree(sets);
|
||||
@@ -883,6 +881,24 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
}
|
||||
sets[j] = setobj;
|
||||
}
|
||||
|
||||
/* Set intersection with an empty set always results in an empty set.
|
||||
* Return ASAP if there is an empty set. */
|
||||
if (empty > 0) {
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
addReply(c,shared.emptyset[c->resp]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sort sets from the smallest to largest, this will improve our
|
||||
* algorithm's performance */
|
||||
qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
|
||||
@@ -976,10 +992,12 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
zfree(sets);
|
||||
}
|
||||
|
||||
/* SINTER key [key ...] */
|
||||
void sinterCommand(client *c) {
|
||||
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
|
||||
}
|
||||
|
||||
/* SINTERSTORE destination key [key ...] */
|
||||
void sinterstoreCommand(client *c) {
|
||||
sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
|
||||
}
|
||||
@@ -1149,18 +1167,22 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
zfree(sets);
|
||||
}
|
||||
|
||||
/* SUNION key [key ...] */
|
||||
void sunionCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_UNION);
|
||||
}
|
||||
|
||||
/* SUNIONSTORE destination key [key ...] */
|
||||
void sunionstoreCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_UNION);
|
||||
}
|
||||
|
||||
/* SDIFF key [key ...] */
|
||||
void sdiffCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_DIFF);
|
||||
}
|
||||
|
||||
/* SDIFFSTORE destination key [key ...] */
|
||||
void sdiffstoreCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_DIFF);
|
||||
}
|
||||
|
||||
+66
-31
@@ -47,6 +47,12 @@
|
||||
* setting stream_node_max_bytes to a huge number. */
|
||||
#define STREAM_LISTPACK_MAX_PRE_ALLOCATE 4096
|
||||
|
||||
/* Don't let listpacks grow too big, even if the user config allows it.
|
||||
* doing so can lead to an overflow (trying to store more than 32bit length
|
||||
* into the listpack header), or actually an assertion since lpInsert
|
||||
* will return NULL. */
|
||||
#define STREAM_LISTPACK_MAX_SIZE (1<<30)
|
||||
|
||||
void streamFreeCG(streamCG *cg);
|
||||
void streamFreeNACK(streamNACK *na);
|
||||
size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer);
|
||||
@@ -433,8 +439,11 @@ void streamGetEdgeID(stream *s, int first, streamID *edge_id)
|
||||
*
|
||||
* The function returns C_OK if the item was added, this is always true
|
||||
* if the ID was generated by the function. However the function may return
|
||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. */
|
||||
* C_ERR in several cases:
|
||||
* 1. If an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. errno will be set to EDOM.
|
||||
* 2. If a size of a single element or the sum of the elements is too big to
|
||||
* be stored into the stream. errno will be set to ERANGE. */
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
@@ -448,7 +457,23 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* or return an error. Automatically generated IDs might
|
||||
* overflow (and wrap-around) when incrementing the sequence
|
||||
part. */
|
||||
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
|
||||
if (streamCompareID(&id,&s->last_id) <= 0) {
|
||||
errno = EDOM;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Avoid overflow when trying to add an element to the stream (listpack
|
||||
* can only host up to 32bit length sttrings, and also a total listpack size
|
||||
* can't be bigger than 32bit length. */
|
||||
size_t totelelen = 0;
|
||||
for (int64_t i = 0; i < numfields*2; i++) {
|
||||
sds ele = argv[i]->ptr;
|
||||
totelelen += sdslen(ele);
|
||||
}
|
||||
if (totelelen > STREAM_LISTPACK_MAX_SIZE) {
|
||||
errno = ERANGE;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Add the new entry. */
|
||||
raxIterator ri;
|
||||
@@ -507,21 +532,25 @@ 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)
|
||||
{
|
||||
lp = NULL;
|
||||
int new_node = 0;
|
||||
size_t node_max_bytes = server.stream_node_max_bytes;
|
||||
if (node_max_bytes == 0 || node_max_bytes > STREAM_LISTPACK_MAX_SIZE)
|
||||
node_max_bytes = STREAM_LISTPACK_MAX_SIZE;
|
||||
if (lp_bytes + totelelen >= node_max_bytes) {
|
||||
new_node = 1;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
unsigned char *lp_ele = lpFirst(lp);
|
||||
/* Count both live entries and deleted ones. */
|
||||
int64_t count = lpGetInteger(lp_ele) + lpGetInteger(lpNext(lp,lp_ele));
|
||||
if (count >= server.stream_node_max_entries) {
|
||||
/* Shrink extra pre-allocated memory */
|
||||
lp = lpShrinkToFit(lp);
|
||||
if (ri.data != lp)
|
||||
raxInsert(s->rax,ri.key,ri.key_len,lp,NULL);
|
||||
lp = NULL;
|
||||
}
|
||||
if (count >= server.stream_node_max_entries) new_node = 1;
|
||||
}
|
||||
|
||||
if (new_node) {
|
||||
/* Shrink extra pre-allocated memory */
|
||||
lp = lpShrinkToFit(lp);
|
||||
if (ri.data != lp)
|
||||
raxInsert(s->rax,ri.key,ri.key_len,lp,NULL);
|
||||
lp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,16 +731,16 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
|
||||
|
||||
int64_t deleted = 0;
|
||||
while (raxNext(&ri)) {
|
||||
/* Check if we exceeded the amount of work we could do */
|
||||
if (limit && deleted >= limit)
|
||||
break;
|
||||
|
||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)
|
||||
break;
|
||||
|
||||
unsigned char *lp = ri.data, *p = lpFirst(lp);
|
||||
int64_t entries = lpGetInteger(p);
|
||||
|
||||
/* Check if we exceeded the amount of work we could do */
|
||||
if (limit && (deleted + entries) > limit)
|
||||
break;
|
||||
|
||||
/* Check if we can remove the whole node. */
|
||||
int remove_node;
|
||||
streamID master_id = {0}; /* For MINID */
|
||||
@@ -763,13 +792,13 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
|
||||
* update it after (and if) we actually remove the entry */
|
||||
unsigned char *pcopy = p;
|
||||
|
||||
int flags = lpGetInteger(p);
|
||||
int64_t flags = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip flags. */
|
||||
int to_skip;
|
||||
int64_t to_skip;
|
||||
|
||||
int ms_delta = lpGetInteger(p);
|
||||
int64_t ms_delta = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip ID ms delta */
|
||||
int seq_delta = lpGetInteger(p);
|
||||
int64_t seq_delta = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip ID seq delta */
|
||||
|
||||
streamID currid = {0}; /* For MINID */
|
||||
@@ -1101,7 +1130,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
* the first entry emitted for this listpack, then we already
|
||||
* emitted the current entry, and have to go back to the previous
|
||||
* one. */
|
||||
int lp_count = lpGetInteger(si->lp_ele);
|
||||
int64_t lp_count = lpGetInteger(si->lp_ele);
|
||||
while(lp_count--) si->lp_ele = lpPrev(si->lp,si->lp_ele);
|
||||
/* Seek lp-count of prev entry. */
|
||||
si->lp_ele = lpPrev(si->lp,si->lp_ele);
|
||||
@@ -1131,7 +1160,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
|
||||
/* Get the flags entry. */
|
||||
si->lp_flags = si->lp_ele;
|
||||
int flags = lpGetInteger(si->lp_ele);
|
||||
int64_t flags = lpGetInteger(si->lp_ele);
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek ID. */
|
||||
|
||||
/* Get the ID: it is encoded as difference between the master
|
||||
@@ -1240,7 +1269,7 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
|
||||
* deleted entries in the listpack header.
|
||||
*
|
||||
* We start flagging: */
|
||||
int flags = lpGetInteger(si->lp_flags);
|
||||
int64_t flags = lpGetInteger(si->lp_flags);
|
||||
flags |= STREAM_ITEM_FLAG_DELETED;
|
||||
lp = lpReplaceInteger(lp,&si->lp_flags,flags);
|
||||
|
||||
@@ -1796,11 +1825,13 @@ void xaddCommand(client *c) {
|
||||
/* Append using the low level function and return the ID. */
|
||||
streamID id;
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
&id, parsed_args.id_given ? &parsed_args.id : NULL)
|
||||
== C_ERR)
|
||||
&id, parsed_args.id_given ? &parsed_args.id : NULL) == C_ERR)
|
||||
{
|
||||
addReplyError(c,"The ID specified in XADD is equal or smaller than the "
|
||||
"target stream top item");
|
||||
if (errno == EDOM)
|
||||
addReplyError(c,"The ID specified in XADD is equal or smaller than "
|
||||
"the target stream top item");
|
||||
else
|
||||
addReplyError(c,"Elements are too large to be stored");
|
||||
return;
|
||||
}
|
||||
addReplyStreamID(c,&id);
|
||||
@@ -3233,7 +3264,7 @@ void xtrimCommand(client *c) {
|
||||
|
||||
/* Argument parsing. */
|
||||
streamAddTrimArgs parsed_args;
|
||||
if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 1) < 0)
|
||||
if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 0) < 0)
|
||||
return; /* streamParseAddOrTrimArgsOrReply already replied. */
|
||||
|
||||
/* If the key does not exist, we are ok returning zero, that is, the
|
||||
@@ -3562,7 +3593,7 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||
/* In non-deep mode we just validated the listpack header (encoded size) */
|
||||
if (!deep) return 1;
|
||||
|
||||
next = p = lpFirst(lp);
|
||||
next = p = lpValidateFirst(lp);
|
||||
if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
if (!p) return 0;
|
||||
|
||||
@@ -3600,7 +3631,11 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
|
||||
/* entry id */
|
||||
lpGetIntegerIfValid(p, &valid_record);
|
||||
if (!valid_record) return 0;
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
lpGetIntegerIfValid(p, &valid_record);
|
||||
if (!valid_record) return 0;
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) {
|
||||
|
||||
+28
-7
@@ -37,8 +37,14 @@ int getGenericCommand(client *c);
|
||||
* String Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
static int checkStringLength(client *c, long long size) {
|
||||
if (!(c->flags & CLIENT_MASTER) && size > server.proto_max_bulk_len) {
|
||||
static int checkStringLength(client *c, long long size, long long append) {
|
||||
if (c->flags & CLIENT_MASTER)
|
||||
return C_OK;
|
||||
/* 'uint64_t' cast is there just to prevent undefined behavior on overflow */
|
||||
long long total = (uint64_t)size + append;
|
||||
/* Test configured max-bulk-len represending a limit of the biggest string object,
|
||||
* and also test for overflow. */
|
||||
if (total > server.proto_max_bulk_len || total < size || total < append) {
|
||||
addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -443,7 +449,7 @@ void setrangeCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
|
||||
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
|
||||
return;
|
||||
|
||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
|
||||
@@ -463,7 +469,7 @@ void setrangeCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
|
||||
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
|
||||
return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
@@ -687,8 +693,7 @@ void appendCommand(client *c) {
|
||||
|
||||
/* "append" is an argument, so always an sds */
|
||||
append = c->argv[2];
|
||||
totlen = stringObjectLen(o)+sdslen(append->ptr);
|
||||
if (checkStringLength(c,totlen) != C_OK)
|
||||
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
|
||||
return;
|
||||
|
||||
/* Append the value */
|
||||
@@ -797,6 +802,12 @@ void stralgoLCS(client *c) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Detect string truncation or later overflows. */
|
||||
if (sdslen(a) >= UINT32_MAX-1 || sdslen(b) >= UINT32_MAX-1) {
|
||||
addReplyError(c, "String too long for LCS");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
@@ -805,9 +816,19 @@ void stralgoLCS(client *c) {
|
||||
/* Setup an uint32_t array to store at LCS[i,j] the length of the
|
||||
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
|
||||
* we index it as LCS[j+(blen+1)*j] */
|
||||
uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
|
||||
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
|
||||
|
||||
/* Try to allocate the LCS table, and abort on overflow or insufficient memory. */
|
||||
unsigned long long lcssize = (unsigned long long)(alen+1)*(blen+1); /* Can't overflow due to the size limits above. */
|
||||
unsigned long long lcsalloc = lcssize * sizeof(uint32_t);
|
||||
uint32_t *lcs = NULL;
|
||||
if (lcsalloc < SIZE_MAX && lcsalloc / lcssize == sizeof(uint32_t))
|
||||
lcs = ztrymalloc(lcsalloc);
|
||||
if (!lcs) {
|
||||
addReplyError(c, "Insufficient memory");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Start building the LCS table. */
|
||||
for (uint32_t i = 0; i <= alen; i++) {
|
||||
for (uint32_t j = 0; j <= blen; j++) {
|
||||
|
||||
+109
-46
@@ -720,8 +720,8 @@ zskiplistNode *zslLastInLexRange(zskiplist *zsl, zlexrangespec *range) {
|
||||
|
||||
double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
|
||||
char buf[128];
|
||||
if (vlen > sizeof(buf))
|
||||
vlen = sizeof(buf);
|
||||
if (vlen > sizeof(buf) - 1)
|
||||
vlen = sizeof(buf) - 1;
|
||||
memcpy(buf,vstr,vlen);
|
||||
buf[vlen] = '\0';
|
||||
return strtod(buf,NULL);
|
||||
@@ -1192,9 +1192,10 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
zs->zsl = zslCreate();
|
||||
|
||||
eptr = ziplistIndex(zl,0);
|
||||
serverAssertWithInfo(NULL,zobj,eptr != NULL);
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssertWithInfo(NULL,zobj,sptr != NULL);
|
||||
if (eptr != NULL) {
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssertWithInfo(NULL,zobj,sptr != NULL);
|
||||
}
|
||||
|
||||
while (eptr != NULL) {
|
||||
score = zzlGetScore(sptr);
|
||||
@@ -1242,15 +1243,18 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
}
|
||||
|
||||
/* Convert the sorted set object into a ziplist if it is not already a ziplist
|
||||
* and if the number of elements and the maximum element size is within the
|
||||
* expected ranges. */
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
|
||||
* and if the number of elements and the maximum element size and total elements size
|
||||
* are within the expected ranges. */
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
|
||||
zset *zset = zobj->ptr;
|
||||
|
||||
if (zset->zsl->length <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
maxelelen <= server.zset_max_ziplist_value &&
|
||||
ziplistSafeToAdd(NULL, totelelen))
|
||||
{
|
||||
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return (by reference) the score of the specified member of the sorted set
|
||||
@@ -1370,20 +1374,28 @@ int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, dou
|
||||
}
|
||||
return 1;
|
||||
} else if (!xx) {
|
||||
/* Optimize: check if the element is too large or the list
|
||||
/* check if the element is too large or the list
|
||||
* becomes too long *before* executing zzlInsert. */
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value)
|
||||
if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value ||
|
||||
!ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
|
||||
{
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (newscore) *newscore = score;
|
||||
*out_flags |= ZADD_OUT_ADDED;
|
||||
return 1;
|
||||
} else {
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (newscore) *newscore = score;
|
||||
*out_flags |= ZADD_OUT_ADDED;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
*out_flags |= ZADD_OUT_NOP;
|
||||
return 1;
|
||||
}
|
||||
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
}
|
||||
|
||||
/* Note that the above block handling ziplist would have either returned or
|
||||
* converted the key to skiplist. */
|
||||
if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
zskiplistNode *znode;
|
||||
dictEntry *de;
|
||||
@@ -2361,7 +2373,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
|
||||
}
|
||||
}
|
||||
|
||||
static int zsetDictGetMaxElementLength(dict *d) {
|
||||
static size_t zsetDictGetMaxElementLength(dict *d, size_t *totallen) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
size_t maxelelen = 0;
|
||||
@@ -2371,6 +2383,8 @@ static int zsetDictGetMaxElementLength(dict *d) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
if (sdslen(ele) > maxelelen) maxelelen = sdslen(ele);
|
||||
if (totallen)
|
||||
(*totallen) += sdslen(ele);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
@@ -2378,7 +2392,7 @@ static int zsetDictGetMaxElementLength(dict *d) {
|
||||
return maxelelen;
|
||||
}
|
||||
|
||||
static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* DIFF Algorithm 1:
|
||||
*
|
||||
* We perform the diff by iterating all the elements of the first set,
|
||||
@@ -2426,13 +2440,14 @@ static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *
|
||||
znode = zslInsert(dstzset->zsl,zval.score,tmp);
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
if (sdslen(tmp) > *maxelelen) *maxelelen = sdslen(tmp);
|
||||
(*totelelen) += sdslen(tmp);
|
||||
}
|
||||
}
|
||||
zuiClearIterator(&src[0]);
|
||||
}
|
||||
|
||||
|
||||
static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* DIFF Algorithm 2:
|
||||
*
|
||||
* Add all the elements of the first set to the auxiliary set.
|
||||
@@ -2486,7 +2501,7 @@ static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *
|
||||
|
||||
/* Using this algorithm, we can't calculate the max element as we go,
|
||||
* we have to iterate through all elements to find the max one after. */
|
||||
*maxelelen = zsetDictGetMaxElementLength(dstzset->dict);
|
||||
*maxelelen = zsetDictGetMaxElementLength(dstzset->dict, totelelen);
|
||||
}
|
||||
|
||||
static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
|
||||
@@ -2523,14 +2538,14 @@ static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
|
||||
return (algo_one_work <= algo_two_work) ? 1 : 2;
|
||||
}
|
||||
|
||||
static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* Skip everything if the smallest input is empty. */
|
||||
if (zuiLength(&src[0]) > 0) {
|
||||
int diff_algo = zsetChooseDiffAlgorithm(src, setnum);
|
||||
if (diff_algo == 1) {
|
||||
zdiffAlgorithm1(src, setnum, dstzset, maxelelen);
|
||||
zdiffAlgorithm1(src, setnum, dstzset, maxelelen, totelelen);
|
||||
} else if (diff_algo == 2) {
|
||||
zdiffAlgorithm2(src, setnum, dstzset, maxelelen);
|
||||
zdiffAlgorithm2(src, setnum, dstzset, maxelelen, totelelen);
|
||||
} else if (diff_algo != 0) {
|
||||
serverPanic("Unknown algorithm");
|
||||
}
|
||||
@@ -2565,7 +2580,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
zsetopsrc *src;
|
||||
zsetopval zval;
|
||||
sds tmp;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
robj *dstobj;
|
||||
zset *dstzset;
|
||||
zskiplistNode *znode;
|
||||
@@ -2701,6 +2716,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
tmp = zuiNewSdsFromValue(&zval);
|
||||
znode = zslInsert(dstzset->zsl,score,tmp);
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
}
|
||||
}
|
||||
@@ -2737,6 +2753,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
/* Remember the longest single element encountered,
|
||||
* to understand if it's possible to convert to ziplist
|
||||
* at the end. */
|
||||
totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
/* Update the element with its initial score. */
|
||||
dictSetKey(accumulator, de, tmp);
|
||||
@@ -2771,14 +2788,14 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
dictReleaseIterator(di);
|
||||
dictRelease(accumulator);
|
||||
} else if (op == SET_OP_DIFF) {
|
||||
zdiff(src, setnum, dstzset, &maxelelen);
|
||||
zdiff(src, setnum, dstzset, &maxelelen, &totelelen);
|
||||
} else {
|
||||
serverPanic("Unknown operator");
|
||||
}
|
||||
|
||||
if (dstkey) {
|
||||
if (dstzset->zsl->length) {
|
||||
zsetConvertToZiplistIfNeeded(dstobj, maxelelen);
|
||||
zsetConvertToZiplistIfNeeded(dstobj, maxelelen, totelelen);
|
||||
setKey(c, c->db, dstkey, dstobj);
|
||||
addReplyLongLong(c, zsetLength(dstobj));
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
@@ -2854,7 +2871,7 @@ typedef enum {
|
||||
|
||||
typedef struct zrange_result_handler zrange_result_handler;
|
||||
|
||||
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c);
|
||||
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c, long length);
|
||||
typedef void (*zrangeResultFinalizeFunction)(
|
||||
zrange_result_handler *c, size_t result_count);
|
||||
typedef void (*zrangeResultEmitCBufferFunction)(
|
||||
@@ -2882,8 +2899,22 @@ struct zrange_result_handler {
|
||||
zrangeResultEmitLongLongFunction emitResultFromLongLong;
|
||||
};
|
||||
|
||||
/* Result handler methods for responding the ZRANGE to clients. */
|
||||
static void zrangeResultBeginClient(zrange_result_handler *handler) {
|
||||
/* Result handler methods for responding the ZRANGE to clients.
|
||||
* length can be used to provide the result length in advance (avoids deferred reply overhead).
|
||||
* length can be set to -1 if the result length is not know in advance.
|
||||
*/
|
||||
static void zrangeResultBeginClient(zrange_result_handler *handler, long length) {
|
||||
if (length > 0) {
|
||||
/* In case of WITHSCORES, respond with a single array in RESP2, and
|
||||
* nested arrays in RESP3. We can't use a map response type since the
|
||||
* client library needs to know to respect the order. */
|
||||
if (handler->withscores && (handler->client->resp == 2)) {
|
||||
length *= 2;
|
||||
}
|
||||
addReplyArrayLen(handler->client, length);
|
||||
handler->userdata = NULL;
|
||||
return;
|
||||
}
|
||||
handler->userdata = addReplyDeferredLen(handler->client);
|
||||
}
|
||||
|
||||
@@ -2918,6 +2949,9 @@ static void zrangeResultEmitLongLongToClient(zrange_result_handler *handler,
|
||||
static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
size_t result_count)
|
||||
{
|
||||
/* If the reply size was know at start there's nothing left to do */
|
||||
if (!handler->userdata)
|
||||
return;
|
||||
/* In case of WITHSCORES, respond with a single array in RESP2, and
|
||||
* nested arrays in RESP3. We can't use a map response type since the
|
||||
* client library needs to know to respect the order. */
|
||||
@@ -2929,9 +2963,12 @@ static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
}
|
||||
|
||||
/* Result handler methods for storing the ZRANGESTORE to a zset. */
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler)
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler, long length)
|
||||
{
|
||||
handler->dstobj = createZsetZiplistObject();
|
||||
if (length > (long)server.zset_max_ziplist_entries)
|
||||
handler->dstobj = createZsetObject();
|
||||
else
|
||||
handler->dstobj = createZsetZiplistObject();
|
||||
}
|
||||
|
||||
static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
|
||||
@@ -3025,11 +3062,11 @@ void genericZrangebyrankCommand(zrange_result_handler *handler,
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
|
||||
/* 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) {
|
||||
handler->beginResultEmission(handler, 0);
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
return;
|
||||
}
|
||||
@@ -3037,6 +3074,7 @@ void genericZrangebyrankCommand(zrange_result_handler *handler,
|
||||
rangelen = (end-start)+1;
|
||||
result_cardinality = rangelen;
|
||||
|
||||
handler->beginResultEmission(handler, rangelen);
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
@@ -3132,7 +3170,7 @@ void genericZrangebyscoreCommand(zrange_result_handler *handler,
|
||||
client *c = handler->client;
|
||||
unsigned long rangelen = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
/* For invalid offset, return directly. */
|
||||
if (offset > 0 && offset >= (long)zsetLength(zobj)) {
|
||||
@@ -3421,7 +3459,7 @@ void genericZrangebylexCommand(zrange_result_handler *handler,
|
||||
client *c = handler->client;
|
||||
unsigned long rangelen = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
@@ -3663,7 +3701,12 @@ void zrangeGenericCommand(zrange_result_handler *handler, int argc_start, int st
|
||||
lookupKeyWrite(c->db,key) :
|
||||
lookupKeyRead(c->db,key);
|
||||
if (zobj == NULL) {
|
||||
addReply(c,shared.emptyarray);
|
||||
if (store) {
|
||||
handler->beginResultEmission(handler, -1);
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
} else {
|
||||
addReply(c, shared.emptyarray);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -3820,11 +3863,16 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
long arraylen = 0;
|
||||
long result_count = 0;
|
||||
|
||||
/* We emit the key only for the blocking variant. */
|
||||
if (emitkey) addReplyBulk(c,key);
|
||||
|
||||
/* Respond with a single (flat) array in RESP2 or if countarg is not
|
||||
* provided (returning a single element). In RESP3, when countarg is
|
||||
* provided, use nested array. */
|
||||
int use_nested_array = c->resp > 2 && countarg != NULL;
|
||||
|
||||
/* Remove the element. */
|
||||
do {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -3867,16 +3915,19 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
serverAssertWithInfo(c,zobj,zsetDel(zobj,ele));
|
||||
server.dirty++;
|
||||
|
||||
if (arraylen == 0) { /* Do this only for the first iteration. */
|
||||
if (result_count == 0) { /* Do this only for the first iteration. */
|
||||
char *events[2] = {"zpopmin","zpopmax"};
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
}
|
||||
|
||||
if (use_nested_array) {
|
||||
addReplyArrayLen(c,2);
|
||||
}
|
||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
||||
addReplyDouble(c,score);
|
||||
sdsfree(ele);
|
||||
arraylen += 2;
|
||||
++result_count;
|
||||
|
||||
/* Remove the key, if indeed needed. */
|
||||
if (zsetLength(zobj) == 0) {
|
||||
@@ -3886,7 +3937,10 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
} while(--count);
|
||||
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
|
||||
if (!use_nested_array) {
|
||||
result_count *= 2;
|
||||
}
|
||||
setDeferredArrayLen(c,arraylen_ptr,result_count + (emitkey != 0));
|
||||
}
|
||||
|
||||
/* ZPOPMIN key [<count>] */
|
||||
@@ -3987,7 +4041,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
int uniq = 1;
|
||||
robj *zsetobj;
|
||||
|
||||
if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray))
|
||||
== NULL || checkType(c, zsetobj, OBJ_ZSET)) return;
|
||||
size = zsetLength(zsetobj);
|
||||
|
||||
@@ -4023,7 +4077,9 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c, key, sdslen(key));
|
||||
if (withscores)
|
||||
addReplyDouble(c, dictGetDoubleVal(de));
|
||||
addReplyDouble(c, *(double*)dictGetVal(de));
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
break;
|
||||
}
|
||||
} else if (zsetobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
ziplistEntry *keys, *vals = NULL;
|
||||
@@ -4037,6 +4093,8 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
count -= sample_count;
|
||||
ziplistRandomPairs(zsetobj->ptr, sample_count, keys, vals);
|
||||
zarndmemberReplyWithZiplist(c, sample_count, keys, vals);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
break;
|
||||
}
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
@@ -4172,7 +4230,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ZRANDMEMBER [<count> WITHSCORES] */
|
||||
/* ZRANDMEMBER key [<count> [WITHSCORES]] */
|
||||
void zrandmemberCommand(client *c) {
|
||||
long l;
|
||||
int withscores = 0;
|
||||
@@ -4180,12 +4238,17 @@ void zrandmemberCommand(client *c) {
|
||||
ziplistEntry ele;
|
||||
|
||||
if (c->argc >= 3) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||
if (getRangeLongFromObjectOrReply(c,c->argv[2],-LONG_MAX,LONG_MAX,&l,NULL) != C_OK) return;
|
||||
if (c->argc > 4 || (c->argc == 4 && strcasecmp(c->argv[3]->ptr,"withscores"))) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
} else if (c->argc == 4)
|
||||
} else if (c->argc == 4) {
|
||||
withscores = 1;
|
||||
if (l < -LONG_MAX/2 || l > LONG_MAX/2) {
|
||||
addReplyError(c,"value is out of range");
|
||||
return;
|
||||
}
|
||||
}
|
||||
zrandmemberWithCountCommand(c, l, withscores);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/pem.h>
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#include <openssl/decoder.h>
|
||||
#endif
|
||||
|
||||
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
|
||||
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
|
||||
@@ -146,12 +149,13 @@ void tlsInit(void) {
|
||||
*/
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
OPENSSL_config(NULL);
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x10101000L
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
|
||||
#else
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_ATFORK, NULL);
|
||||
#endif
|
||||
ERR_load_crypto_strings();
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
|
||||
#ifdef USE_CRYPTO_LOCKS
|
||||
initCryptoLocks();
|
||||
@@ -174,7 +178,8 @@ void tlsCleanup(void) {
|
||||
redis_tls_client_ctx = NULL;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
|
||||
// unavailable on LibreSSL
|
||||
OPENSSL_cleanup();
|
||||
#endif
|
||||
}
|
||||
@@ -320,20 +325,46 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
if (ctx_config->prefer_server_ciphers)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
|
||||
#if defined(SSL_CTX_set_ecdh_auto)
|
||||
#if ((OPENSSL_VERSION_NUMBER < 0x30000000L) && defined(SSL_CTX_set_ecdh_auto))
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
#endif
|
||||
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
|
||||
|
||||
if (ctx_config->dh_params_file) {
|
||||
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
|
||||
DH *dh = NULL;
|
||||
if (!dhfile) {
|
||||
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
|
||||
EVP_PKEY *pkey = NULL;
|
||||
OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey(
|
||||
&pkey, "PEM", NULL, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
|
||||
if (!dctx) {
|
||||
serverLog(LL_WARNING, "No decoder for DH params.");
|
||||
fclose(dhfile);
|
||||
goto error;
|
||||
}
|
||||
if (!OSSL_DECODER_from_fp(dctx, dhfile)) {
|
||||
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
|
||||
OSSL_DECODER_CTX_free(dctx);
|
||||
fclose(dhfile);
|
||||
goto error;
|
||||
}
|
||||
|
||||
OSSL_DECODER_CTX_free(dctx);
|
||||
fclose(dhfile);
|
||||
|
||||
if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) <= 0) {
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
|
||||
EVP_PKEY_free(pkey);
|
||||
goto error;
|
||||
}
|
||||
/* Not freeing pkey, it is owned by OpenSSL now */
|
||||
#else
|
||||
DH *dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
|
||||
fclose(dhfile);
|
||||
if (!dh) {
|
||||
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
|
||||
@@ -348,6 +379,11 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
}
|
||||
|
||||
DH_free(dh);
|
||||
#endif
|
||||
} else {
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
|
||||
SSL_CTX_set_dh_auto(ctx, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If a client-side certificate is configured, create an explicit client context */
|
||||
@@ -679,6 +715,8 @@ static void connTLSClose(connection *conn_) {
|
||||
tls_connection *conn = (tls_connection *) conn_;
|
||||
|
||||
if (conn->ssl) {
|
||||
if (conn->c.state == CONN_STATE_CONNECTED)
|
||||
SSL_shutdown(conn->ssl);
|
||||
SSL_free(conn->ssl);
|
||||
conn->ssl = NULL;
|
||||
}
|
||||
|
||||
+58
-13
@@ -260,6 +260,9 @@ void trackingRememberKeys(client *c) {
|
||||
* - Following a flush command, to send a single RESP NULL to indicate
|
||||
* that all keys are now invalid. */
|
||||
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
|
||||
int using_redirection = 0;
|
||||
if (c->client_tracking_redirection) {
|
||||
client *redir = lookupClientByID(c->client_tracking_redirection);
|
||||
@@ -273,10 +276,14 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
addReplyBulkCBuffer(c,"tracking-redir-broken",21);
|
||||
addReplyLongLong(c,c->client_tracking_redirection);
|
||||
}
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
return;
|
||||
}
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
c = redir;
|
||||
using_redirection = 1;
|
||||
old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Only send such info for clients in RESP version 3 or more. However
|
||||
@@ -295,6 +302,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
* redirecting to another client. We can't send anything to
|
||||
* it since RESP2 does not support push messages in the same
|
||||
* connection. */
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -305,6 +313,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
addReplyArrayLen(c,1);
|
||||
addReplyBulkCBuffer(c,keyname,keylen);
|
||||
}
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* This function is called when a key is modified in Redis and in the case
|
||||
@@ -326,7 +335,7 @@ void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
|
||||
* tree. This way we know who was the client that did the last
|
||||
* change to the key, and can avoid sending the notification in the
|
||||
* case the client is in NOLOOP mode. */
|
||||
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
|
||||
raxInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
|
||||
}
|
||||
raxStop(&ri);
|
||||
}
|
||||
@@ -347,13 +356,16 @@ void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
|
||||
* of memory pressure: in that case the key didn't really change, so we want
|
||||
* just to notify the clients that are in the table for this key, that would
|
||||
* otherwise miss the fact we are no longer tracking the key for them. */
|
||||
void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
void trackingInvalidateKey(client *c, robj *keyobj, int bcast) {
|
||||
if (TrackingTable == NULL) return;
|
||||
|
||||
if (bcast && raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(c,key,keylen);
|
||||
unsigned char *key = (unsigned char*)keyobj->ptr;
|
||||
size_t keylen = sdslen(keyobj->ptr);
|
||||
|
||||
rax *ids = raxFind(TrackingTable,(unsigned char*)key,keylen);
|
||||
if (bcast && raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(c,(char *)key,keylen);
|
||||
|
||||
rax *ids = raxFind(TrackingTable,key,keylen);
|
||||
if (ids == raxNotFound) return;
|
||||
|
||||
raxIterator ri;
|
||||
@@ -378,12 +390,20 @@ void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
/* If the client enabled the NOLOOP mode, don't send notifications
|
||||
* about keys changed by the client itself. */
|
||||
if (target->flags & CLIENT_TRACKING_NOLOOP &&
|
||||
target == c)
|
||||
target == server.current_client)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sendTrackingMessage(target,key,keylen,0);
|
||||
/* If target is current client and it's executing a command, we need schedule key invalidation.
|
||||
* As the invalidation messages may be interleaved with command
|
||||
* response and should after command response. */
|
||||
if (target == server.current_client && server.fixed_time_expire) {
|
||||
incrRefCount(keyobj);
|
||||
listAddNodeTail(server.tracking_pending_keys, keyobj);
|
||||
} else {
|
||||
sendTrackingMessage(target,(char *)keyobj->ptr,sdslen(keyobj->ptr),0);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
@@ -394,10 +414,28 @@ void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
raxRemove(TrackingTable,(unsigned char*)key,keylen,NULL);
|
||||
}
|
||||
|
||||
/* Wrapper (the one actually called across the core) to pass the key
|
||||
* as object. */
|
||||
void trackingInvalidateKey(client *c, robj *keyobj) {
|
||||
trackingInvalidateKeyRaw(c,keyobj->ptr,sdslen(keyobj->ptr),1);
|
||||
void trackingHandlePendingKeyInvalidations() {
|
||||
if (!listLength(server.tracking_pending_keys)) return;
|
||||
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.tracking_pending_keys,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
robj *key = listNodeValue(ln);
|
||||
/* current_client maybe freed, so we need to send invalidation
|
||||
* message only when current_client is still alive */
|
||||
if (server.current_client != NULL) {
|
||||
if (key != NULL) {
|
||||
sendTrackingMessage(server.current_client,(char *)key->ptr,sdslen(key->ptr),0);
|
||||
} else {
|
||||
sendTrackingMessage(server.current_client,shared.null[server.current_client->resp]->ptr,
|
||||
sdslen(shared.null[server.current_client->resp]->ptr),1);
|
||||
}
|
||||
}
|
||||
if (key != NULL) decrRefCount(key);
|
||||
}
|
||||
listEmpty(server.tracking_pending_keys);
|
||||
}
|
||||
|
||||
/* This function is called when one or all the Redis databases are
|
||||
@@ -424,7 +462,12 @@ void trackingInvalidateKeysOnFlush(int async) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
sendTrackingMessage(c,shared.null[c->resp]->ptr,sdslen(shared.null[c->resp]->ptr),1);
|
||||
if (c == server.current_client) {
|
||||
/* We use a special NULL to indicate that we should send null */
|
||||
listAddNodeTail(server.tracking_pending_keys,NULL);
|
||||
} else {
|
||||
sendTrackingMessage(c,shared.null[c->resp]->ptr,sdslen(shared.null[c->resp]->ptr),1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,7 +517,9 @@ void trackingLimitUsedSlots(void) {
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
raxRandomWalk(&ri,0);
|
||||
if (raxEOF(&ri)) break;
|
||||
trackingInvalidateKeyRaw(NULL,(char*)ri.key,ri.key_len,0);
|
||||
robj *keyobj = createStringObject((char*)ri.key,ri.key_len);
|
||||
trackingInvalidateKey(NULL,keyobj,0);
|
||||
decrRefCount(keyobj);
|
||||
if (raxSize(TrackingTable) <= max_keys) {
|
||||
timeout_counter = 0;
|
||||
raxStop(&ri);
|
||||
|
||||
+26
-4
@@ -45,9 +45,12 @@
|
||||
#include "sha256.h"
|
||||
|
||||
/* Glob-style pattern matching. */
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase)
|
||||
static int stringmatchlen_impl(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase, int *skipLongerMatches, int nesting)
|
||||
{
|
||||
/* Protection against abusive patterns. */
|
||||
if (nesting > 1000) return 0;
|
||||
|
||||
while(patternLen && stringLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
@@ -58,12 +61,25 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
if (patternLen == 1)
|
||||
return 1; /* match */
|
||||
while(stringLen) {
|
||||
if (stringmatchlen(pattern+1, patternLen-1,
|
||||
string, stringLen, nocase))
|
||||
if (stringmatchlen_impl(pattern+1, patternLen-1,
|
||||
string, stringLen, nocase, skipLongerMatches, nesting+1))
|
||||
return 1; /* match */
|
||||
if (*skipLongerMatches)
|
||||
return 0; /* no match */
|
||||
string++;
|
||||
stringLen--;
|
||||
}
|
||||
/* There was no match for the rest of the pattern starting
|
||||
* from anywhere in the rest of the string. If there were
|
||||
* any '*' earlier in the pattern, we can terminate the
|
||||
* search early without trying to match them to longer
|
||||
* substrings. This is because a longer match for the
|
||||
* earlier part of the pattern would require the rest of the
|
||||
* pattern to match starting later in the string, and we
|
||||
* have just determined that there is no match for the rest
|
||||
* of the pattern starting from anywhere in the current
|
||||
* string. */
|
||||
*skipLongerMatches = 1;
|
||||
return 0; /* no match */
|
||||
break;
|
||||
case '?':
|
||||
@@ -165,6 +181,12 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase) {
|
||||
int skipLongerMatches = 0;
|
||||
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches,0);
|
||||
}
|
||||
|
||||
int stringmatch(const char *pattern, const char *string, int nocase) {
|
||||
return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
#define REDIS_VERSION "255.255.255"
|
||||
#define REDIS_VERSION_NUM 0x00ffffff
|
||||
#define REDIS_VERSION "6.2.19"
|
||||
#define REDIS_VERSION_NUM 0x00060213
|
||||
|
||||
+29
-8
@@ -263,10 +263,21 @@
|
||||
* to stay there to signal that a full scan is needed to get the number of
|
||||
* items inside the ziplist. */
|
||||
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
|
||||
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
|
||||
if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) \
|
||||
ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
|
||||
}
|
||||
|
||||
/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
|
||||
* zlbytes*/
|
||||
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
|
||||
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
|
||||
size_t len = zl? ziplistBlobLen(zl): 0;
|
||||
if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* We use this function to receive information about a ziplist entry.
|
||||
* Note that this is not how the data is actually encoded, is just what we
|
||||
* get filled by a function in order to operate more easily. */
|
||||
@@ -405,10 +416,10 @@ unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, uns
|
||||
(len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \
|
||||
} else if ((encoding) == ZIP_STR_32B) { \
|
||||
(lensize) = 5; \
|
||||
(len) = ((ptr)[1] << 24) | \
|
||||
((ptr)[2] << 16) | \
|
||||
((ptr)[3] << 8) | \
|
||||
((ptr)[4]); \
|
||||
(len) = ((uint32_t)(ptr)[1] << 24) | \
|
||||
((uint32_t)(ptr)[2] << 16) | \
|
||||
((uint32_t)(ptr)[3] << 8) | \
|
||||
((uint32_t)(ptr)[4]); \
|
||||
} else { \
|
||||
(lensize) = 0; /* bad encoding, should be covered by a previous */ \
|
||||
(len) = 0; /* ZIP_ASSERT_ENCODING / zipEncodingLenSize, or */ \
|
||||
@@ -546,7 +557,7 @@ void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding) {
|
||||
memcpy(p,&i16,sizeof(i16));
|
||||
memrev16ifbe(p);
|
||||
} else if (encoding == ZIP_INT_24B) {
|
||||
i32 = value<<8;
|
||||
i32 = ((uint64_t)value)<<8;
|
||||
memrev32ifbe(&i32);
|
||||
memcpy(p,((uint8_t*)&i32)+1,sizeof(i32)-sizeof(uint8_t));
|
||||
} else if (encoding == ZIP_INT_32B) {
|
||||
@@ -709,7 +720,8 @@ unsigned char *ziplistNew(void) {
|
||||
}
|
||||
|
||||
/* Resize the ziplist. */
|
||||
unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
unsigned char *ziplistResize(unsigned char *zl, size_t len) {
|
||||
assert(len < UINT32_MAX);
|
||||
zl = zrealloc(zl,len);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(len);
|
||||
zl[len-1] = ZIP_END;
|
||||
@@ -1070,6 +1082,9 @@ unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
|
||||
/* Combined zl length should be limited within UINT16_MAX */
|
||||
zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;
|
||||
|
||||
/* larger values can't be stored into ZIPLIST_BYTES */
|
||||
assert(zlbytes < UINT32_MAX);
|
||||
|
||||
/* Save offset positions before we start ripping memory apart. */
|
||||
size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
|
||||
size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));
|
||||
@@ -1145,6 +1160,8 @@ unsigned char *ziplistIndex(unsigned char *zl, int index) {
|
||||
/* No need for "safe" check: when going backwards, we know the header
|
||||
* we're parsing is in the range, we just need to assert (below) that
|
||||
* the size we take doesn't cause p to go outside the allocation. */
|
||||
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
|
||||
assert(p + prevlensize < zl + zlbytes - ZIPLIST_END_SIZE);
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
while (prevlen > 0 && index--) {
|
||||
p -= prevlen;
|
||||
@@ -1522,8 +1539,12 @@ int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Make sure 'p' really does point to the end of the ziplist. */
|
||||
if (p != zl + bytes - ZIPLIST_END_SIZE)
|
||||
return 0;
|
||||
|
||||
/* Make sure the <zltail> entry really do point to the start of the last entry. */
|
||||
if (prev != ZIPLIST_ENTRY_TAIL(zl))
|
||||
if (prev != NULL && prev != ZIPLIST_ENTRY_TAIL(zl))
|
||||
return 0;
|
||||
|
||||
/* Check that the count in the header is correct */
|
||||
|
||||
@@ -65,6 +65,7 @@ int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
|
||||
void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val);
|
||||
void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||
unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||
int ziplistSafeToAdd(unsigned char* zl, size_t add);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int ziplistTest(int argc, char *argv[], int accurate);
|
||||
|
||||
@@ -430,6 +430,9 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check that the zipmap is not empty. */
|
||||
if (count == 0) return 0;
|
||||
|
||||
/* check that the count in the header is correct */
|
||||
if (zm[0] != ZIPMAP_BIGLEN && zm[0] != count)
|
||||
return 0;
|
||||
|
||||
+2
-2
@@ -430,12 +430,12 @@ size_t zmalloc_get_rss(void) {
|
||||
size_t infolen = sizeof(info);
|
||||
int mib[6];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[1] = KERN_PROC2;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = getpid();
|
||||
mib[4] = sizeof(info);
|
||||
mib[5] = 1;
|
||||
if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
|
||||
if (sysctl(mib, __arraycount(mib), &info, &infolen, NULL, 0) == 0)
|
||||
return (size_t)info.p_vm_rssize * getpagesize();
|
||||
|
||||
return 0L;
|
||||
|
||||
Binary file not shown.
@@ -12,6 +12,7 @@ set ::tlsdir "tests/tls"
|
||||
# blocking.
|
||||
proc bg_block_op {host port db ops tls} {
|
||||
set r [redis $host $port 0 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select $db
|
||||
|
||||
for {set j 0} {$j < $ops} {incr j} {
|
||||
|
||||
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
|
||||
|
||||
proc bg_complex_data {host port db ops tls} {
|
||||
set r [redis $host $port 0 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select $db
|
||||
createComplexDataset $r $ops
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# A fake Redis node for replaying predefined/expected traffic with a client.
|
||||
#
|
||||
# Usage: tclsh fake_redis_node.tcl PORT COMMAND REPLY [ COMMAND REPLY [ ... ] ]
|
||||
#
|
||||
# Commands are given as space-separated strings, e.g. "GET foo", and replies as
|
||||
# RESP-encoded replies minus the trailing \r\n, e.g. "+OK".
|
||||
|
||||
set port [lindex $argv 0];
|
||||
set expected_traffic [lrange $argv 1 end];
|
||||
|
||||
# Reads and parses a command from a socket and returns it as a space-separated
|
||||
# string, e.g. "set foo bar".
|
||||
proc read_command {sock} {
|
||||
set char [read $sock 1]
|
||||
switch $char {
|
||||
* {
|
||||
set numargs [gets $sock]
|
||||
set result {}
|
||||
for {set i 0} {$i<$numargs} {incr i} {
|
||||
read $sock 1; # dollar sign
|
||||
set len [gets $sock]
|
||||
set str [read $sock $len]
|
||||
gets $sock; # trailing \r\n
|
||||
lappend result $str
|
||||
}
|
||||
return $result
|
||||
}
|
||||
{} {
|
||||
# EOF
|
||||
return {}
|
||||
}
|
||||
default {
|
||||
# Non-RESP command
|
||||
set rest [gets $sock]
|
||||
return "$char$rest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc accept {sock host port} {
|
||||
global expected_traffic
|
||||
foreach {expect_cmd reply} $expected_traffic {
|
||||
if {[eof $sock]} {break}
|
||||
set cmd [read_command $sock]
|
||||
if {[string equal -nocase $cmd $expect_cmd]} {
|
||||
puts $sock $reply
|
||||
flush $sock
|
||||
} else {
|
||||
puts $sock "-ERR unexpected command $cmd"
|
||||
break
|
||||
}
|
||||
}
|
||||
close $sock
|
||||
}
|
||||
|
||||
socket -server accept $port
|
||||
after 5000 set done timeout
|
||||
vwait done
|
||||
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
|
||||
proc gen_write_load {host port seconds tls} {
|
||||
set start_time [clock seconds]
|
||||
set r [redis $host $port 1 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select 9
|
||||
while 1 {
|
||||
$r set [expr rand()] [expr rand()]
|
||||
|
||||
@@ -158,6 +158,18 @@ tags {"aof"} {
|
||||
assert_match "*not valid*" $result
|
||||
}
|
||||
|
||||
test "Short read: Utility should show the abnormal line num in AOF" {
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof "!!!"
|
||||
}
|
||||
|
||||
catch {
|
||||
exec src/redis-check-aof $aof_path
|
||||
} result
|
||||
assert_match "*ok_up_to_line=8*" $result
|
||||
}
|
||||
|
||||
test "Short read: Utility should be able to fix the AOF" {
|
||||
set result [exec src/redis-check-aof --fix $aof_path << "y\n"]
|
||||
assert_match "*Successfully truncated AOF*" $result
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user