Compare commits

...
369 Commits
Author SHA1 Message Date
Oran Agra ad950e4c32 Release Redis 6.2.16
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2024-10-02 23:13:48 +03:00
Oran Agra 0e9710d4dd Release Redis 6.2.15 2024-10-02 22:58:33 +03:00
Oran Agra 9317bf6465 Prevent pattern matching abuse (CVE-2024-31228) 2024-10-02 22:58:33 +03:00
Oran Agra 1f7c148be2 Fix lua bit.tohex (CVE-2024-31449)
INT_MIN value must be explicitly checked, and cannot be negated.
2024-10-02 22:58:33 +03:00
Oran Agra 91863dd854 Redis 6.2.14
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-10-18 10:43:10 +03:00
Jachin 2f27ebb414 Fix compile on macOS 13 (#12611)
Use the __MAC_OS_X_VERSION_MIN_REQUIRED macro to detect the
macOS system version instead of using MAC_OS_X_VERSION_10_6.

From MacOSX14.0.sdk, the default definitions of MAC_OS_X_VERSION_xxx have
been removed in usr/include/AvailabilityMacros.h. It includes AvailabilityVersions.h,
where the following condition must be met:
`#if (!defined(_POSIX_C_SOURCE) && !defined(_XOPEN_SOURCE)) || defined(_DARWIN_C_SOURCE)`
Only then will MAC_OS_X_VERSION_xxx be defined.
However, in the project, _DARWIN_C_SOURCE is not defined, which leads to the
loss of the definition for MAC_OS_X_VERSION_10_6.

(cherry picked from commit a2b0701d2c)
2023-10-18 10:43:10 +03:00
sundb 89391e42c8 Skip test for sdsRemoveFreeSpace when mem_allocator is not jemalloc (#11878)
Test `trim on SET with big value` (introduced from #11817) fails under mac m1 with libc mem_allocator.
The reason is that malloc(33000) will allocate 65536 bytes(>42000).
This test still passes under ubuntu with libc mem_allocator.

```
*** [err]: trim on SET with big value in tests/unit/type/string.tcl
Expected [r memory usage key] < 42000 (context: type source line 471 file /Users/iospack/data/redis_fork/tests/unit/type/string.tcl cmd {assert {[r memory usage key] < 42000}} proc ::test)
```

simple test under mac m1 with libc mem_allocator:
```c
void *p = zmalloc(33000);
printf("malloc size: %zu\n", zmalloc_size(p));

# output
malloc size: 65536
```

(cherry picked from commit 3fba3ccd96)
(cherry picked from commit 646069a900)
2023-10-18 10:43:10 +03:00
Yossi Gottlieb 8f30d2a604 Fix issue of listen before chmod on Unix sockets (CVE-2023-45145)
Before this commit, Unix socket setup performed chmod(2) on the socket
file after calling listen(2). Depending on what umask is used, this
could leave the file with the wrong permissions for a short period of
time. As a result, another process could exploit this race condition and
establish a connection that would otherwise not be possible.

We now make sure the socket permissions are set up prior to calling
listen(2).

(cherry picked from commit a11b3bc34a054818f2ac70e50adfc542ca1cba42)
(cherry picked from commit 3ca02a0c541e019d77014424e207123160520cec)
2023-10-18 10:43:10 +03:00
Oran Agra 24c0bf5c5b Redis 6.2.13
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-07-10 14:37:59 +03:00
sundb b313fa4166 Fix alpine tests (partial #12035)
This is a partial cherry-pick from Redis 7.2

1) Fixed `ps -p [pid]`  doesn't output `<defunct>` when using procps 4.x causing `replication
  child dies when parent is killed - diskless` test to fail.

(cherry picked from commit 42c8c61813)
(cherry picked from commit 63fc6c87071d12e3577fc383d5a3f25c60423c8e)
2023-07-10 14:37:59 +03:00
sundb f613873de0 Re-enable hash downsize rehashing during fork (partial #12276)
This was introduced by the recent change in #11692 which prevented a
down-sizing rehashing while there is a fork.

## Solution
1. Fix the rehashing code, so that the same as it allows rehashing for up-sizing
  during fork when the ratio is extreme, it will allow it for down-sizing as well.

Co-authored-by: Oran Agra <oran@redislabs.com>

This is a partial cherry pick of:
(cherry picked from commit b00a235186)
(cherry picked from commit d4c37320382edb342292a3e30250d46896a12016)
2023-07-10 14:37:59 +03:00
Oran AgraandYossi Gottlieb 405d14fd44 Lua cjson and cmsgpack integer overflow issues (CVE-2022-24834)
* Fix integer overflows due to using wrong integer size.
* Add assertions / panic when overflow still happens.
* Deletion of dead code to avoid need to maintain it
* Some changes are not because of bugs, but rather paranoia.
* Improve cmsgpack and cjson test coverage.

Co-authored-by: Yossi Gottlieb <yossigo@gmail.com>
2023-07-10 14:37:59 +03:00
Oran Agra 2d4b9401f1 Redis 6.22.12
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-04-17 15:54:26 +03:00
Oran Agra 521e05cb8b fix false valgrind error on new hash test (#11200)
New test fails on valgrind because strtold("+inf") with valgrind returns a non-inf result
same thing is done in incr.tcl.

(cherry picked from commit c3b7bde914)
2023-04-17 15:54:26 +03:00
Binbin 7afd1724dd Fix the bug that CLIENT REPLY OFF|SKIP cannot receive push notifications (#11875)
This bug seems to be there forever, CLIENT REPLY OFF|SKIP will
mark the client with CLIENT_REPLY_OFF or CLIENT_REPLY_SKIP flags.
With these flags, prepareClientToWrite called by addReply* will
return C_ERR directly. So the client can't receive the Pub/Sub
messages and any other push notifications, e.g client side tracking.

In this PR, we adding a CLIENT_PUSHING flag, disables the reply
silencing flags. When adding push replies, set the flag, after the reply,
clear the flag. Then add the flag check in prepareClientToWrite.

Fixes #11874

Note, the SUBSCRIBE command response is a bit awkward,
see https://github.com/redis/redis-doc/pull/2327

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 416842e6c0)
(cherry picked from commit f8ae7a414c)
2023-04-17 15:54:26 +03:00
Madelyn Olson eee173a64e Always compact nodes in stream listpacks after creating new nodes (#11885)
This change attempts to alleviate a minor memory usage degradation for Redis 6.2 and onwards when using rather large objects (~2k) in streams. Introduced in #6281, we pre-allocate the head nodes of a stream to be 4kb, to limit the amount of unnecessary initial reallocations that are done. However, if we only ever allocate one object because 2 objects exceeds the max_stream_entry_size, we never actually shrink it to fit the single item. This can lead to a lot of excessive memory usage. For smaller item sizes this becomes less of an issue, as the overhead decreases as the items become smaller in size.

This commit also changes the MEMORY USAGE of streams, since it was reporting the lpBytes instead of the allocated size. This introduced an observability issue when diagnosing the memory issue, since Redis reported the same amount of used bytes pre and post change, even though the new implementation allocated more memory.

(cherry picked from commit 2bb29e4aa3)
(cherry picked from commit 17181517ec)
2023-04-17 15:54:26 +03:00
Slava Koyfman 126348536a Disconnect pub-sub subscribers when revoking allchannels permission (#11992)
The existing logic for killing pub-sub clients did not handle the `allchannels`
permission correctly. For example, if you:

    ACL SETUSER foo allchannels

Have a client authenticate as the user `foo` and subscribe to a channel, and then:

    ACL SETUSER foo resetchannels

The subscribed client would not be disconnected, though new clients under that user
would be blocked from subscribing to any channels.

This was caused by an incomplete optimization in `ACLKillPubsubClientsIfNeeded`
checking whether the new channel permissions were a strict superset of the old ones.

(cherry picked from commit f38aa6bfb7)
(cherry picked from commit 9caeadb8661f5fabd8386e3f07d445fe6ca46e7f)
2023-04-17 15:54:26 +03:00
chendianqiang e030e351fd fix hincrbyfloat not to create a key if the new value is invalid (#11149)
Check the validity of the value before performing the create operation,
prevents new data from being generated even if the request fails to execute.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: chendianqiang <chendianqiang@meituan.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
(cherry picked from commit bc7fe41e58)
(cherry picked from commit 606a385935363ea46c0df4f40f8a949d85f7a20a)
2023-04-17 15:54:26 +03:00
Oran Agra 720ea82eab Redis 6.2.11
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-02-28 18:32:14 +02:00
Oran Agra 0a8a45f94d Integer Overflow in RAND commands can lead to assertion (CVE-2023-25155)
Issue happens when passing a negative long value that greater than
the max positive value that the long can store.

(cherry picked from commit 41430af6a821c551abb862666ef896f2c196dea6)
2023-02-28 18:32:14 +02:00
Tom Levy f44b6a0e9a String pattern matching had exponential time complexity on pathological patterns (CVE-2022-36021)
Authenticated users can use string matching commands with a
specially crafted pattern to trigger a denial-of-service attack on Redis,
causing it to hang and consume 100% CPU time.

(cherry picked from commit e75f92047c22e659d49bba3a083cd0c9935f21e6)
2023-02-28 18:32:14 +02:00
Madelyn Olson 1797c5e374 Backport client pause cron from #9549 2023-02-28 18:32:14 +02:00
Madelyn Olson 683a4ce4b2 Prevent Redis from crashing from key tracking invalidations (#11814)
(cherry picked from commit f7150c45bc5d6f03c8ba86a9a9296d024c6848dc)
2023-02-28 18:32:14 +02:00
zhaozhao.zz 2322f3d87b correct cluster inbound link keepalive time (#11785)
(cherry picked from commit a35e08370a)
(cherry picked from commit 76473f50990e06872d5a08886549815077f5def5)
2023-02-28 18:32:14 +02:00
uriyage f084778cea Optimization: sdsRemoveFreeSpace to avoid realloc on noop (#11766)
In #7875 (Redis 6.2), we changed the sds alloc to be the usable allocation
size in order to:

> reduce the need for realloc calls by making the sds implicitly take over
the internal fragmentation

This change was done most sds functions, excluding `sdsRemoveFreeSpace` and
`sdsResize`, the reason is that in some places (e.g. clientsCronResizeQueryBuffer)
we call sdsRemoveFreeSpace when we see excessive free space and want to trim it.
so if we don't trim it exactly to size, the caller may still see excessive free space and
call it again and again.

However, this resulted in some excessive calls to realloc, even when there's no need
and it's gonna be a no-op (e.g. when reducing 15 bytes allocation to 13).

It turns out that a call for realloc with jemalloc can be expensive even if it ends up
doing nothing, so this PR adds a check using `je_nallocx`, which is cheap to avoid
the call for realloc.

in addition to that this PR unifies sdsResize and sdsRemoveFreeSpace into common
code. the difference between them was that sdsResize would avoid using SDS_TYPE_5,
since it want to keep the string ready to be resized again, while sdsRemoveFreeSpace
would permit using SDS_TYPE_5 and get an optimal memory consumption.
now both methods take a `would_regrow` argument that makes it more explicit.

the only actual impact of that is that in clientsCronResizeQueryBuffer we call both sdsResize
and sdsRemoveFreeSpace for in different cases, and we now prevent the use of SDS_TYPE_5 in both.

The new test that was added to cover this concern used to pass before this PR as well,
this PR is just a performance optimization and cleanup.

Benchmark:
`redis-benchmark -c 100 -t set  -d 512 -P 10  -n  100000000`
on i7-9850H with jemalloc, shows improvement from 1021k ops/sec to 1067k (average of 3 runs).
some 4.5% improvement.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 46393f9819)
(cherry picked from commit b12eeccddd9318a5d97a5aee2dad88999dfad53f)
2023-02-28 18:32:14 +02:00
Oran Agra 658f9bf8c4 Make sure that fork child doesn't do incremental rehashing (#11692)
Turns out that a fork child calling getExpire while persisting keys (and
possibly also a result of some module fork tasks) could cause dictFind
to do incremental rehashing in the child process, which is both a waste
of time, and also causes COW harm.

(cherry picked from commit 2bec254d89)
(cherry picked from commit 3e82bdf738)
2023-02-28 18:32:14 +02:00
Oran Agra 9c76b1ab55 Redis 6.2.10
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-01-17 17:08:17 +02:00
Oran Agra e12aacf3a2 Revert change to KEYS command from (#11676)
in Redis 7.0 this fix covers KEYS as well, but in 6.2 and 6.0 it doesn't,
this is because in 7.0 there's a mechanism to avoid sending partial replies
to the client, and in older releases there isn't, and without it there's a
risk that the client would be able to read what looks like a complete KEYS
command.
2023-01-17 17:08:17 +02:00
Oran Agra 137696d808 Redis 6.2.9
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2023-01-16 18:41:08 +02:00
Oran Agra 37b3b2a7e0 Fix range issues in ZRANDMEMBER and HRANDFIELD (CVE-2023-22458)
missing range check in ZRANDMEMBER and HRANDIFLD leading to panic due
to protocol limitations
2023-01-16 18:41:08 +02:00
Oran Agra 5453899878 Avoid integer overflows in SETRANGE and SORT (CVE-2022-35977)
Authenticated users issuing specially crafted SETRANGE and SORT(_RO)
commands can trigger an integer overflow, resulting with Redis attempting
to allocate impossible amounts of memory and abort with an OOM panic.
2023-01-16 18:41:08 +02:00
Oran Agra 3148f3e8a5 Obuf limit, exit during loop in *RAND* commands and KEYS
Related to the hang reported in #11671
Currently, redis can disconnect a client due to reaching output buffer limit,
it'll also avoid feeding that output buffer with more data, but it will keep
running the loop in the command (despite the client already being marked for
disconnection)

This PR is an attempt to mitigate the problem, specifically for commands that
are easy to abuse, specifically: KEYS, HRANDFIELD, SRANDMEMBER, ZRANDMEMBER.
The RAND family of commands can take a negative COUNT argument (which is not
bound to the number of elements in the key), so it's enough to create a key
with one field, and then these commands can be used to hang redis.
For KEYS the caller can use the existing keyspace in redis (if big enough).
2023-01-16 18:41:08 +02:00
Moti Cohen 3ebace932d Fix sentinel issue if replica changes IP (#11590)
As Sentinel supports dynamic IP only when using hostnames, there
are few leftover addess comparison logic that doesn't take into
account that the IP might get change.

Co-authored-by: moticless <moticless@github.com>
(cherry picked from commit 4a27aa4875)
2023-01-16 18:41:08 +02:00
Oran Agra 423c78f4fa Redis 6.2.8
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2022-12-12 17:02:54 +02:00
Yossi Gottlieb de4d78d7df Fix TLS tests on newer tcl-tls/OpenSSL. (#10910)
Before this commit, TLS tests on Ubuntu 22.04 would fail as dropped
connections result with an ECONNABORTED error thrown instead of an empty
read.

(cherry picked from commit 69d5576832)
2022-12-12 17:02:54 +02:00
Yossi Gottlieb e5e642aa9b Use 'gcc' instead of 'ld' to link test modules. (#9710)
This solves several problems in a more elegant way:

* No need to explicitly use `-lc` on x86_64 when building with `-m32`.
* Avoids issues with undefined floating point emulation funcs on ARM.

(cherry picked from commit f26e90be0c)
2022-12-12 17:02:54 +02:00
Yossi Gottlieb 52cccfbe94 Fix daily failures due to macos-latest change. (#9637)
* Fix test modules linking on macOS 11.x.
* Use macOS 10.x for FreeBSD VM as VirtualBox is not yet supported on
  11.

(cherry picked from commit 6d5a911707)
2022-12-12 17:02:54 +02:00
Ozan Tezcan 052e01e75d Some fixes to undefined behaviour bugs taken from (#9601)
**Signed integer overflow.** Although, signed overflow issue can be problematic time to time
and change how compiler generates code, current findings mostly about signed shift or simple
addition overflow. For most platforms Redis can be compiled for, this wouldn't cause any issue
as far as I can tell (checked generated code on godbolt.org).

UB means nothing guaranteed and risky to reason about program behavior but I don't think any
of the fixes here worth backporting. As sanitizers are now part of the CI, preventing new issues
will be the real benefit.

partial cherry pick from commit b91d8b289b
The bug in BITFIELD seems to affect 12.2.1 used on Alpine
2022-12-12 17:02:54 +02:00
Madelyn Olson 5d66aa3d22 Initialize manual failover replica target (#9814)
(cherry picked from commit 362b3b02e6)
2022-12-12 17:02:54 +02:00
Oran Agra 2f94d40e02 Bump vmactions/freebsd-vm to 0.3.0 2022-12-12 17:02:54 +02:00
Oran Agra d345d1da9b resolve build warnings in quicklist test 2022-12-12 17:02:54 +02:00
Oran Agra 0a3ae0f0a4 Try to fix a race in psync2 test (#11553)
This test sets the master ping interval to 1 hour, in order to avoid
pings in the replicatoin stream incrementing the replication offset,
however, it didn't increase the repl-timeout so on slow machines
where the test took more than 60 seconds, the replicas would drop
and reconnect.

```
*** [err]: PSYNC2: Partial resync after restart using RDB aux fields in tests/integration/psync2.tcl
Replica didn't partial sync
```

The test would detect 4 additional partial syncs where it expects
only one.

(cherry picked from commit b0250b4508)
2022-12-12 17:02:54 +02:00
chenyang8094 6029bd560e Fix null pointer subtraction warning (#10498)
The warning:
```
pqsort.c:106:7: warning: performing pointer subtraction with a null pointer has undefined behavior [-Wnull-pointer-subtraction]
loop:   SWAPINIT(a, es);
        ^~~~~~~~~~~~~~~
pqsort.c:65:47: note: expanded from macro 'SWAPINIT'
#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)NULL) % sizeof(long) || \
```
Clang version:
```
Apple clang version 13.1.6 (clang-1316.0.21.2)
Target: x86_64-apple-darwin21.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
```

(cherry picked from commit cb625844bf)
2022-12-12 17:02:54 +02:00
uriyage 39e5a6fa2f Module CLIENT_CHANGE, Fix crash on free blocked client with DB!=0 (#11500)
In moduleFireServerEvent we change the real client DB to 0 on freeClient in case the event is REDISMODULE_EVENT_CLIENT_CHANGE.
It results in a crash if the client is blocked on a key on other than DB 0.

The DB change is not necessary even for module-client, as we set its DB to 0 on either createClient or moduleReleaseTempClient.

Co-authored-by: Madelyn Olson <34459052+madolson@users.noreply.github.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit e4eb18b303)
2022-12-12 17:02:54 +02:00
Oran Agra 4ac3d79bfc fixes for fork child exit and test: #11463 (#11499)
Fix a few issues with the recent #11463
* use exitFromChild instead of exit
* test should ignore defunct process since that's what we expect to
  happen for thees child processes when the parent dies.
* fix typo

Co-authored-by: Binbin <binloveplay1314@qq.com>
(cherry picked from commit 4c54528f0f)
2022-12-12 17:02:54 +02:00
Oran Agra 51fa40ff42 diskless master, avoid bgsave child hung when fork parent crashes (#11463)
During a diskless sync, if the master main process crashes, the child would
have hung in `write`. This fix closes the read fd on the child side, so that if the
parent crashes, the child will get a write error and exit.

This change also fixes disk-based replication, BGSAVE and AOFRW.
In that case the child wouldn't have been hang, it would have just kept
running until done which may be pointless.

There is a certain degree of risk here. in case there's a BGSAVE child that could
maybe succeed and the parent dies for some reason, the old code would have let
the child keep running and maybe succeed and avoid data loss.
On the other hand, if the parent is restarted, it would have loaded an old rdb file
(or none), and then the child could reach the end and rename the rdb file (data
conflicting with what the parent has), or also have a race with another BGSAVE
child that the new parent started.

Note that i removed a comment saying a write error will be ignored in the child
and handled by the parent (this comment was very old and i don't think relevant).

(cherry picked from commit ccaef5c923)
2022-12-12 17:02:54 +02:00
Moti Cohen 042512aa36 Fix sentinel function that compares hostnames (if failed resolve) (#11419)
Funcion sentinelAddrEqualsHostname() of sentinel makes DNS resolve
and based on it determines if two IP addresses are equal. Now, If the
DNS resolve command fails, the function simply returns 0, even if the
hostnames are identical.

This might become an issue in case of failover such that sentinel might
receives from Redis instance, response to regular INFO query it sent,
and wrongly decide that the instance is pointing to is different leader
than the one recorded because of this function, yet hostnames are
identical. In turn sentinel disconnects the connection between sentinel
and valid slave which leads to -failover-abort-no-good-slave.
See issue #11241.

I managed to reproduce only part of the flow in which the function
return wrong result and trigger +fix-slave-config.

The fix is even if the function failed to 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

(cherry picked from commit bd23b15ad7)
2022-12-12 17:02:54 +02:00
Oran Agra 3f39c18ac1 Improve linux overcommit check and warning (#11357)
1. show the overcommit warning when overcommit is disabled (2),
   not just when it is set to heuristic (0).
2. improve warning text to mention the issue with jemalloc causing VM
   mapping fragmentation when set to 2.

(cherry picked from commit dd60c6c8d3)
2022-12-12 17:02:54 +02:00
Meir Shpilraien (Spielrein) d8102f5ff8 Avoid crash on crash report when a bad function pointer was called (#11298)
If Redis crashes due to calling an invalid function pointer,
the `backtrace` function will try to dereference this invalid pointer
which will cause a crash inside the crash report and will kill
the processes without having all the crash report information.

Example:

```
=== REDIS BUG REPORT START: Cut & paste starting from here ===
198672:M 19 Sep 2022 18:06:12.936 # Redis 255.255.255 crashed by signal: 11, si_code: 1
198672:M 19 Sep 2022 18:06:12.936 # Accessing address: 0x1
198672:M 19 Sep 2022 18:06:12.936 # Crashed running the instruction at: 0x1
// here the processes is crashing
```

This PR tries to fix this crash be:
1. Identify the issue when it happened.
2. Replace the invalid pointer with a pointer to some dummy function
   so that `backtrace` will not crash.

I identification is done by comparing `eip` to `info->si_addr`, if they
are the same we know that the crash happened on the same address it tries to
accesses and we can conclude that it tries to call and invalid function pointer.

To replace the invalid pointer we introduce a new function, `setMcontextEip`,
which is very similar to `getMcontextEip` and it knows to set the Eip for the
different supported OS's. After printing the trace we retrieve the old `Eip` value.

(cherry picked from commit 0bf90d9443)
2022-12-12 17:02:54 +02:00
zhaozhao.zz 65ad4ff87e fix infinite sleep in performEvictions when have lazyfree jobs (#11237)
This bug is introduced in #7653. (Redis 6.2.0)

When `server.maxmemory_eviction_tenacity` is 100, `eviction_time_limit_us` is
`ULONG_MAX`, and if we cannot find the best key to delete (e.g. maxmemory-policy
is `volatile-lru` and all keys with ttl have been evicted), in `cant_free` redis will sleep
forever if some items are being freed in the lazyfree thread.

(cherry picked from commit 464aa04188)
2022-12-12 17:02:54 +02:00
DarrenJiang13 ec5564a4cf fix the client type in trackingInvalidateKey() (#11052)
Fix bug with scripts ignoring client tracking NOLOOP and
send an invalidation message anyway.

(cherry picked from commit 44859a41ee)
2022-12-12 17:02:54 +02:00
Huang Zhw 2cd5f6f3ff tracking pending invalidation message of flushdb sent by (#11068)
trackingHandlePendingKeyInvalidations should use proto.

(cherry picked from commit 61451b02cb)
2022-12-12 17:02:54 +02:00
Huang Zhw fbcd591b28 When client tracking is on, invalidation message of flushdb in a (#11038)
When FLUSHDB / FLUSHALL / SWAPDB is inside MULTI / EXEC, the
client side tracking invalidation message was interleaved with transaction response.

(cherry picked from commit 6f0a27e38e)
2022-12-12 17:02:54 +02:00
Meir Shpilraien (Spielrein) e1b17f1232 Fix #11030, use lua_rawget to avoid triggering metatables and crash. (#11032)
Fix #11030, use lua_rawget to avoid triggering metatables.

#11030 shows how return `_G` from the Lua script (either function or eval), cause the
Lua interpreter to Panic and the Redis processes to exit with error code 1.
Though return `_G` only panic on Redis 7 and 6.2.7, the underline issue exists on older
versions as well (6.0 and 6.2). The underline issue is returning a table with a metatable
such that the metatable raises an error.

The following example demonstrate the issue:
```
127.0.0.1:6379> eval "local a = {}; setmetatable(a,{__index=function() foo() end}) return a" 0
Error: Server closed the connection
```
```
PANIC: unprotected error in call to Lua API (user_script:1: Script attempted to access nonexistent global variable 'foo')
```

The Lua panic happened because when returning the result to the client, Redis needs to
introspect the returning table and transform the table into a resp. In order to scan the table,
Redis uses `lua_gettable` api which might trigger the metatable (if exists) and might raise an error.
This code is not running inside `pcall` (Lua protected call), so raising an error causes the
Lua to panic and exit. Notice that this is not a crash, its a Lua panic that exit with error code 1.

Returning `_G` panics on Redis 7 and 6.2.7 because on those versions `_G` has a metatable
that raises error when trying to fetch a none existing key.

### Solution

Instead of using `lua_gettable` that might raise error and cause the issue, use `lua_rawget`
that simply return the value from the table without triggering any metatable logic.
This is promised not to raise and error.

The downside of this solution is that it might be considered as breaking change, if someone
rely on metatable in the returned value. An alternative solution is to wrap this entire logic
with `pcall` (Lua protected call), this alternative require a much bigger refactoring.

### Back Porting

The same fix will work on older versions as well (6.2, 6.0). Notice that on those version,
the issue can cause Redis to crash if inside the metatable logic there is an attempt to accesses
Redis (`redis.call`). On 7.0, there is not crash and the `redis.call` is executed as if it was done
from inside the script itself.

### Tests

Tests was added the verify the fix

(cherry picked from commit 020e046b42)
2022-12-12 17:02:54 +02:00
chenyang8094 8c76e0f288 Register abs-expire apis (#11025)
RM_SetAbsExpire and RM_GetAbsExpire were not actually operational since
they were introduced, due to omission in API registration.

(cherry picked from commit 39d216a326)
2022-12-12 17:02:54 +02:00
Yossi Gottlieb b4de9e832d TLS: Notify clients on connection shutdown. (#10931)
Use SSL_shutdown(), in a best-effort manner, when closing a TLS
connection. This change better supports OpenSSL 3.x clients that will
not silently ignore the socket-level EOF.

(cherry picked from commit 45ae605332)
2022-12-12 17:02:54 +02:00
Oran Agra 70aaf50082 optimize zset conversion on large ZRANGESTORE (#10789)
when we know the size of the zset we're gonna store in advance,
we can check if it's greater than the listpack encoding threshold,
in which case we can create a skiplist from the get go, and avoid
converting the listpack to skiplist later after it was already populated.

(cherry picked from commit 2189100383)
2022-12-12 17:02:54 +02:00
Oran Agra 66472a5ee3 crash report instructions (#10816)
Trying to avoid people opening crash report issues about module crashes and ARM QEMU bugs.

(cherry picked from commit 475563e2e9)
2022-12-12 17:02:54 +02:00
Vitaly fd7bde5726 Fix ZRANGESTORE crash when zset_max_listpack_entries is 0 (#10767)
When `zrangestore` is called container destination object is created.
Before this PR we used to create a listpack based object even if `zset-max-ziplist-entries`
or equivalent`zset-max-listpack-entries` was set to 0.
This triggered immediate conversion of the listpack into a skiplist in `zrangestore`, which hits
an assertion resulting in an engine crash.

Added a TCL test that reproduces this issue.

(cherry picked from commit 6461f09f43)
2022-12-12 17:02:54 +02:00
Madelyn Olson 9d40f1cbb5 Unpause clients earlier during manual cluster failover (#9676)
Unpause clients after manual failover ends instead of the timed offset

(cherry picked from commit 32215e7889)
2022-12-12 17:02:54 +02:00
Wen Hui 389c5e14ad Sentinel: don't log auth-pass value for better security (#9652)
(cherry picked from commit 43b22f17dc)
2022-12-12 17:02:54 +02:00
Meir Shpilraien (Spielrein) 64c657a8af Remove dead code on sorting reply on Lua scripts. (#10701)
On v6.2.7 a new mechanism was added to Lua scripts that allows
filtering the globals of the Lua interpreter. This mechanism was
added on the following commit: https://github.com/redis/redis/commit/11b602fbf8f9cdf8fc741c24625ab6287ab998a9

One of the globals that was filtered out was `__redis__compare_helper`. This global
was missed and was not added to the allow list or to the deny list. This is
why we get the following warning when Redis starts:
`A key '__redis__compare_helper' was added to Lua globals which is not on the globals allow list nor listed on the deny list.`

After investigating the git blame log, the conclusion is that `__redis__compare_helper`
is no longer needed, the PR deletes this function, and fixes the warning.

Detailed Explanation:

`__redis__compare_helper` was added on this commit: https://github.com/redis/redis/commit/2c861050c1
Its purpose is to sort the replies of `SORT` command when script replication is enable and keep the replies
deterministic and avoid primary and replica synchronization issues. On `SORT` command, there was a need for
special compare function that are able to compare boolean values.

The need to sort the `SORT` command reply was removed on this commit: https://github.com/redis/redis/commit/36741b2c818a95e8ef167818271614ee6b1bc414
The sorting was moved to be part of the `SORT` command and there was not longer a need
to sort it on the Lua interpreter. The commit made `__redis__compare_helper` a dead code but did
not deleted it.
2022-12-06 12:22:36 +02:00
Oran Agra e6f67092f8 Redis 6.2.7
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2022-04-27 16:31:52 +03:00
Yossi Gottlieb 3053337043 Fix test modules build issue on OS X 11. (#9658)
(cherry picked from commit 8bf4c2e38c)
2022-04-27 16:31:52 +03:00
Oran Agra 215c5eacb0 Skip cluster test unit in TLS mode.
see 7d6744c739
2022-04-27 16:31:52 +03:00
Oran Agra afb48c6cc9 Whitelist Lua print function to avoid breaking change in old releases 2022-04-27 16:31:52 +03:00
filipe oliveira 7fddebc272 Optimization: Use either monotonic or wall-clock to measure command execution time, to regain up to 4% execution time (#10502)
In #7491 (part of redis 6.2), we started using the monotonic timer instead of mstime to measure
command execution time for stats, apparently this meant sampling the clock 3 times per command
rather than two (wince we also need the wall-clock time).
In some cases this causes a significant overhead.

This PR fixes that by avoiding the use of monotonic timer, except for the cases were we know it
should be extremely fast.
This PR also adds a new INFO field called `monotonic_clock` that shows which clock redis is using.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 3cd8baf616)
2022-04-27 16:31:52 +03:00
Oran Agra 28d20b8f14 Bring some CI adjustments from 7.0 into 6.2 2022-04-27 16:31:52 +03:00
Yossi Gottlieb ba2feb3004 Clean unused var compiler warning in module test. (#9289)
(cherry picked from commit 8bf433dc86)
2022-04-27 16:31:52 +03:00
sundb aa0606db08 Fix memory leak due to missing freeCallback in blockonbackground moduleapi test (#9499)
Before #9497, before redis-server was shut down, we did not manually shut down all the clients,
which would have prevented valgrind from detecting a memory leak in the client's argc.

(cherry picked from commit 1376d83363)
2022-04-27 16:31:52 +03:00
Ozan Tezcan 49c1c96fc1 Fix overflow check in expireGenericCommand
Partial cherry pick from #9601 in order for the tests in #9601 to pass

(cherry picked from commit b91d8b289b)
2022-04-27 16:31:52 +03:00
Oran Agra d92f2f5ad6 test suite improvements pulled back from 7.0 for cherry picked commits 2022-04-27 16:31:52 +03:00
Oran Agra e1c09d5982 crash log, print killer pid only when si_code is SI_USER (#10454)
Avoid printing "Killed by PID" when si_code != SI_USER.
Apparently SI_USER isn't always set to 0. e.g. on Mac it's 0x10001 and the check that did <= was wrong.

(cherry picked from commit 6761d10cc3)
2022-04-27 16:31:52 +03:00
yiyuaner 8d9e75c769 Fix an off by one error in zzlStrtod (#10465)
When vlen = sizeof(buf), the statement buf[vlen] = '\0' accessing the buffer buf is an off by one error.

(cherry picked from commit 08aed7e7dd)
2022-04-27 16:31:52 +03:00
Vitah Lin 2643c691be Fix memory leak in RM_StreamIteratorStop and moduleFreeKeyIterator (#10353)
* Fix memory leak in RM_StreamIteratorStop
* Fix memory leak in moduleFreeKeyIterator

(cherry picked from commit dff153ff24)
2022-04-27 16:31:52 +03:00
filipe oliveira ef78757e69 Optimization: Avoid deferred array reply on ZRANGE commands BYRANK (#10337)
Avoid deferred array reply on genericZrangebyrankCommand() when consumer type is client.
I.e. any ZRANGE / ZREVRNGE (when tank is used).
This was a performance regression introduced in #7844 (v 6.2) mainly affecting pipelined workloads.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 1dc89e2d02)
2022-04-27 16:31:52 +03:00
filipe oliveira 35e697de83 Optimize deferred replies to use shared objects instead of sprintf (#10334)
Avoid sprintf/ll2string on setDeferredAggregateLen()/addReplyLongLongWithPrefix() when we can used shared objects.
In some pipelined workloads this achieves about 10% improvement.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit b857928ba7)
2022-04-27 16:31:52 +03:00
qetu3790 159981e73c Fix geo search bounding box check causing missing results (#10018)
Consider the following example:
1. geoadd k1 -0.15307903289794921875 85 n1 0.3515625 85.00019260486917005437 n2.
2. geodist k1 n1 n2 returns  "4891.9380"
3. but GEORADIUSBYMEMBER k1 n1 4891.94 m only returns n1.
n2 is in the  boundingbox but out of search areas.So we let  search areas contain boundingbox to get n2.

Co-authored-by: Binbin <binloveplay1314@qq.com>
(cherry picked from commit b2d393b990)
2022-04-27 16:31:52 +03:00
Yossi Gottlieb ec06e93319 Fix OpenSSL 3.0.x related issues. (#10291)
* Drop obsolete initialization calls.
* Use decoder API for DH parameters.
* Enable auto DH parameters if not explicitly used, which should be the
  preferred configuration going forward.

(cherry picked from commit 3881f7850f)
2022-04-27 16:31:52 +03:00
Oran Agra d6a8e64e69 Fix and improve module error reply statistics (#10278)
This PR handles several aspects
1. Calls to RM_ReplyWithError from thread safe contexts don't violate thread safety.
2. Errors returning from RM_Call to the module aren't counted in the statistics (they
  might be handled silently by the module)
3. When a module propagates a reply it got from RM_Call to it's client, then the error
  statistics are counted.

This is done by:
1. When appending an error reply to the output buffer, we avoid updating the global
  error statistics, instead we cache that error in a deferred list in the client struct.
2. When creating a RedisModuleCallReply object, the deferred error list is moved from
  the client into that object.
3. when a module calls RM_ReplyWithCallReply we copy the deferred replies to the dest
  client (if that's a real client, then that's when the error statistics are updated to the server)

Note about RM_ReplyWithCallReply: 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.

Fix #10180

(cherry picked from commit b099889a3a)
2022-04-27 16:31:52 +03:00
Oran Agra a06f10b009 Attempt to fix a rare crash in cluster tests. (#10265)
The theory is that a replica gets disconnected from within REPLCONF ACK,
so when we go up the stack, we'll crash when attempting to access
c->cmd->flags

(cherry picked from commit aa9beaca77)
2022-04-27 16:31:52 +03:00
ivanstosic-janea 0a26869a13 Fix protocol error caused by redis-benchmark (#10236)
The protocol error was caused by the buggy `writeHandler` in `redis-benchmark.c`,
which didn't handle one of the cases, thereby repeating data, leading to protocol errors
when the values being sent are very long.

This PR fixes #10233, issue introduced by #7959

(cherry picked from commit bb875603fb)
2022-04-27 16:31:52 +03:00
Binbin 2c529844fc Fix PSYNC crash with wrong offset (#10243)
`PSYNC replicationid str_offset` will crash the server.

The reason is in `masterTryPartialResynchronization`,
we will call `getLongLongFromObjectOrReply` check the
offset. With a wrong offset, it will add a reply and
then trigger a full SYNC and the client become a replica.

So crash in `c->bufpos == 0 && listLength(c->reply) == 0`.
In this commit, we check the psync_offset before entering
function `masterTryPartialResynchronization`, and return.

Regardless of that crash, accepting the sync, but also replying
with an error would have corrupt the replication stream.

(cherry picked from commit 344e41c922)
2022-04-27 16:31:52 +03:00
Moti Cohen 33f7e12b88 Improve srand entropy (and fix Sentinel failures) (#10197)
As Sentinel relies upon consensus algorithm, all sentinel instances,
randomize a time to initiate their next attempt to become the
leader of the group. But time after time, all raffled the same value.

The problem is in the line `srand(time(NULL)^getpid())` such that
all spinned up containers get same time (in seconds) and same pid
which is always 1. Added material `tv_usec` and verify that even
consecutive calls brings different values and makes the difference.

(cherry picked from commit 52b2fbe970)
2022-04-27 16:31:52 +03:00
Moti Cohen 90891a7dde Fixed Sentinel support for hostnames (#10146)
Sentinel tries to resolve instances hostname to IP only during registration.
It might be that the instance is unavailable during that time, such as
leader crashed and failover took place. Yet, promoted replica must support:

 - Register leader, even if it fails to resolve its hostname during failover
 - Try later to resolve it, if instance is disconnected. Note that
   this condition also support ip-change of an instance.

(cherry picked from commit 79f089bdd9)
2022-04-27 16:31:52 +03:00
David CARLIER 4a59230d82 zmalloc_get_rss netbsd impl fix proposal. (#10116)
Seems like the previous implementation was broken (always returning 0)

since kinfo_proc2 is used the KERN_PROC2 sysctl oid is more appropriate
and also the query's length was not necessarily accurate (6 here).

(cherry picked from commit 50fa627b90)
2022-04-27 16:31:52 +03:00
Binbin e24b947c9b LPOP/RPOP with count against non existing list return null array (#10095)
It used to return `$-1` in RESP2, now we will return `*-1`.
This is a bug in redis 6.2 when COUNT was added, the `COUNT`
option was introduced in #8179. Fix #10089.

the documentation of [LPOP](https://redis.io/commands/lpop) says
```
When called without the count argument:
Bulk string reply: the value of the first element, or nil when key does not exist.

When called with the count argument:
Array reply: list of popped elements, or nil when key does not exist.
```

(cherry picked from commit 39feee8e3a)
2022-04-27 16:31:52 +03:00
guybe7 c5a753c890 lpGetInteger returns int64_t, avoid overflow (#10068)
Fix #9410

Crucial for the ms and sequence deltas, but I changed all
calls, just in case (e.g. "flags")

Before this commit:
`ms_delta` and `seq_delta` could have overflown, causing `currid` to be wrong,
which in turn would cause `streamTrim` to trim the entire rax node (see new test)

(cherry picked from commit 7cd6a64d2f)
2022-04-27 16:31:52 +03:00
王辉 64327e4169 Fix C11_ATOMIC detection on GNU Make 4.3 (#10033)
Older version of GNU Make (<4.3) required quoting of number signs (#) to
avoid them being treated as a comment. Newer versions will treat this
quote as a literal.

This issue and a proposed solution is discussed here:
https://lists.gnu.org/archive/html/info-gnu/2020-01/msg00004.html

Co-authored-by: Yossi Gottlieb <yossigo@gmail.com>
(cherry picked from commit 747b08bee0)
2022-04-27 16:31:52 +03:00
sundb aab9b12786 Fix when the master connection is disconnected, replication retry read indefinitely (#10032)
Now if redis is still loading when we receive sigterm, we will wait for the loading to reach the event
loop (once in 2mb) before actually shutting down. See #10003.

This change caused valgrind CI to fail.
See https://github.com/redis/redis/runs/4662901673?check_suite_focus=true

This pr is mainly to solve the problem that redis process cannot be exited normally.
When the master is disconnected, if repl is processing diskless loading and using `connRead` to read data from master,
it may enter an infinite retry state, which does not handle `connRead` returning 0(master connection disconnected).

(cherry picked from commit 73951abe7b)
2022-04-27 16:31:52 +03:00
Madelyn Olson 066b68328e Redact ACL SETUSER arguments if the user has spaces (#9935)
(cherry picked from commit c40d23b89f)
2022-04-27 16:31:52 +03:00
sundb db3e3ebcb2 Santize dump payload: fix invalid listpack entry start with EOF (#9889)
When an invalid listpack entry starts with EOF, we will skip it when we verify it in the loop.

(cherry picked from commit 1808618f5d)
2022-04-27 16:31:52 +03:00
OfirMos 86db5091c7 fixed mem leak on rdb load error (#9860)
a rare case of short read that can happen when breaking the master-replica
connection on diskless load mode,

(cherry picked from commit 9f9c78578f)
2022-04-27 16:31:52 +03:00
Meir Shpilraien (Spielrein) 93c1d31d97 Clean Lua stack before parsing call reply to avoid crash on a call with many arguments (#9809)
This commit 0f8b634cd (CVE-2021-32626 released in 6.2.6, 6.0.16, 5.0.14)
fixes an invalid memory write issue by using `lua_checkstack` API to make
sure the Lua stack is not overflow. This fix was added on 3 places:
1. `luaReplyToRedisReply`
2. `ldbRedis`
3. `redisProtocolToLuaType`

On the first 2 functions, `lua_checkstack` is handled gracefully while the
last is handled with an assert and a statement that this situation can
not happened (only with misbehave module):

> the Redis reply might be deep enough to explode the LUA stack (notice
that currently there is no such command in Redis that returns such a nested
reply, but modules might do it)

The issue that was discovered is that user arguments is also considered part
of the stack, and so the following script (for example) make the assertion reachable:
```
local a = {}
for i=1,7999 do
    a[i] = 1
end
return redis.call("lpush", "l", unpack(a))
```

This is a regression because such a script would have worked before and now
its crashing Redis. The solution is to clear the function arguments from the Lua
stack which makes the original assumption true and the assertion unreachable.

(cherry picked from commit 6b0b04f1b2)
2022-04-27 16:31:52 +03:00
Binbin 8fca090ede Add tests to cover EXPIRE overflow fix (#9839)
In #8287, some overflow checks have been added. But when
`when *= 1000` overflows, it will become a positive number.
And the check not able to catch it. The key will be added with
a short expiration time and will deleted a few seconds later.

In #9601, will check the overflow after `*=` and return an
error first, and avoiding this situation.

In this commit, added some tests to cover those code paths.
Found it in #9825, and close it.

(cherry picked from commit 9273d09dd4)
2022-04-27 16:31:52 +03:00
Oran Agra e38d0b5a5f fix invalid read on corrupt ziplist (#9831)
If the last bytes in ziplist are corrupt and we decode from tail to head,
we may reach slightly outside the ziplist.

(cherry picked from commit a3a014294f)
2022-04-27 16:31:52 +03:00
Wen Hui 94013b8ecb Sentinel tls memory leak (#9753)
There was a memory leak when tls is used in Sentinels.
The memory leak is noticed when some of the replicas are offline.

(cherry picked from commit 2ce29e032b)
2022-04-27 16:31:52 +03:00
Itamar Haber 968cd2b967 Fixes LPOP/RPOP wrong replies when count is 0 (#9692)
Introduced in #8179, this fixes the command's replies in the 0 count edge case.
[BREAKING] changes the reply type when count is 0 to an empty array (instead of nil)
Moves LPOP ... 0 fast exit path after type check to reply with WRONGTYPE

(cherry picked from commit 06dd202a05)
2022-04-27 16:31:52 +03:00
Rafi Einstein 19c2b179c7 Fix memory leak when there's a read error of module aux data from rdb. (#9705)
(cherry picked from commit 734cde7e38)
2022-04-27 16:31:52 +03:00
Oran Agra 3ba7c6acbe fix new cluster tests issues (#9657)
Following #9483 the daily CI exposed a few problems.

* The cluster creation code (uses redis-cli) is complicated to test with TLS enabled.
  for now i'm just skipping them since the tests we run there don't really need that kind of coverage
* cluster port binding failures
  note that `find_available_port` already looks for a free cluster port
  but the code in `wait_server_started` couldn't detect the failure of binding
  (the text it greps for wasn't found in the log)

(cherry picked from commit 7d6744c739)
2022-04-27 16:31:52 +03:00
qetu3790 936ee01759 Release clients blocked on module commands in cluster resharding and down state (#9483)
Prevent clients from being blocked forever in cluster when they block with their own module command
and the hash slot is migrated to another master at the same time.
These will get a redirection message when unblocked.
Also, release clients blocked on module commands when cluster is down (same as other blocked clients)

This commit adds basic tests for the main (non-cluster) redis test infra that test the cluster.
This was done because the cluster test infra can't handle some common test features,
but most importantly we only build the test modules with the non-cluster test suite.

note that rather than really supporting cluster operations by the test infra, it was added (as dup code)
in two files, one for module tests and one for non-modules tests, maybe in the future we'll refactor that.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 4962c5526d)
2022-04-27 16:31:52 +03:00
DarrenJiang13 8a0515d9a3 add missed error counting (#9646)
* add: add missed error counting in sentinel.c and cluster.c

(cherry picked from commit aa6deff01e)
2022-04-27 16:31:52 +03:00
Yossi Gottlieb 21ab5d4f78 hiredis: improve calloc() overflow fix. (#9630)
Cherry pick a more complete fix to 0215324a6 that also doesn't leak
memory from latest hiredis.

(cherry picked from commit 922ef86a3b)
2022-04-27 16:31:52 +03:00
Huang Zhw 0fb96d55bd Make tracking invalidation messages always after command's reply (#9422)
Tracking invalidation messages were sometimes sent in inconsistent order,
before the command's reply rather than after.
In addition to that, they were sometimes embedded inside other commands
responses, like MULTI-EXEC and MGET.

(cherry picked from commit fd135f3e2d)
2022-04-27 16:31:52 +03:00
Binbin f5d8a36983 Add missing pause tcl test to test_helper.tcl (#9158)
* Add keyname tags to avoid CROSSSLOT errors in external server CI
* Use new wait_for_blocked_clients_count in pause.tcl

(cherry picked from commit 5dddf496ce)
2022-04-27 16:31:52 +03:00
meir 11b602fbf8 Protect any table which is reachable from globals and added globals allow list.
The allow list is done by setting a metatable on the global table before initializing
any library. The metatable set the `__newindex` field to a function that check
the allow list before adding the field to the table. Fields which is not on the
allow list are simply ignored.

After initialization phase is done we protect the global table and each table
that might be reachable from the global table. For each table we also protect
the table metatable if exists.
2022-04-27 16:31:52 +03:00
meir b2ce3719af Protect globals of evals scripts.
Use the new `lua_enablereadonlytable` Lua API to protect the global tables of
evals scripts. The implemetation is easy, we simply call `lua_enablereadonlytable`
on the global table to turn it into a readonly table.
2022-04-27 16:31:52 +03:00
meir 21414ad480 Move user eval function to be located on Lua registry.
Today, Redis wrap the user Lua code with a Lua function.
For example, assuming the user code is:

```
return redis.call('ping')
```

The actual code that would have sent to the Lua interpreter was:

```
f_b3a02c833904802db9c34a3cf1292eee3246df3c() return redis.call('ping') end
```

The wraped code would have been saved on the global dictionary with the
following name: `f_<script sha>` (in our example `f_b3a02c833904802db9c34a3cf1292eee3246df3c`).

This approach allows one user to easily override the implementation a another user code, example:

```
f_b3a02c833904802db9c34a3cf1292eee3246df3c = function() return 'hacked' end
```

Running the above code will cause `evalsha b3a02c833904802db9c34a3cf1292eee3246df3c 0` to return
hacked although it should have returned `pong`.

Another disadventage is that Redis basically runs code on the loading (compiling) phase without been
aware of it. User can do code injection like this:

```
return 1 end <run code on compling phase> function() return 1
```

The wraped code will look like this and the entire `<run code on compling phase>` block will run outside
of eval or evalsha context:

```
f_<sha>() return 1 end <run code on compling phase> function() return 1 end
```
2022-04-27 16:31:52 +03:00
meir 13c1e1f298 Added support for Lua readonly tables.
The new feature can be turned off and on using the new `lua_enablereadonlytable` Lua API.

(cherry picked from commit 92b5098b87e2d0880a530899119524bf1dfbc332)
2022-04-27 16:31:52 +03:00
Vo Trong PhucandPhuc. Vo Trong 34505d26f7 add check good slaves to write when execute script (#10249)
There was no check min-slave-* config when evaluating Lua script.
Add check enough good slaves for write command when evaluating scripts.

Co-authored-by: Phuc. Vo Trong <phucvt@vng.com.vn>
2022-04-11 12:59:27 +03:00
Oran Agra 4930d19e70 Redis 6.2.6
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2021-10-04 13:59:40 +03:00
Oran Agra aba9517542 corrupt-dump-fuzzer test, avoid creating junk keys (#9302)
The execution of the RPOPLPUSH command by the fuzzer created junk keys,
that were later being selected by RANDOMKEY and modified.
This also meant that lists were statistically tested more than other
files.

Fix the fuzzer not to pass junk key names to RPOPLPUSH, and add a check
that detects that new keys are not added by the fuzzer to detect future
similar issues.

(cherry picked from commit 3f3f678a47)
2021-10-04 13:59:40 +03:00
sundb 7540708a61 Fix missing check for sanitize_dump in corrupt-dump-fuzzer test (#9285)
this means the assertion that checks that when deep sanitization is enabled,
there are no crashes, was missing.

(cherry picked from commit 3db0f1a284)
2021-10-04 13:59:40 +03:00
Yunier Pérez 664c7b36a5 Allow to override OPENSSL_PREFIX (#9567)
While the original issue was on Linux, this should work for other
platforms as well.
2021-10-04 13:59:40 +03:00
Yossi Gottlieb c390972c76 Propagate OPENSSL_PREFIX to hiredis. (#9345) 2021-10-04 13:59:40 +03:00
Oran Agra 73d286d523 Fix stream sanitization for non-int first value (#9553)
This was recently broken in #9321 when we validated stream IDs to be
integers but did that after to the stepping next record instead of before.

(cherry picked from commit 5a4ab7c7d2)
2021-10-04 13:59:40 +03:00
David CARLIER 4cbccab2bd TLS build fix on OpenBSD when built with LibreSSL. (#9486)
(cherry picked from commit 418c2e7931)
2021-10-04 13:59:40 +03:00
yvette903 23ed37dd87 Fix: client pause uses an old timeout (#9477)
A write request may be paused unexpectedly because `server.client_pause_end_time` is old.

**Recreate this:**
redis-cli -p 6379
127.0.0.1:6379> client pause 500000000 write
OK
127.0.0.1:6379> client unpause
OK
127.0.0.1:6379> client pause 10000 write
OK
127.0.0.1:6379> set key value

The write request `set key value` is paused util  the timeout of 500000000 milliseconds was reached.

**Fix:**
reset `server.client_pause_end_time` = 0 in `unpauseClients`

(cherry picked from commit f560531d5b)
2021-10-04 13:59:40 +03:00
zhaozhao.zz 9b25484a13 Fix wrong offset when replica pause (#9448)
When a replica paused, it would not apply any commands event the command comes from master, if we feed the non-applied command to replication stream, the replication offset would be wrong, and data would be lost after failover(since replica's `master_repl_offset` grows but command is not applied).

To fix it, here are the changes:
* Don't update replica's replication offset or propagate commands to sub-replicas when it's paused in `commandProcessed`.
* Show `slave_read_repl_offset` in info reply.
* Add an assert to make sure master client should never be blocked unless pause or module (some modules may use block way to do background (parallel) processing and forward original block module command to the replica, it's not a good way but it can work, so the assert excludes module now, but someday in future all modules should rewrite block command to propagate like what `BLPOP` does).

(cherry picked from commit 1b83353dc3)
2021-10-04 13:59:40 +03:00
Madelyn Olson 49f8f43890 Add test verifying PUBSUB NUMPAT behavior (#9209)
(cherry picked from commit 8b8f05c86c)
2021-10-04 13:59:40 +03:00
guybe7 c936f801c0 Fix two minor bugs (MIGRATE key args and getKeysUsingCommandTable) (#9455)
1. MIGRATE has a potnetial key arg in argv[3]. It should be reflected in the command table.
2. getKeysUsingCommandTable should never free getKeysResult, it is always freed by the caller)
   The reason we never encountered this double-free bug is that almost always getKeysResult
   uses the statis buffer and doesn't allocate a new one.

(cherry picked from commit 6aa2285e32)
2021-10-04 13:59:40 +03:00
sundb 694a869e78 Fix the timing of read and write events under kqueue (#9416)
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`, if an `fd` has both read and write events,
reading the event using `kevent` will generate two events, which will
result in uncontrolled read and write timing.

This also means that the guarantees of AOF `appendfsync` = `always` are
not met on MacOS without this fix.

The main change to this pr is to cache the events already obtained when reading
them, so that if the same `fd` occurs again, only the mask in the cache is updated,
rather than a new event is generated.

This was exposed by the following test failure on MacOS:
```
*** [err]: AOF fsync always barrier issue in tests/integration/aof.tcl
Expected 544 != 544 (context: type eval line 26 cmd {assert {$size1 != $size2}} proc ::test)
```

(cherry picked from commit 306a5ccd2d)
2021-10-04 13:59:40 +03:00
sundb a2e8a3a241 Sanitize dump payload: fix double free after insert dup nodekey to stream rax and returns 0 (#9399)
(cherry picked from commit 492d8d0961)
2021-10-04 13:59:40 +03:00
Wang Yuan 2f8ec0e024 Fix the wrong detection of sync_file_range system call (#9371)
If we want to check `defined(SYNC_FILE_RANGE_WAIT_BEFORE)`, we should include fcntl.h.
otherwise, SYNC_FILE_RANGE_WAIT_BEFORE is not defined, and there is alway not `sync_file_range` system call.
Introduced by #8532

(cherry picked from commit 8edc3cd62c)
2021-10-04 13:59:40 +03:00
sundb 09c63c45dd Sanitize dump payload: handle remaining empty key when RDB loading and restore command (#9349)
This commit mainly fixes empty keys due to RDB loading and restore command,
which was omitted in #9297.

1) When loading quicklsit, if all the ziplists in the quicklist are empty, NULL will be returned.
    If only some of the ziplists are empty, then we will skip the empty ziplists silently.
2) When loading hash zipmap, if zipmap is empty, sanitization check will fail.
3) When loading hash ziplist, if ziplist is empty, NULL will be returned.
4) Add RDB loading test with sanitize.

(cherry picked from commit cbda492909)
2021-10-04 13:59:40 +03:00
DarrenJiang13 1ed0f049fe [BUGFIX] Add some missed error statistics (#9328)
add error counting for some missed behaviors.

(cherry picked from commit 43eb0ce3bf)
2021-10-04 13:59:40 +03:00
Oran Agra 4b04ca0b18 Improvements to corrupt payload sanitization (#9321)
Recently we found two issues in the fuzzer tester: #9302 #9285
After fixing them, more problems surfaced and this PR (as well as #9297) aims to fix them.

Here's a list of the fixes
- Prevent an overflow when allocating a dict hashtable
- Prevent OOM when attempting to allocate a huge string
- Prevent a few invalid accesses in listpack
- Improve sanitization of listpack first entry
- Validate integrity of stream consumer groups PEL
- Validate integrity of stream listpack entry IDs
- Validate ziplist tail followed by extra data which start with 0xff

Co-authored-by: sundb <sundbcn@gmail.com>
(cherry picked from commit 0c90370e6d)
2021-10-04 13:59:40 +03:00
sundb 2f54107289 Sanitize dump payload: fix empty keys when RDB loading and restore command (#9297)
When we load rdb or restore command, if we encounter a length of 0, it will result in the creation of an empty key.
This could either be a corrupt payload, or a result of a bug (see #8453 )

This PR mainly fixes the following:
1) When restore command will return `Bad data format` error.
2) When loading RDB, we will silently discard the key.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 8ea777a6a0)
2021-10-04 13:59:40 +03:00
menwen e34f06ae5d Add latency monitor sample when key is deleted via lazy expire (#9317)
Fix that there is no sample latency after the key expires via expireIfNeeded().
Some refactoring for shared code.

(cherry picked from commit ca559819f7)
2021-10-04 13:59:40 +03:00
Viktor Söderqvist 77386ae011 redis-cli ASK redirect test: Add retry loop to fix timing issue (#9315)
(cherry picked from commit 1c59567a7f)
2021-10-04 13:59:40 +03:00
Oran Agra 0c959294a8 Skip new redis-cli ASK test in TLS mode (#9312)
(cherry picked from commit 52df350fe5)
2021-10-04 13:59:40 +03:00
Huang Zhw 8892b5cf9e When redis-cli received ASK, it didn't handle it (#8930)
When redis-cli received ASK, it used string matching wrong and didn't
handle it.

When we access a slot which is in migrating state, it maybe
return ASK. After redirect to the new node, we need send ASKING
command before retry the command.  In this PR after redis-cli receives
ASK, we send a ASKING command before send the origin command
after reconnecting.

Other changes:
* Make redis-cli -u and -c (unix socket and cluster mode) incompatible
  with one another.
* When send command fails, we avoid the 2nd reconnect retry and just
  print the error info. Users will decide how to do next.
  See #9277.
* Add a test faking two redis nodes in TCL to just send ASK and OK in
  redis protocol to test ASK behavior.

Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit cf61ad14cc)
2021-10-04 13:59:40 +03:00
Binbin 9da232c05a redis-cli: Sleep for a while in each cliConnect when we got connect error in cluster mode. (#8884)
There's an infinite loop when redis-cli fails to connect in cluster mode.
This commit adds a 1 second sleep to prevent flooding the console with errors.
It also adds a specific error print in a few places that could have error without printing anything.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 8351a10b95)
2021-10-04 13:59:40 +03:00
Huang Zhw 1ce06604de redis-cli when SELECT fails, we should reset dbnum to 0 (#8898)
when SELECT fails, we should reset dbnum to 0, so the prompt will not
display incorrectly.

Additionally when SELECT and HELLO fail, we output message to inform
it.

Add config.input_dbnum which means the dbnum about to select.
And config.dbnum means currently selected dbnum. When users succeed to
select db, config.dbnum and config.input_dbnum will be the same. When
users select db failed, config.input_dbnum will be kept. Next time if users
auth success, config.input_dbnum will be automatically selected.
When reconnect, we should select the origin dbnum.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 6b47598998)
2021-10-04 13:59:40 +03:00
Binbin 530c70b0a9 GEO* STORE with empty src key delete the dest key and return 0, not empty array (#9271)
With an empty src key, we need to deal with two situations:
1. non-STORE: We should return emptyarray.
2. STORE: Try to delete the store key and return 0.

This applies to both GEOSEARCHSTORE (new to v6.2), and
also GEORADIUS STORE (which was broken since forever)

This pr try to fix #9261. i.e. both STORE variants would have behaved
like the non-STORE variants when the source key was missing,
returning an empty array and not deleting the destination key,
instead of returning 0, and deleting the destination key.

Also add more tests for some commands.
- GEORADIUS: wrong type src key, non existing src key, empty search,
  store with non existing src key, store with empty search
- GEORADIUSBYMEMBER: wrong type src key, non existing src key,
  non existing member, store with non existing src key
- GEOSEARCH: wrong type src key, non existing src key, empty search,
  frommember with non existing member
- GEOSEARCHSTORE: wrong type key, non existing src key,
  fromlonlat with empty search, frommember with non existing member

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 86555ae0f7)
2021-10-04 13:59:40 +03:00
YiyuanGUO dadc67a92e Fix integer overflow in _sdsMakeRoomFor (CVE-2021-41099) 2021-10-04 13:59:40 +03:00
Oran Agra 2775a3526e Fix ziplist and listpack overflows and truncations (CVE-2021-32627, CVE-2021-32628)
- fix possible heap corruption in ziplist and listpack resulting by trying to
  allocate more than the maximum size of 4GB.
- prevent ziplist (hash and zset) from reaching size of above 1GB, will be
  converted to HT encoding, that's not a useful size.
- prevent listpack (stream) from reaching size of above 1GB.
- XADD will start a new listpack if the new record may cause the previous
  listpack to grow over 1GB.
- XADD will respond with an error if a single stream record is over 1GB
- List type (ziplist in quicklist) was truncating strings that were over 4GB,
  now it'll respond with an error.
2021-10-04 13:59:40 +03:00
meir@redislabs.com 8f241ab3b8 Fix invalid memory write on lua stack overflow {CVE-2021-32626}
When LUA call our C code, by default, the LUA stack has room for 20
elements. In most cases, this is more than enough but sometimes it's not
and the caller must verify the LUA stack size before he pushes elements.

On 3 places in the code, there was no verification of the LUA stack size.
On specific inputs this missing verification could have lead to invalid
memory write:
1. On 'luaReplyToRedisReply', one might return a nested reply that will
   explode the LUA stack.
2. On 'redisProtocolToLuaType', the Redis reply might be deep enough
   to explode the LUA stack (notice that currently there is no such
   command in Redis that returns such a nested reply, but modules might
   do it)
3. On 'ldbRedis', one might give a command with enough arguments to
   explode the LUA stack (all the arguments will be pushed to the LUA
   stack)

This commit is solving all those 3 issues by calling 'lua_checkstack' and
verify that there is enough room in the LUA stack to push elements. In
case 'lua_checkstack' returns an error (there is not enough room in the
LUA stack and it's not possible to increase the stack), we will do the
following:
1. On 'luaReplyToRedisReply', we will return an error to the user.
2. On 'redisProtocolToLuaType' we will exit with panic (we assume this
   scenario is rare because it can only happen with a module).
3. On 'ldbRedis', we return an error.
2021-10-04 13:59:40 +03:00
meir@redislabs.com 3e09be56a8 Fix protocol parsing on 'ldbReplParseCommand' (CVE-2021-32672)
The protocol parsing on 'ldbReplParseCommand' (LUA debugging)
Assumed protocol correctness. This means that if the following
is given:
*1
$100
test
The parser will try to read additional 94 unallocated bytes after
the client buffer.
This commit fixes this issue by validating that there are actually enough
bytes to read. It also limits the amount of data that can be sent by
the debugger client to 1M so the client will not be able to explode
the memory.
2021-10-04 13:59:40 +03:00
Oran Agra 757f8f771e Prevent unauthenticated client from easily consuming lots of memory (CVE-2021-32675)
This change sets a low limit for multibulk and bulk length in the
protocol for unauthenticated connections, so that they can't easily
cause redis to allocate massive amounts of memory by sending just a few
characters on the network.
The new limits are 10 arguments of 16kb each (instead of 1m of 512mb)
2021-10-04 13:59:40 +03:00
Oran Agra 04ba485042 Fix redis-cli / redis-sential overflow on some platforms (CVE-2021-32762)
The redis-cli command line tool and redis-sentinel service may be vulnerable
to integer overflow when parsing specially crafted large multi-bulk network
replies. This is a result of a vulnerability in the underlying hiredis
library which does not perform an overflow check before calling the calloc()
heap allocation function.

This issue only impacts systems with heap allocators that do not perform their
own overflow checks. Most modern systems do and are therefore not likely to
be affected. Furthermore, by default redis-sentinel uses the jemalloc allocator
which is also not vulnerable.
2021-10-04 13:59:40 +03:00
Oran Agra b1149a49b2 Fix Integer overflow issue with intsets (CVE-2021-32687)
The vulnerability involves changing the default set-max-intset-entries
configuration parameter to a very large value and constructing specially
crafted commands to manipulate sets
2021-10-04 13:59:40 +03:00
Oran Agra db09f6eb2e Redis 6.2.5
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2021-07-21 21:06:49 +03:00
Huang Zhw 835d15b536 On 32 bit platform, the bit position of GETBIT/SETBIT/BITFIELD/BITCOUNT,BITPOS may overflow (see CVE-2021-32761) (#9191)
GETBIT, SETBIT may access wrong address because of wrap.
BITCOUNT and BITPOS may return wrapped results.
BITFIELD may access the wrong address but also allocate insufficient memory and segfault (see CVE-2021-32761).

This commit uses `uint64_t` or `long long` instead of `size_t`.
related https://github.com/redis/redis/pull/8096

At 32bit platform:
> setbit bit 4294967295 1
(integer) 0
> config set proto-max-bulk-len 536870913
OK
> append bit "\xFF"
(integer) 536870913
> getbit bit 4294967296
(integer) 0

When the bit index is larger than 4294967295, size_t can't hold bit index. In the past,  `proto-max-bulk-len` is limit to 536870912, so there is no problem.

After this commit, bit position is stored in `uint64_t` or `long long`. So when `proto-max-bulk-len > 536870912`, 32bit platforms can still be correct.

For 64bit platform, this problem still exists. The major reason is bit pos 8 times of byte pos. When proto-max-bulk-len is very larger, bit pos may overflow.
But at 64bit platform, we don't have so long string. So this bug may never happen.

Additionally this commit add a test cost `512MB` memory which is tag as `large-memory`. Make freebsd ci and valgrind ci ignore this test.

(cherry picked from commit 71d452876e)
2021-07-21 21:06:49 +03:00
Oran Agra bae0512c8a longer timeout in replication test (#8963)
the test normally passes. but we saw one failure in a valgrind run in github actions

(cherry picked from commit 8458baf6a9)
2021-07-21 21:06:49 +03:00
Huang Zhw 8d6134952a Remove testmodule in src/modules/Makefile. (#9250)
src/modules make failed. As in #3718 testmodule.c was removed. But the makefile was not updated

(cherry picked from commit d54c9086c2)
2021-07-21 21:06:49 +03:00
Oran Agra 1d7c0e5949 Fix failing basics moduleapi test on 32bit CI (#9140)
(cherry picked from commit 5ffdbae1f6)
2021-07-21 21:06:49 +03:00
Oran Agra 37b0f3617d Adjustments to recent RM_StringTruncate fix (#3718) (#9125)
- Introduce a new sdssubstr api as a building block for sdsrange.
  The API of sdsrange is many times hard to work with and also has
  corner case that cause bugs. sdsrange is easy to work with and also
  simplifies the implementation of sdsrange.
- Revert the fix to RM_StringTruncate and just use sdssubstr instead of
  sdsrange.
- Solve valgrind warnings from the new tests introduced by the previous
  PR.

(cherry picked from commit ae418eca24)
2021-07-21 21:06:49 +03:00
Huang Zhw 6866117194 Fix missing separator in module info line (usedby and using lists) (#9241)
Fix module info genModulesInfoStringRenderModulesList lack separator when there's more than one module in the list.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 1895e134a7)
2021-07-21 21:06:49 +03:00
Binbin b622537199 SMOVE only notify dstset when the addition is successful. (#9244)
in case dest key already contains the member, the dest key isn't modified, so the command shouldn't invalidate watch.

(cherry picked from commit 11dc4e59b3)
2021-07-21 21:06:49 +03:00
qetu3790 355b1b6a57 Set TCP keepalive on inbound clusterbus connections (#9230)
Set TCP keepalive on inbound clusterbus connections to prevent memory leak

(cherry picked from commit f03af47a34)
2021-07-21 21:06:49 +03:00
Yossi Gottlieb d6f4273241 Fix compatibility with OpenSSL 1.1.0. (#9233)
(cherry picked from commit 277e4dc203)
2021-07-21 21:06:49 +03:00
Oran Agra de1b19ea88 fix valgrind issues with recently added test in modules/blockonbackground (#9192)
fixes test issue introduced in #9167

1. invalid reads due to accessing non-retained string (passed as unblock context).
2. leaking module blocked client context, see #6922 for info.

(cherry picked from commit a8518cce95)
2021-07-21 21:06:49 +03:00
Yossi Gottlieb 79fa5618f1 Fix CLIENT UNBLOCK crashing modules. (#9167)
Modules that use background threads with thread safe contexts are likely
to use RM_BlockClient() without a timeout function, because they do not
set up a timeout.

Before this commit, `CLIENT UNBLOCK` would result with a crash as the
`NULL` timeout callback is called. Beyond just crashing, this is also
logically wrong as it may throw the module into an unexpected client
state.

This commits makes `CLIENT UNBLOCK` on such clients behave the same as
any other client that is not in a blocked state and therefore cannot be
unblocked.

(cherry picked from commit aa139e2f02)
2021-07-21 21:06:49 +03:00
Huang Zhw 91bf2ab86d redis-cli cluster import command may issue wrong MIGRATE command. (#8945)
In clusterManagerCommandImport strcat was used to concat COPY and
REPLACE, the space maybe not enough.
If we use --cluster-replace but not --cluster-copy, the MIGRATE
command contained COPY instead of REPLACE.

(cherry picked from commit a049f6295a)
2021-07-21 21:06:49 +03:00
Binbin 88655019cc Fix accidental deletion of sinterstore command when we meet wrong type error. (#9032)
SINTERSTORE would have deleted the dest key right away,
even when later on it is bound to fail on an (WRONGTYPE) error.

With this change it first picks up all the input keys, and only later
delete the dest key if one is empty.

Also add more tests for some commands.
Mainly focus on
- `wrong type error`:
	expand test case (base on sinter bug) in non-store variant
	add tests for store variant (although it exists in non-store variant, i think it would be better to have same tests)
- the dstkey result when we meet `non-exist key (empty set)` in *store

sdiff:
- improve test case about wrong type error (the one we found in sinter, although it is safe in sdiff)
- add test about using non-exist key (treat it like an empty set)
sdiffstore:
- according to sdiff test case, also add some tests about `wrong type error` and `non-exist key`
- the different is that in sdiffstore, we will consider the `dstkey` result

sunion/sunionstore add more tests (same as above)

sinter/sinterstore also same as above ...

(cherry picked from commit b8a5da80c4)
2021-07-21 21:06:49 +03:00
Jason Elbaum fad44611dc Change return value type for ZPOPMAX/MIN in RESP3 (#8981)
When using RESP3, ZPOPMAX/ZPOPMIN should return nested arrays for consistency
with other commands (e.g. ZRANGE).

We do that only when COUNT argument is present (similarly to how LPOP behaves).
for reasoning see https://github.com/redis/redis/issues/8824#issuecomment-855427955

This is a breaking change only when RESP3 is used, and COUNT argument is present!

(cherry picked from commit 7f342020dc)
2021-07-21 21:06:49 +03:00
Mikhail Fesenko 8884971223 Direct redis-cli repl prints to stderr, because --rdb can print to stdout. fflush stdout after responses (#9136)
1. redis-cli can output --rdb data to stdout
   but redis-cli also write some messages to stdout which will mess up the rdb.

2. Make redis-cli flush stdout when printing a reply
  This was needed in order to fix a hung in redis-cli test that uses
  --replica.
   Note that printf does flush when there's a newline, but fwrite does not.

3. fix the redis-cli --replica test which used to pass previously
   because it didn't really care what it read, and because redis-cli
   used printf to print these other things to stdout.

4. improve redis-cli --replica test to run with both diskless and disk-based.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Viktor Söderqvist <viktor@zuiderkwast.se>
(cherry picked from commit 1eb4baa5b8)
2021-07-21 21:06:49 +03:00
Rob Snyder 07e1248686 Fix ziplist length updates on bigendian platforms (#2080)
Adds call to intrev16ifbe to ensure ZIPLIST_LENGTH is compared correctly

(cherry picked from commit eaa52719a3)
2021-07-21 21:06:49 +03:00
Oran Agra 6cd84b64f0 Test infra, handle RESP3 attributes and big-numbers and bools (#9235)
- promote the code in DEBUG PROTOCOL to addReplyBigNum
- DEBUG PROTOCOL ATTRIB skips the attribute when client is RESP2
- networking.c addReply for push and attributes generate assertion when
  called on a RESP2 client, anything else would produce a broken
  protocol that clients can't handle.

(cherry picked from commit 6a5bac309e)
2021-07-21 21:06:49 +03:00
Binbin c6b3966d02 hrandfield and zrandmember with count should return emptyarray when key does not exist. (#9178)
due to a copy-paste bug, it used to reply with null response rather than empty array.
this commit includes new tests that are looking at the RESP response directly in
order to be able to tell the difference between them.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit a418a2d3fc)
2021-07-21 21:06:49 +03:00
Oran Agra 432e056659 Tests: add a way to read raw RESP protocol reponses (#9193)
This makes it possible to distinguish between null response and an empty
array (currently the tests infra translates both to an empty string/list)

(cherry picked from commit 7103367ad4)
2021-07-21 21:06:49 +03:00
Mikhail Fesenko bbb6c2fb53 redis-cli --rdb: fix broken fsync/ftruncate for stdout (#9135)
A change in redis 6.2 caused redis-cli --rdb that's directed to stdout to fail because fsync fails.
This commit avoids doing ftruncate (fails with a warning) and fsync (fails with an error) when the
output file is `-`, and adds the missing documentation that `-` means stdout.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Wang Yuan <wangyuancode@163.com>
(cherry picked from commit 74fe15b360)
2021-07-21 21:06:49 +03:00
Leibale Eidelman 095a6e5937 fix ZRANGESTORE - should return 0 when src points to an empty key (#9089)
mistakenly it used to return an empty array rather than 0.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 95274f1f8a)
2021-07-21 21:06:49 +03:00
guybe7 56020fd821 Include sizeof(struct stream) in objectComputeSize (#9164)
Affects MEMORY USAGE

(cherry picked from commit 4434cebbb3)
2021-07-21 21:06:49 +03:00
Binbin 11235c0e40 ZRANDMEMBER WITHSCORES with negative COUNT may return bad score (#9162)
Return a bad score when used with negative count (or count of 1), and non-ziplist encoded zset.
Also add test to validate the return value and cover the issue.

(cherry picked from commit 4bc5a8324d)
2021-07-21 21:06:49 +03:00
Evan 406caa5f56 modules: Add newlen == 0 handling to RM_StringTruncate (#3717) (#3718)
Previously, passing 0 for newlen would not truncate the string at all.
This adds handling of this case, freeing the old string and creating a new empty string.

Other changes:
- Move `src/modules/testmodule.c` to `tests/modules/basics.c`
- Introduce that basic test into the test suite
- Add tests to cover StringTruncate
- Add `test-modules` build target for the main makefile
- Extend `distclean` build target to clean modules too

(cherry picked from commit 1ccf2ca2f4)
2021-07-21 21:06:49 +03:00
yvette903 8bd5e39102 Fix coredump after Client Unpause command when threaded I/O is enabled (#9041)
Fix crash when using io-threads-do-reads and issuing CLIENT PAUSE and
CLIENT UNPAUSE.
This issue was introduced in redis 6.2 together with the FAILOVER command.

(cherry picked from commit 096c5fd5d2)
2021-07-21 21:06:49 +03:00
guybe7 c547dff650 Module API for current command name (#8792)
sometimes you can be very deep in the call stack, without access to argv.
once you're there you may want your reply/log to contain the command name.

(cherry picked from commit e16d3eb998)
2021-07-21 21:06:49 +03:00
Huang Zhw e6df2f6210 Fix XTRIM or XADD with LIMIT may delete more entries than Count. (#9048)
The decision to stop trimming due to LIMIT in XADD and XTRIM was after the limit was reached.
i.e. the code was deleting **at least** that count of records (from the LIMIT argument's perspective, not the MAXLEN),
instead of **up to** that count of records.
see #9046

(cherry picked from commit eaa7a7bb93)
2021-07-21 21:06:49 +03:00
Huang Zhw 312ccae003 XTRIM call streamParseAddOrTrimArgsOrReply use wrong arg xadd. (#9047)
xtrimCommand call streamParseAddOrTrimArgsOrReply should use xadd==0.

When the syntax is valid, it does not cause any bugs because the params of XADD is superset of XTRIM.
Just XTRIM will not respond with error on invalid syntax. The syntax of XADD will also be accpeted by XTRIM.

(cherry picked from commit 91f3689bf5)
2021-07-21 21:06:49 +03:00
YaacovHazan ff27217639 stabilize tests that involved with load handlers (#8967)
When test stop 'load handler' by killing the process that generating the load,
some commands that already in the input buffer, still might be processed by the server.
This may cause some instability in tests, that count on that no more commands
processed after we stop the `load handler'

In this commit, new proc 'wait_load_handlers_disconnected' added, to verify that no more
cammands from any 'load handler' prossesed, by checking that the clients who
genreate the load is disconnceted.

Also, replacing check of dbsize with wait_for_ofs_sync before comparing debug digest, as
it would fail in case the last key the workload wrote was an overridden key (not a new one).

Affected tests
Race fix:
- failover command to specific replica works
- Connect multiple replicas at the same time (issue #141), master diskless=$mdl, replica diskless=$sdl
- AOF rewrite during write load: RDB preamble=$rdbpre

Cleanup and speedup:
- Test replication with blocking lists and sorted sets operations
- Test replication with parallel clients writing in different DBs
- Test replication partial resync: $descr (diskless: $mdl, $sdl, reconnect: $reconnect

(cherry picked from commit 32a2584e07)
2021-07-21 21:06:49 +03:00
perryitay 3f4f9b6331 Fail EXEC command in case a watched key is expired (#9194)
There are two issues fixed in this commit: 
1. we want to fail the EXEC command in case there is a watched key that's logically
   expired but not yet deleted by active expire or lazy expire.
2. we saw that currently cache time is update in every `call()` (including nested calls),
   this time is being also being use for the isKeyExpired comparison, we want to update
   the cache time only in the first call (execCommand)

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit ac8b1df885)
2021-07-21 21:06:49 +03:00
Huang Zhw 5b8e395174 Do not install a file event to send data to rewrite child when parent stop sending diff to child in aof rewrite. (#8767)
In aof rewrite, when parent stop sending data to child, if there is
new rewrite data, aofChildWriteDiffData write event will be installed.
Then this event is issued and deletes the file event without do anyting.
This will happen over and over again until aof rewrite finish.

This bug used to waste a few system calls per excessive wake-up
(epoll_ctl and epoll_wait) per cycle, each cycle triggered by receiving
a write command from a client.

(cherry picked from commit cb961d8c8e)
2021-07-21 21:06:49 +03:00
Omer Shadmi 9914b676b3 Avoid exiting to allow diskless loading to recover from RDB short read on module AUX data (#9199)
Currently a replica is able to recover from a short read (when diskless loading
is enabled) and avoid crashing/exiting, replying to the master and then the rdb
could be sent again by the master for another load attempt by the replica.
There were a few scenarios that were not behaving similarly, such as when
there is no end-of-file marker, or when module aux data failed to load, which
should be allowed to occur due to a short read.

(cherry picked from commit f06d782f5a)
2021-07-21 21:06:49 +03:00
Oran Agra abd44c8393 Fix race in client side tracking (#9116)
The `Tracking gets notification of expired keys` test in tracking.tcl
used to hung in valgrind CI quite a lot.

It turns out the reason is that with valgrind and a busy machine, the
server cron active expire cycle could easily run in the same event loop
as the command that created `mykey`, so that when they key got expired,
there were two change events to broadcast, one that set the key and one
that expired it, but since we used raxTryInsert, the client that was
associated with the "last" change was the one that created the key, so
the NOLOOP filtered that event.

This commit adds a test that reproduces the problem by using lazy expire
in a multi-exec which makes sure the key expires in the same event loop
as the one that added it.

(cherry picked from commit 9b564b525d)
2021-07-21 21:06:49 +03:00
Maxim Galushka f70916f8b0 redis-cli: support for REDIS_REPLY_SET in CSV and RAW output. (#7338)
Fixes #6792. Added support of REDIS_REPLY_SET in raw and csv output of `./redis-cli`

Test:

run commands to test:
  ./redis-cli -3 --csv COMMAND
  ./redis-cli -3 --raw COMMAND

Now they are returning resuts, were failing with: "Unknown reply type: 10" before the change.

(cherry picked from commit 96bb078577)
2021-07-21 21:06:49 +03:00
M Sazzadul Hoque ef3ad68020 Fix 6.2.4 release month to June (#9027) 2021-06-01 19:03:06 +03:00
Oran Agra f9470c9a17 Redis 6.2.4
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2021-06-01 17:03:36 +03:00
Oran Agra e9a1438ac4 Fix integer overflow in STRALGO LCS (CVE-2021-32625) (#9011)
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.

(cherry picked from commit 1ddecf1958)
2021-06-01 17:03:36 +03:00
YaacovHazan 5102c0da92 unregister AE_READABLE from the read pipe in backgroundSaveDoneHandlerSocket (#8991)
In diskless replication, we create a read pipe for the RDB, between the child and the parent.
When we close this pipe (fd), the read handler also needs to be removed from the event loop (if it still registered).
Otherwise, next time we will use the same fd, the registration will be fail (panic), because
we will use EPOLL_CTL_MOD (the fd still register in the event loop), on fd that already removed from epoll_ctl

(cherry picked from commit 501d775583)
2021-06-01 17:03:36 +03:00
Wen Hui d68b775df5 fix sentinel test failure (#8983)
fix for recent breakage from #8958, did a mistake when updating the pr.

(cherry picked from commit be6ce8a92a)
2021-06-01 17:03:36 +03:00
Wen Hui ed995ffa03 [SENTINEL] reset sentinel-user/pass to NULL when user config with empty string (#8958)
(cherry picked from commit ae6f58690b)
2021-06-01 17:03:36 +03:00
Madelyn Olson 875a1f07d8 Hide migrate command from slowlog if they include auth (#8859)
Redact commands that include sensitive data from slowlog and monitor

(cherry picked from commit a59e75a475)
2021-06-01 17:03:36 +03:00
patpatbear d070546568 sinterstore: add missing keyspace del event when any source set not exists. (#8949)
this patch fixes sinterstore by add missing keyspace del event when any source set not exists.

Co-authored-by: srzhao <srzhao@sysnew.com>
(cherry picked from commit 46d9f31e94)
2021-06-01 17:03:36 +03:00
Oran Agra 511be70eba Fix crash unlinking a stream with groups rax and no groups (#8932)
When estimating the effort for unlink, we try to compute the effort of
the first group and extrapolate.
If there's a groups rax that's empty, there'a an assertion.

reproduce:
xadd s * a b
xgroup create s bla $
xgroup destroy s bla
unlink s

(cherry picked from commit 97108845e2)
2021-06-01 17:03:36 +03:00
Oran Agra 840f7f61c5 fix redis-benchmark to ignore unsupported configs (#8916)
Redis Enterprise supports the CONFIG GET command, but it replies with am
empty array since the save and appendonly configs are not supported.
before this fix redis-benchmark would segfault for trying to access the
error string on an array type reply.
see #8869

(cherry picked from commit 4d1094e8be)
2021-06-01 17:03:36 +03:00
Wang Yuan 9d69d6e8fb Fix wrong COW memory in log (#8917)
Always 0 MB of memory used by copy-on-write, introduced in #8645.

(cherry picked from commit 81e2d7272b)
2021-06-01 17:03:36 +03:00
yoav-steinberg 15c078df61 Enforce client output buffer soft limit when no traffic. (#8833)
When client breached the output buffer soft limit but then went idle,
we didn't disconnect on soft limit timeout, now we do.
Note this also resolves some sporadic test failures in due to Linux
buffering data which caused tests to fail if during the test we went
back under the soft COB limit.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: sundb <sundbcn@gmail.com>
(cherry picked from commit 152fce5e2c)
2021-06-01 17:03:36 +03:00
Oran Agra e90e5640e7 Redis 6.2.3
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
2021-05-03 22:57:00 +03:00
Oran Agra 2df6695f2b Resolve nonsense static analysis warnings
(cherry picked from commit fd7d51c353)
2021-05-03 22:57:00 +03:00
Oran Agra 92e3b1802f Fix integer overflow in STRALGO LCS (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 with remote
code execution.

(cherry picked from commit f0c5f920d0)
2021-05-03 22:57:00 +03:00
Oran Agra 0463520693 Fix integer overflow in intset (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).

(cherry picked from commit 29900d4e6b)
2021-05-03 22:57:00 +03:00
sundb 922e3bf59f Fix memory leak in moduleDefragGlobals (#8853)
(cherry picked from commit 5100ef9f82)
2021-05-03 22:57:00 +03:00
Huang Zhw 42f2ad0516 Improve redis-cli help. When help command, we only match command (#8879)
prefix args not all args. So when we help commands with subcommands,
all subcommands will be output.

(cherry picked from commit 0b1b9edb28)
2021-05-03 22:57:00 +03:00
Binbin 1eab6202ac redis-benchmark: Add zfree(data) and fix lrange size / text mismatch (#8872)
missing zfree(data) in redis-benchmark.

And also correct the wrong size in lrange.
the text mentioned 500, but size was 450, changed to 500

(cherry picked from commit 1eff8564c7)
2021-05-03 22:57:00 +03:00
Binbin d30ee6c44a redis-cli: Do not use hostsocket when we got redirected in cluster mode (#8870)
When redis-cli was used with both -c (cluster) and -s (unix socket),
it would have kept trying to use that unix socket, even if it got
redirected by the cluster (resulting in an infinite loop).

(cherry picked from commit 416f277339)
2021-05-03 22:57:00 +03:00
filipe oliveira 4f2da00e94 redis-benchmark: Error/Warning handling updates. (#8869)
- Immediately exit on errors that are not related to topology updates.
- Deprecates the `-e` option ( retro compatible ) and warns that we now
  exit immediately on errors that are not related to topology updates.
- Fixed wrongfully failing on config fetch error (warning only). This only affects RE.

Bottom line:
- MOVED and ASK errors will not show any warning (unlike the throttled error with `-e` before).
- CLUSTERDOWN still prints an error unconditionally and sleeps for 1 second.
- other errors are fatal.

(cherry picked from commit ef6f902372)
2021-05-03 22:57:00 +03:00
Huang Zhw 34b9a3fa2e Fix potential CONFIG SET bind test failure. (#8875)
Use an invalid IP address to trigger CONFIG SET bind failure, instead of DNS which is not guaranteed to always fail.

(cherry picked from commit 2b22fffc78)
2021-05-03 22:57:00 +03:00
yoav-steinberg 5c7b869e61 Bump freebsd-vm version to fix CI failures (#8876)
Specifically we had issues with NTP sync failure which was resolved here: https://github.com/vmactions/freebsd-vm/commit/457af7345642e154a79d219971a2d4a7c7fe2118

(cherry picked from commit 2e88b06396)
2021-05-03 22:57:00 +03:00
Oran Agra 6cbea7d29b Prevent replicas from sending commands that interact with keyspace (#8868)
This solves an issue reported in #8712 in which a replica would bypass
the client write pause check and cause an assertion due to executing a
write command during failover.

The fact is that we don't expect replicas to execute any command other
than maybe REPLCONF and PING, etc. but matching against the ADMIN
command flag is insufficient, so instead i just block keyspace access
for now.

(cherry picked from commit 46f4ebbe84)
2021-05-03 22:57:00 +03:00
Yossi Gottlieb 8cfa37fc21 Remove redundant -latomic on arm64. (#8867)
(cherry picked from commit ebfbb09109)
2021-05-03 22:57:00 +03:00
Yang Bodong 16c53085f7 When the password is wrong, redis-benchmark should exit (#8855)
(cherry picked from commit 8423b77f14)
2021-05-03 22:57:00 +03:00
Istemi Ekin Akkus 2ccb926314 Modules: Fix RM_GetClusterNodeInfo() to correctly populate the master_id (#8846)
(cherry picked from commit af035c1e1d)
2021-05-03 22:57:00 +03:00
zyxwvu Shi 6384fe3414 Use monotonic clock to check for Lua script timeout. (#8812)
This prevents a case where NTP moves the system clock
forward resulting in a false detection of a busy script.

Signed-off-by: zyxwvu Shi <i@shiyc.cn>
(cherry picked from commit f61c37cec9)
2021-05-03 22:57:00 +03:00
Wang Yuan ef64333e63 Expire key firstly and then notify keyspace event (#8830)
Modules event subscribers may get wrong things in notifyKeyspaceEvent callback,
such as wrong number of keys, or be able to lookup this key.
This commit changes the order to be like the one in evict.c.

Cleanup:
Since we know the key exists (it expires now), db*Delete is sure to return 1,
so there's no need to check it's output (misleading).

(cherry picked from commit 63acfe4b00)
2021-05-03 22:57:00 +03:00
bugwz 0851705304 Print the number of abnormal line in AOF (#8823)
When redis-check-aof finds an error, it prints the line number for faster troubleshooting. 

(cherry picked from commit 761d7d2771)
2021-05-03 22:57:00 +03:00
Huang Zhw b97a4ad7f2 Fix migrateCommand may migrate wrong value. (#8815)
This scene is hard to happen. When first attempt some keys expired,
only kv position is updated not ov. Then socket err happens, second
attempt is taken. This time kv items may be mismatching with ov items.

(cherry picked from commit 080d4579db)
2021-05-03 22:57:00 +03:00
Madelyn Olson d01afe885c Fix memory leak when doing lazyfreeing client tracking table (#8822)
Interior rax pointers were not being freed

(cherry picked from commit c73b4ddfd9)
2021-05-03 22:57:00 +03:00
Oran Agra 959d6035e5 Merge 6.2.2 release
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
Release 6.2.2
2021-04-20 08:03:58 +03:00
Oran Agra aa730ef1ea Redis 6.2.2 2021-04-19 21:39:40 +03:00
Oran Agra f5ca1f9ee9 Merge unstable into 6.2 2021-04-19 21:36:00 +03:00
Viktor Söderqvist 61d3fdb474 Fix space included in links in modules API doc (#8818) 2021-04-19 21:35:14 +03:00
Hanna Fadida 53a4d6c3b1 Modules: adding a module type for key space notification (#8759)
Adding a new type mask ​for key space notification, REDISMODULE_NOTIFY_MODULE, to enable unique notifications from commands on REDISMODULE_KEYTYPE_MODULE type keys (which is currently unsupported).

Modules can subscribe to a module key keyspace notification by RM_SubscribeToKeyspaceEvents,
and clients by notify-keyspace-events of redis.conf or via the CONFIG SET, with the characters 'd' or 'A' 
(REDISMODULE_NOTIFY_MODULE type mask is part of the '**A**ll' notation for key space notifications).

Refactor: move some pubsub test infra from pubsub.tcl to util.tcl to be re-used by other tests.
2021-04-19 21:33:26 +03:00
guybe7 f40ca9cb58 Modules: Replicate lazy-expire even if replication is not allowed (#8816)
Before this commit using RM_Call without "!" could cause the master
to lazy-expire a key (delete it) but without replicating to replicas.
This could cause the replica's memory usage to gradually grow and
could also cause consistency issues if the master and replica have
a clock diff.
This bug was introduced in #8617

Added a test which demonstrates that scenario.
2021-04-19 17:16:02 +03:00
7a3d1487e4 ACL channels permission handling for save/load scenario. (#8794)
In the initial release of Redis 6.2 setting a user to only allow pubsub access to
a specific channel, and doing ACL SAVE, resulted in an assertion when
ACL LOAD was used. This was later changed by #8723 (not yet released),
but still not properly resolved (now it errors instead of crash).

The problem is that the server that generates an ACL file, doesn't know what
would be the setting of the acl-pubsub-default config in the server that will load it.
so ACL SAVE needs to always start with resetchannels directive.

This should still be compatible with old acl files (from redis 6.0), and ones from earlier
versions of 6.2 that didn't mess with channels.

Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
2021-04-19 13:27:44 +03:00
sundb 3a955d9ad4 Fix ouput buffer limit test (#8803)
The tail size of c->reply is 16kb, but in the test only publish a
few chars each time, due to a change in #8699, the obuf limit
is now checked a new memory allocation is made, so this test
would have sometimes failed to trigger a soft limit disconnection
in time.

The solution is to write bigger payloads to the output buffer, but
still limit their rate (not more than 100k/s).
2021-04-19 10:08:07 +03:00
Wen Hui 0413fbc7d0 fix invalid master_link_down_since_seconds in info repication (#8785)
When replica never successfully connect to master, server.repl_down_since
will be initialized to 0, therefore, the info master_link_down_since_seconds
was showing the current unix timestamp, which does not make much sense.

This commit fixes the issue by showing master_link_down_since_seconds to -1.
means the replica never connect to master before.

This commit also resets this variable back to 0 when a replica is turned into
a master, so that it'll behave the same if the master is later turned into a
replica again.

The implication of this change is that if some app is checking if the value is > 60
do something, like conclude the replica is stale, this could case harm (changing
a big positive number with a small one).
2021-04-19 09:34:21 +03:00
Yossi Gottlieb c0f5c678c2 Revert cluster slot migration tests. (#8806)
Disables #8649 and subsequent attempts to stabilize the test.
2021-04-18 20:51:08 +03:00
Oran Agra a9897b0084 Fix timing of new replication test (#8807)
In github actions CI with valgrind, i saw that even the fast replica
(one that wasn't paused), didn't get to complete the replication fast
enough, and ended up getting disconnected by timeout.

Additionally, due to a typo in uname, we didn't get to actually run the
CPU efficiency part of the test.
2021-04-18 15:12:34 +03:00
Oran Agra f4b5a4d869 Improve testsuite print of log file (#8805)
1. the `dump_logs` option would have printed only logs of servers that were
   spawn before the test proc started, and not ones that the test proc
   started inside it.
2. when a server proc catches an exception it should normally forward the
   exception upwards, specifically when it's an assertion that should be
   caught by a test proc above. however, in `durable` mode, we caught all
   exceptions printed them to stdout and let the code continue,
   this was wrong to do for assertions, which should have still been
   propagated to the test function.
3. don't bother to search for crash log to print if we printed the the
   entire log anyway
4. if no crash log was found, no need to print anything (i.e. the fact it
   wasn't found)
5. rename warnings_from_file to crashlog_from_file
2021-04-18 11:55:54 +03:00
guybe7 a60016e061 ReplicationCron: Prevent invalid access to freed pointer (#8799)
Fixes #8797
2021-04-16 16:56:38 +03:00
Wen Hui 374401d786 Avoid close before logging to preserve errno (#8703) 2021-04-15 21:11:40 +03:00
guybe7 7d749d810b Fix error reply in case zset command is not the STORE variant (#8793) 2021-04-15 18:36:51 +03:00
guybe7 d63d02601f Add a timeout mechanism for replicas stuck in fullsync (#8762)
Starting redis 6.0 (part of the TLS feature), diskless master uses pipe from the fork
child so that the parent is the one sending data to the replicas.
This mechanism has an issue in which a hung replica will cause the master to wait
for it to read the data sent to it forever, thus preventing the fork child from terminating
and preventing the creations of any other forks.

This PR adds a timeout mechanism, much like the ACK-based timeout,
we disconnect replicas that aren't reading the RDB file fast enough.
2021-04-15 17:18:51 +03:00
Bonsai 0a2621c673 clean condition and variable store to nwritten that is never read (#8788) 2021-04-14 22:44:08 -07:00
Bonsai 07601b7e07 fix typo, stracture to structure (#8784) 2021-04-14 15:46:54 +03:00
YaacovHazan 645c664fbb stabilized and improve pendingquerybuf test suit (#8780)
replace the hardcoded after 2000, with waiting for the sync and
wait for condition
2021-04-14 11:49:00 +03:00
Viktor Söderqvist d7920ff9b1 Modules API docs: Sections and links (#8442)
* Modules API docs: Link API function names to their definitions

Occurrences of API functions are linked to their definition.

A function index with links to all functions is added on the bottom
of the page.

Comment blocks in module.c starting with a markdown h2 heading are
used as sections. A table of contents is generated from these
headings.

The functions names are changed from h2 to h3, since they are now
rendered as sub-headings within each section.

Existing sections in module.c are used with some minor changes.
Some documentation text is added or sligtly modified.

The markdown renderer will add IDs which may clash with our
generated IDs. By prefixing section IDs with "section-" we make
them different.

Replace double dashes with a unicode long ndash
2021-04-14 00:58:05 +03:00
Huang Zhw c6cd1e59b1 Remove extra param in role-change event of sentinelEvent. (#8742) 2021-04-13 20:19:42 +03:00
Viktor Söderqvist 4938052f6b Small doc fix for stream module API (#8757)
In a code example, using RedisModule_FreeString instead of
RedisModule_Free makes it behave correctly regardless of whether
automatic memory is used or not.
2021-04-13 20:14:12 +03:00
Oran Agra b278e44376 Revert "Fix: server will crash if rdbload or rdbsave method is not provided in module (#8670)" (#8771)
This reverts commit 808f3004f0.
2021-04-13 17:41:46 +03:00
Oran Agra c07e16fadd Add more attempts to a timing sensitive test (#8770) 2021-04-13 17:35:10 +03:00
Wen Hui 38da8d07d0 Clean up no-conf server warning for sentinel mode (#8769) 2021-04-13 16:28:54 +03:00
Oran Agra 733daef127 fix access to uninitialized var in checkClientPauseTimeoutAndReturnIfPaused (#8765)
server.client_pause_end_time is uninitialized, or actually 0, at startup,
which means this method would think the timeout was reached
and go look for paused clients.

This causes no harm since unpauseClients will not find any paused clients.
2021-04-13 08:41:12 +03:00
Oran Agra 175a9e3199 Fix busy loop in ae.c when timer event is about to fire (#8764)
The code used to decide on the next time to wake on a timer with
microsecond accuracy, but when deciding to go to sleep it used
milliseconds accuracy (with truncation), this means that it would wake
up too early, see that there's no timer to process, and go to sleep
again for 0ms again and again until the right microsecond arrived.

i.e. a timer for 100ms, would sleep for 99ms, but then do a busy loop
through the kernel in the last millisecond, triggering many calls to
beforeSleep.

The fix is to change all the logic in ae.c to work with microseconds,
which is good since most of the ae backends support micro (or even nano)
seconds. however the epoll backend, doesn't support micro, so to avoid
this problem it needs to round upwards, rather than truncate.

Issue created by the monotonic timer PR #7644 (redis 6.2)
Before that, all the timers in ae.c were in milliseconds (using
mstime), so when it requested the backend to sleep till the next timer
event, it would have worked ok.
2021-04-13 07:35:03 +03:00
Yossi Gottlieb 5e3a15ae1b Fix failing cluster tests. (#8763)
Disable replica migration to avoid a race condition where the
migrated-from node turns into a replica.

Long term, this test should probably be improved to handle multiple
slots and accept such auto migrations but this is a quick fix to
stabilize the CI without completely dropping this test.
2021-04-13 00:00:57 +03:00
Wang Yuan a0e19e3cf1 Fix wrong check for aof fsync and handle aof fsync errno (#8751)
The bio aof fsync fd may be closed by main thread (AOFRW done handler)
and even possibly reused for another socket, pipe, or file.
This can can an EBADF or EINVAL fsync error, which will lead to -MISCONF errors failing all writes.
We just ignore these errno because aof fsync did not really fail.

We handle errno when fsyncing aof in bio, so we could know the real reason
when users get -MISCONF Errors writing to the AOF file error

Issue created with #8419
2021-04-11 08:14:31 +03:00
yjph cd03e293c3 Fix the display of make install (#8667) 2021-04-10 21:25:53 +03:00
Wen Hui d96f47cf06 use getPositiveLongFromObjectOrReply for positive check of args (#8750)
just a cleanup
2021-04-07 10:28:53 +03:00
Yang Bodong 4c14e8668c Fix out of range confusing error messages (XAUTOCLAIM, RPOP count) (#8746)
Fix out of range error messages to be clearer (avoid mentioning 9223372036854775807)
* Fix XAUTOCLAIM COUNT option confusing error msg
* Fix other RPOP and alike error message to mention positive
2021-04-07 10:01:28 +03:00
Oran Agra 4de1031d8a update help.h (#8747) 2021-04-06 12:42:18 +03:00
yjph 073598ed8f Update the location information in some URLs (#8595) 2021-04-06 12:29:02 +03:00
Bonsai 808f3004f0 Fix: server will crash if rdbload or rdbsave method is not provided in module (#8670)
With this fix, module data type registration will fail if the load or save callbacks are not defined, or the optional aux load and save callbacks are not either both defined or both missing.
2021-04-06 12:09:36 +03:00
Yossi Gottlieb 4724dd439e Clean up and stabilize cluster migration tests. (#8745)
This is work in progress, focusing on two main areas:
* Avoiding race conditions with cluster configuration propagation.
* Ignoring limitations with redis-cli --cluster fix which makes it hard
  to distinguish real errors (e.g. failure to fix) from expected
  conditions in this test (e.g. nodes not agreeing on configuration).
2021-04-06 11:57:57 +03:00
Huang Zhw 3b74b55084 Fix "default" and overwritten / reset users will not have pubsub channels permissions by default. (#8723)
Background:
Redis 6.2 added ACL control for pubsub channels (#7993), which were supposed
to be permissive by default to retain compatibility with redis 6.0 ACL. 
But due to a bug, only newly created users got this `acl-pubsub-default` applied,
while overwritten (updated) users got reset to `resetchannels` (denied).

Since the "default" user exists before loading the config file,
any ACL change to it, results in an update / overwrite.

So when a "default" user is loaded from config file or include ACL
file with no channels related rules, the user will not have any
permissions to any channels. But other users will have default
permissions to any channels.

When upgraded from 6.0 with config rewrite, this will lead to
"default" user channels permissions lost.
When users are loaded from include file, then call "acl load", users
will also lost channels permissions.

Similarly, the `reset` ACL rule, would have reset the user to be denied
access to any channels, ignoring `acl-pubsub-default` and breaking
compatibility with redis 6.0.

The implication of this fix is that it regains compatibility with redis 6.0,
but breaks compatibility with redis 6.2.0 and 2.0.1. e.g. after the upgrade,
the default user will regain access to pubsub channels.

Other changes:
Additionally this commit rename server.acl_pubusub_default to
server.acl_pubsub_default and fix typo in acl tests.
2021-04-05 23:13:20 +03:00
Huang Zhw a3da3e592d redis-cli --bigkeys / memkeys, report detailed error on dbsize failure (#8740)
When DBSIZE failed (e.g. on AUTH error), the printed error didn't reflect the reason.
2021-04-05 08:30:41 +03:00
Sokolov YuraandOran Agra 1cab962098 Add cluster-allow-replica-migration option. (#5285)
Previously (and by default after commit) when master loose its last slot
(due to migration, for example), its replicas will migrate to new last slot
holder.

There are cases where this is not desired:
* Consolidation that results with removed nodes (including the replica, eventually).
* Manually configured cluster topologies, which the admin wishes to preserve.

Needlessly migrating a replica triggers a full synchronization and can have a negative impact, so
we prefer to be able to avoid it where possible.

This commit adds 'cluster-allow-replica-migration' configuration option that is
enabled by default to preserve existed behavior. When disabled, replicas will
not be auto-migrated.

Fixes #4896

Co-authored-by: Oran Agra <oran@redislabs.com>
2021-04-04 09:43:24 +03:00
Wang Yuan 1eb85249e7 Handle remaining fsync errors (#8419)
In `aof.c`, we call fsync when stop aof, and now print a log to let user know that if fail.
In `cluster.c`, we now return error, the calling function already handles these write errors.
In `redis-cli.c`, users hope to save rdb, we now print a message if fsync failed.
In `rio.c`, we now treat fsync errors like we do for write errors. 
In `server.c`, we try to fsync aof file when shutdown redis, we only can print one log if fail.
In `bio.c`, if failing to fsync aof file, we will set `aof_bio_fsync_status` to error , and reject writing just like last writing aof error,  moreover also set INFO command field `aof_last_write_status` to error.
2021-04-01 12:45:15 +03:00
Valentino Geron 44d8b039e8 Fix XAUTOCLAIM response to return the next available id as the cursor (#8725)
This command used to return the last scanned entry id as the cursor,
instead of the next one to be scanned.
so in the next call, the user could / should have sent `(cursor` and not
just `cursor` if he wanted to avoid scanning the same record twice.

Scanning the record twice would look odd if someone is checking what
exactly was scanned, but it also has a side effect of incrementing the
delivery count twice.
2021-04-01 12:13:55 +03:00
Oran Agra 370ab4c4db Solve sentinel test issue in TLS due to recent tests change. (#8728)
5629dbe71 added a change that configures the tcp (plaintext) port
alongside the tls port, this causes the INFO command for tcp_port
to return that instead of the tls port when running in tls, and that broke
the sentinel tests that query it.

the fix is to add a method that gets the right port from CONFIG instead
of relying on the tcp_port info field.
2021-04-01 09:44:44 +03:00
guybe7 843f769b96 zsetAdd: Fix wrong reply in case of INCR and GT/LT (#8717)
If GT/LT fails the operation we need to reply with
nill (like failure due to NX).

Other changes:
Add the missing $encoding suffix to many zset tests

Note: there's a behavior change just in case of INCR + GT/LT that fails.
The old code was replying with the wrong (rejected) score, and now it'll reply with nil.

Note that that's anyway a corner case so this "behavior change" shouldn't have too much affect.
Using GT/LT with INCR has a predictable result even before we run the command
(INCR GT will only only / always fail if the increment is negative).
2021-04-01 09:33:53 +03:00
Wen Hui d5935bb0a4 generalize config file check for sentinel (#8730)
The implications of this change is just that in the past when a config file was missing,
in some cases it was exiting before printing the sever startup prints and sometimes after,
and now it'll always exit before printing them.
2021-04-01 09:01:05 +03:00
wuYin e5d50b236c reuse existing range comparators in the zset (#8714)
There are 2 common range comparators for skiplist: zslValueGteMin and
zslValueLteMax, but they're not being reused in zslDeleteRangeByScore

This is a small change to make code cleaner.
2021-04-01 08:50:23 +03:00
Dvir Volk 07f39ae47b Added macros for RM_log logging levels (#4246)
Added macros for RM_log logging levels, to avoid typos and the need
to memorize the level strings by heart
2021-04-01 08:44:57 +03:00
sundb 569a3f4548 Use chi-square for random distributivity verification in test (#8709)
Problem:
Currently, when performing random distribution verification, we determine
the probability of each element occurring in the sum, but the probability is
only an estimate, these tests had rare sporadic failures, and we cannot verify
what the probability of failure will be.

Solution:
Using the chi-square distribution instead of the original random distribution
validation makes the test more reasonable and easier to find problems.
2021-04-01 08:20:15 +03:00
yoav-steinberg 636aa8de76 Always use prev node if there's some room in it setDeferredReply (#8729)
this is a followup PR for #8699
instead of copying the deferred reply data to the previous node only if it has room for the entire thing,
we can now split the new payload, put part of it into the spare space in the prev node,
and the rest may fit into the next node.
2021-04-01 00:07:19 +03:00
guybe7 68ba4d43ac Initialize RedisModuleDigest with zeroes (#8731)
In case the module's digest function doesn't modify
'md' it'll contain garbage and result in wrong
DEBUG DIGEST
2021-03-31 23:56:04 +03:00
guybe7 2c120af61e XAUTOCLAIM: JUSTID should prevent incrementing delivery_count (#8724)
To align with XCLAIM and the XAUTOCLAIM docs
2021-03-30 23:48:01 +03:00
Jérôme Loyet 91f4f41665 Add replica-announced config option (#8653)
The 'sentinel replicas <master>' command will ignore replicas with
`replica-announced` set to no.

The goal of disabling the config setting replica-announced is to allow ghost
replicas. The replica is in the cluster, synchronize with its master, can be
promoted to master and is not exposed to sentinel clients. This way, it is
acting as a live backup or living ghost.

In addition, to prevent the replica to be promoted as master, set
replica-priority to 0.
2021-03-30 23:40:22 +03:00
Yossi Gottlieb 6a052af890 Cluster migration test cleanup. (#8726)
* Dump more output on error (always, cluster tests currently have no
verbose flag).
* Slow down redis-cli check iteration.
2021-03-30 23:33:01 +03:00
Viktor Söderqvist 5629dbe715 Add support for plaintext clients in TLS cluster (#8587)
The cluster bus is established over TLS or non-TLS depending on the configuration tls-cluster. The client ports distributed in the cluster and sent to clients are assumed to be TLS or non-TLS also depending on tls-cluster.

The cluster bus is now extended to also contain the non-TLS port of clients in a TLS cluster, when available. The non-TLS port of a cluster node, when available, is sent to clients connected without TLS in responses to CLUSTER SLOTS, CLUSTER NODES, CLUSTER SLAVES and MOVED and ASK redirects, instead of the TLS port.

The user was able to override the client port by defining cluster-announce-port. Now cluster-announce-tls-port is added, so the user can define an alternative announce port for both TLS and non-TLS clients.

Fixes #8134
2021-03-30 23:11:32 +03:00
yoav-steinberg 8cbd858d45 Minor client output buffer optimizations (#8699)
* Avoid checking output limits if buffer didn't grow.
* Use previouse node in case it has room in deferred output node.
2021-03-30 23:06:29 +03:00
JunhuaY 28375ff63e re-fix config rewrite for empty save directive (#8722)
the bug was also discussed in #8716, and was solved in #8719, but incompletely:
when the server is started, and the save option is default, if you issue the " config set save "" "
to change the save option, and then issue the “config rewrite” command, the " save "" " won't be saved.
2021-03-30 22:49:06 +03:00
Oran Agra cd81dcf18b solve race conditions in psync2-pingoff test (#8720)
Another test race condition in the macos tests.
the test was waiting for PINGs to be generated and put on the replication stream,
but waiting for 1 or 2 seconds doesn't really guarantee that.
then the test that expected 6 full syncs, found only 4
2021-03-30 11:41:06 +03:00
Yossi Gottlieb 65311a3360 Fix config rewrite with an empty "save" parameter. (#8719) 2021-03-29 18:53:20 +03:00
Sokolov YuraandYossi Gottlieb 315df9ada0 Add cluster slot migration tests (#8649)
Add tests for fixing migrating slot at all stages:

1. when migration is half inited on "migrating" node
2. when migration is half inited on "importing" node
3. migration inited, but not finished
4. migration is half finished on "migrating" node
5. migration is half finished on "importing" node

Also add tests for many simultaneous slot migrations.

Co-authored-by: Yossi Gottlieb <yossigo@gmail.com>
2021-03-29 13:52:02 +03:00
Meir Shpilraien (Spielrein) 036963a7da Restore old client 'processCommandAndResetClient' to fix false dead client indicator (#8715)
'processCommandAndResetClient' returns 1 if client is dead. It does it
by checking if serve.current_client is NULL. On script timeout, Redis will re-enter
'processCommandAndResetClient' and when finish we will set server.current_client
to NULL. This will cause later to falsely return 1 and think that the client that
sent the timed-out script is dead (Redis to stop reading from the client buffer).
2021-03-29 13:34:16 +03:00
Steve 54fd3d4024 Fix redis-cli handling of rediss:// URL scheme (#8705) 2021-03-29 00:25:30 +03:00
Huang Zhw a79bb86856 Remove redundant defrag dictEntry in activeDefragSdsListAndDict. (#8713)
In activeDefragSdsListAndDict when defrag list key sucess, the key
in dict is also replaced. Then the corresponding dictEntry is
defraged. But the dictEntry will be defraged in next step by defrag
the dict values too.
2021-03-28 15:16:30 +03:00
Huang Zhw e138698e54 make processCommand check publish channel permissions. (#8534)
Add publish channel permissions check in processCommand.

processCommand didn't check publish channel permissions, so we can
queue a publish command in a transaction. But when exec the transaction,
it will fail with -NOPERM.

We also union keys/commands/channels permissions check togegher in
ACLCheckAllPerm. Remove pubsubCheckACLPermissionsOrReply in 
publishCommand/subscribeCommand/psubscribeCommand. Always 
check permissions in processCommand/execCommand/
luaRedisGenericCommand.
2021-03-26 14:10:01 +03:00
sundb db6655deb4 Fix crash when hz config out of range (#8658)
Fix crash when hz config out of range
2021-03-25 18:59:52 -07:00
Igor cf0a909e2d Introduce fast path to bypass expensive serveClientsBlockedOnKeyByModule call (#8689)
Introduce fast path to bypass expensive serveClientsBlockedOnKeyByModule call
2021-03-25 07:16:03 -07:00
Wang Yuan 1976acfe3e Remove deprecated comments about saving expire time (#8692) 2021-03-25 15:09:12 +02:00
Oran Agra 497351ad07 Fix SLOWLOG for blocked commands (#8632)
* SLOWLOG didn't record anything for blocked commands because the client
  was reset and argv was already empty. there was a fix for this issue
  specifically for modules, now it works for all blocked clients.
* The original command argv (before being re-written) was also reset
  before adding the slowlog on behalf of the blocked command.
* Latency monitor is now updated regardless of the slowlog flags of the
  command or its execution (their purpose is to hide sensitive info from
  the slowlog, not hide the fact the latency happened).
* Latency monitor now uses real_cmd rather than c->cmd (which may be
  different if the command got re-written, e.g. GEOADD)

Changes:
* Unify shared code between slowlog insertion in call() and
  updateStatsOnUnblock(), hopefully prevent future bugs from happening
  due to the later being overlooked.
* Reset CLIENT_PREVENT_LOGGING in resetClient rather than after command
  processing.
* Add a test for SLOWLOG and BLPOP

Notes:
- real_cmd == c->lastcmd, except inside MULTI and Lua.
- blocked commands never happen in these cases (MULTI / Lua)
- real_cmd == c->cmd, except for when the command is rewritten (e.g.
  GEOADD)
- blocked commands (currently) are never rewritten
- other than the command's CLIENT_PREVENT_LOGGING, and the
  execution flag CLIENT_PREVENT_LOGGING, other cases that we want to
  avoid slowlog are on AOF loading (specifically CMD_CALL_SLOWLOG will
  be off when executed from execCommand that runs from an AOF)
2021-03-25 10:20:27 +02:00
hzzb 4a2c4477c9 Fix misleading comment (#8690)
REPLICAOF actually specifies which master to attach.
2021-03-24 19:39:31 -07:00
Madelyn Olson 98d2e0017d Skip unecessary check for pong recieved (#8585) 2021-03-24 19:37:38 -07:00
Qu Chen 7de6451818 Properly initialize variable to make valgrind happy in checkChildrenDone(). Removed usage for the obsolete wait3() and wait4() in favor of waitpid(), and properly check for the exit status code. (#8666) 2021-03-24 08:41:05 -07:00
yoav-steinberg 3060de88ce Remove cron saving during BGSAVE test. (#8688)
This fixes a race where a bgsave can start during the test after we verified no bgsave is running.
2021-03-24 15:14:47 +02:00
Oran Agra f6e1a94e03 Corrupt stream key access to uninitialized memory (#8681)
the corrupt-dump-fuzzer test found a case where an access to a corrupt
stream would have caused accessing to uninitialized memory.
now it'll panic instead.

The issue was that there was a stream that says it has more than 0
records, but looking for the max ID came back empty handed.

p.s. when sanitize-dump-payload is used, this corruption is detected,
and the RESTORE command is gracefully rejected.
2021-03-24 11:33:49 +02:00
Itamar Haber dc39b0c190 Fixes a typo in DEBUG HELP (#8638) 2021-03-24 11:11:38 +02:00
chenyangyang ccc39f64f8 Add RM_GetAbsExpire/RM_SetAbsExpire for module. (#8564)
Add a check to ensure that the expire parameters in RM_SetExpire and RM_SetAbsExpire must be positive.
2021-03-24 11:10:15 +02:00
Oran Agra b45b0d81bb Fix crash report killed by message (#8683)
We sometimes see the crash report saying we were killed by a random
process even in cases where the crash was spontanius in redis.
for instance, crashes found by the corrupt-dump test.

It looks like this si_pid is sometimes left uninitialized, and a good
way to tell if the crash originated in redis or trigged by outside is to
look at si_code, real signal codes are always > 0, and ones generated by
kill are have si_code of 0 or below.
2021-03-24 08:33:24 +02:00
yoav-steinberg d026647f4f Avoid evaluating log arguments when log filtered by level. (#8685) 2021-03-24 08:22:12 +02:00
cl1118 a2af2a306b Update sentinel.c (#8686) 2021-03-23 13:51:53 +02:00
linyi-xq 725762c48d clusterState->mf_master_offset init offset change to -1 (#8651)
resolve cluster failover timeout when master repl_offset is 0
2021-03-22 20:00:33 -07:00
Wen Hui 6bf81eb7b1 add missing flags in sentinel instance info (#8682) 2021-03-22 20:13:30 +02:00
Huang Zhw eef45934fa Cleanup: sentinelGetInstanceTypeString is unused. (#8680)
sentinelGetInstanceTypeString is the same as sentinelRedisInstanceTypeStr,
and not referenced by any functions.
2021-03-22 16:13:24 +02:00
Yossi Gottlieb c4ef1efdb7 Add support for reading encrypted keyfiles. (#8644) 2021-03-22 13:27:46 +02:00
Yossi Gottlieb c3df27d1ea Fix slowdown due to child reporting CoW. (#8645)
Reading CoW from /proc/<pid>/smaps can be slow with large processes on
some platforms.

This measures the time it takes to read CoW info and limits the duty
cycle of future updates to roughly 1/100.

As current_cow_size no longer represnets a current, fixed interval value
there is also a new current_cow_size_age field that provides information
about the age of the size value, in seconds.
2021-03-22 13:25:58 +02:00
Oran Agra 2f717c156a fix race in diskless load cluster tests (#8674) 2021-03-22 10:51:13 +02:00
Oran Agra a7c02b19bf Fix race in replication test (#8679)
Since redis 6.2, redis immediately tries to connect to the master, not
waiting for replication cron.

in the slow freebsd CI, this test failed and master_link_status was
already "up" when INFO was called.
2021-03-22 10:50:39 +02:00
Huang Zhw 0418253a9b Change the order of start startLoadingFile call in redis-check-rdb. (#8676)
redis_check_rdb in some scenarios only stopLoading is called because
startLoadingFile is called too late.
2021-03-22 09:18:56 +02:00
Meir Shpilraien (Spielrein) 9ae4f5c73d Fix script kill to work also on scripts that use pcall (#8661)
pcall function runs another LUA function in protected mode, this means
that any error will be caught by this function and will not stop the LUA
execution. The script kill mechanism uses error to stop the running script.
Scripts that uses pcall can catch the error raise by the script kill mechanism,
this will cause a script like this to be unkillable:

local f = function()
        while 1 do
                redis.call('ping')
        end
end
while 1 do
        pcall(f)
end

The fix is, when we want to kill the script, we set the hook function to be invoked 
after each line. This will promise that the execution will get another
error before it is able to enter the pcall function again.
2021-03-17 18:52:11 +02:00
Theo Buehler 169be0426c Fixes for systems with 64-bit time (#8662)
Some operating systems (e.g., NetBSD and OpenBSD) have switched to
using a 64-bit integer for time_t on all platforms. This results in currently
harmless compiler warnings due to potential truncation.
These changes fix these minor portability concerns.

* Fix format string for systems with 64 bit time
* use llabs to avoid truncation with 64 bit time
2021-03-17 15:45:38 +02:00
Wen Hui 6097d14d4d fix wrong comment for data_age unit (#8665)
fix typo
2021-03-17 15:41:13 +02:00
Yossi Gottlieb f657315e60 Cleanup: remove dead code from anet. (#8660)
In the long term we may want to move away from anet completely and have
everything implemented natively in connection.c, instead of having an
extra layer.

For now, just get rid of unused code.
2021-03-17 12:52:24 +02:00
Huang Zhw a19c4058be When tests exit normally, some processes may still be alive (#8647)
In certain scenario start_server may think it failed to start a redis server
although it started successfully. in these cases, it'll not terminate it, and
it'll remain running when the test is over.

In start_server if config doesn't have bind (the minimal.conf in introspection.tcl),
it will try to bind ipv4 and ipv6. One may success while other fails. It will
output "Could not create server TCP listening socket".
wait_server_started uses this message to check whether instance started
successfully. So it will consider that it failed even though redis started successfully.

Additionally, in some cases it wasn't clear to users why the server exited,
since the warning message printed to the log, could in some cases be harmless,
and in some cases fatal.

This PR adds makes a clear distinction between a warning log message and
a fatal one, and changes the test suite to look for the fatal message.
2021-03-16 17:25:30 +02:00
Saravjeet 'Aman' Singh ec34806c1e Update README.md (#3543)
prettify
2021-03-16 16:43:53 +02:00
Garen Chan f141329fb7 Fix incorrect presentation in README.md (#5697) 2021-03-16 16:43:03 +02:00
Ikko Ashimine 6da2bc96e2 Fix typo in t_stream.c (#8592)
arguements -> arguments
2021-03-16 16:41:45 +02:00
Meir Shpilraien (Spielrein) 95360c2e0c Fix issue where error replies are not counted on stats (#8659)
lookupKeyReadOrReply and lookupKeyWriteOrReply might decide to reply to the user
with the given robj reply. This reply might be an error reply and if so addReply
function is used instead of addReplyErrorObject which will cause the error reply not
to be counted on stats. The fix checks the first char in the reply and if its '-' (error)
it uses addReplyErrorObject.
2021-03-16 14:49:59 +02:00
Wen Hui 18b59f35ef Sentinel: fix potential NULL ptr issue for sentinel instance connection (#8627) 2021-03-16 12:23:19 +02:00
Oran Agra 40d555dbb7 set module eviction context flag only in masters (#8631)
REDISMODULE_CTX_FLAGS_EVICT and REDISMODULE_CTX_FLAGS_MAXMEMORY
shouldn't be set when the module is run inside a replica that doesn't do eviction.

one may argue that the database is under eviction (the master does eviction and sends DELs to the replica).
but on the other hand, we don't really know the master's configuration.
all that matters is if the current instance does evictions or not.
2021-03-16 12:17:14 +02:00
Yossi Gottlieb df5f543b65 Server won't start on alpine/libmusl without IPv6. (#8655)
listenToPort attempts to gracefully handle and ignore certain errors but does not store errno prior to logging, which in turn calls several libc functions that may overwrite errno.

This has been discovered due to libmusl strftime() always returning with errno set to EINVAL, which resulted with docker-library/redis#273.
2021-03-16 11:20:03 +02:00
Madelyn Olson e1d98bca5a Redact slowlog entries for config with sensitive data. (#8584)
Redact config set requirepass/masterauth/masteruser from slowlog in addition to showing ACL commands without sensitive values.
2021-03-15 22:00:29 -07:00
guybe7 dba33a943d Missing EXEC on modules propagation after failed EVAL execution (#8654)
1. moduleReplicateMultiIfNeeded should use server.in_eval like
   moduleHandlePropagationAfterCommandCallback
2. server.in_eval could have been set to 1 and not reset back
   to 0 (a lot of missed early-exits after in_eval is already 1)

Note: The new assertions in processCommand cover (2) and I added
two module tests to cover (1)

Implications:
If an EVAL that failed (and thus left server.in_eval=1) runs before a module
command that replicates, the replication stream will contain MULTI (because
moduleReplicateMultiIfNeeded used to check server.lua_caller which is NULL
at this point) but not EXEC (because server.in_eval==1)
This only affects modules as module.c the only user of server.in_eval.

Affects versions 6.2.0, 6.2.1
2021-03-15 21:19:57 +02:00
Huang Zhw 84d056d0f7 Fix typo and outdated comments. (#8640) 2021-03-14 09:41:43 +02:00
Guillem Jover 3a5905fa85 Send the readiness notification when we are ready to accept connections (#8409)
On a replica we do accept connections, even though commands accessing
the database will operate in read-only mode. But the server is still
already operational and processing commands.

Not sending the readiness notification means that on a HA setup where
the nodes all start as replicas (with replicaof in the config) with
a replica that cannot connect to the master server and which might not
come back in a predictable amount of time or at all, the service
supervisor will end up timing out the service and terminating it, with
no option to promote it to be the main instance. This seems counter to
what the readiness notification is supposed to be signaling.

Instead send the readiness notification when we start accepting
commands, and then send the various server status changes as that.

Fixes: commit 641c64ada1
Fixes: commit dfb598cf33
2021-03-14 08:46:26 +02:00
KinWaiYuen 5b48d90049 Optimize CLUSTER SLOTS reply by reducing unneeded loops (#8541)
This commit more efficiently computes the cluster bulk slots response 
by looping over the entire slot space once, instead of for each node.
2021-03-11 22:40:35 -08:00
Wen Hui 3c09ce26fb Sentinel: fix info_refresh time before sentinel get first response (#8567) 2021-03-11 22:28:21 +02:00
guybe7 a4f03bd7eb Fix some memory leaks in propagagte.c (#8636)
Introduced by 3d0b427c30
2021-03-11 13:50:13 +02:00
Oran Agra 4ae1bea323 allow RESET during busy scripts (#8629)
same as DISCARD, this command (RESET) is about client state, not server state
2021-03-11 07:52:27 +02:00
Harkrishn PatroandHarkrishn Patro b70d81f60b Process hello command even if the default user has no permissions. (#8633)
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
2021-03-10 21:19:35 -08:00
cheese1 c945e1126a Changes http to https in texts (#8495) 2021-03-10 19:11:16 +02:00
guybe7 3d0b427c30 Fix some issues with modules and MULTI/EXEC (#8617)
Bug 1:
When a module ctx is freed moduleHandlePropagationAfterCommandCallback
is called and handles propagation. We want to prevent it from propagating
commands that were not replicated by the same context. Example:
1. module1.foo does: RM_Replicate(cmd1); RM_Call(cmd2); RM_Replicate(cmd3)
2. RM_Replicate(cmd1) propagates MULTI and adds cmd1 to also_propagagte
3. RM_Call(cmd2) create a new ctx, calls call() and destroys the ctx.
4. moduleHandlePropagationAfterCommandCallback is called, calling
   alsoPropagates EXEC (Note: EXEC is still not written to socket),
   setting server.in_trnsaction = 0
5. RM_Replicate(cmd3) is called, propagagting yet another MULTI (now
   we have nested MULTI calls, which is no good) and then cmd3

We must prevent RM_Call(cmd2) from resetting server.in_transaction.
REDISMODULE_CTX_MULTI_EMITTED was revived for that purpose.

Bug 2:
Fix issues with nested RM_Call where some have '!' and some don't.
Example:
1. module1.foo does RM_Call of module2.bar without replication (i.e. no '!')
2. module2.bar internally calls RM_Call of INCR with '!'
3. at the end of module1.foo we call RM_ReplicateVerbatim

We want the replica/AOF to see only module1.foo and not the INCR from module2.bar

Introduced a global replication_allowed flag inside RM_Call to determine
whether we need to replicate or not (even if '!' was specified)

Other changes:
Split beforePropagateMultiOrExec to beforePropagateMulti afterPropagateExec
just for better readability
2021-03-10 18:02:17 +02:00
Itamar Haber ceb3a7a8f6 Adds LEN to STRALGO's comment (#8626)
Ref: https://github.com/redis/redis-doc/pull/1529
2021-03-10 16:19:48 +02:00
guybe7 61a73de64d Cleanup ZADD_* flags (#8559)
Have a clear separation between in and out flags

Other changes:

delete dead code in RM_ZsetIncrby: if zsetAdd returned error (happens only if
the result of the operation is NAN or if score is NAN) we return immediately so
there is no way that zsetAdd succeeded and returned NAN in the out-flags
2021-03-10 16:09:43 +02:00
Oran Agra 7778f1b4b0 strip % sign from current_fork_perc info field (#8628)
`master_sync_perc` and `loading_loaded_perc` don't have that sign,
and i think the info field should be a raw floating point number (the name suggests its units).

we already have `used_memory_peak_perc` and `used_memory_dataset_perc` which do add the `%` sign, but:
1) i think it was a mistake but maybe too late to fix now, and maybe not too late to fix for `current_fork_perc`
2) it is more important to be consistent with the two other "progress" "prec" metrics, and not with the "utilization" metric.
2021-03-10 15:25:16 +02:00
Oran Agra 62a197516b key miss stat increment was misplaced (#8630)
The implication is that OBJECT command would was not updating stat_keyspace_misses
2021-03-10 13:20:06 +02:00
sundb 95d6297db8 Add run all test support with define REDIS_TEST (#8570)
1. Add `redis-server test all` support to run all tests.
2. Add redis test to daily ci.
3. Add `--accurate` option to run slow tests for more iterations (so that
   by default we run less cycles (shorter time, and less prints).
4. Move dict benchmark to REDIS_TEST.
5. fix some leaks in tests
6. make quicklist tests run on a specific fill set of options rather than huge ranges
7. move some prints in quicklist test outside their loops to reduce prints
8. removing sds.h from dict.c since it is now used in both redis-server and
   redis-cli (uses hiredis sds)
2021-03-10 09:13:11 +02:00
uriyage 53774e69fa remove redundant check for evalCommand (#8565) 2021-03-09 11:57:14 -08:00
luhuachao 1ccfd6a1f7 Clean redis-benchmark multi-threaded output (#8615) 2021-03-09 14:50:04 +02:00
Harkrishn PatroandHarkrishn Patro 25f8d4fb41 Run daily workflows only on redis/redis repo. (#8625)
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
2021-03-09 12:35:41 +02:00
Yossi Gottlieb af2175326c Fix memory info on FreeBSD. (#8620)
The obtained process_rss was incorrect (the OS reports pages, not
bytes), resulting with many other fields getting corrupted.

This has been tested on FreeBSD but not other platforms.
2021-03-09 11:33:32 +02:00
Huang Zhw 9b4edfdf08 __quicklistCompress may compress more node than required (#8311)
When a quicklist has quicklist->compress * 2 nodes, then call
__quicklistCompress, all nodes will be decompressed and the middle
two nodes will be recompressed again. This violates the fact that
quicklist->compress * 2 nodes are uncompressed. It's harmless
because when visit a node, we always try to uncompress node first.
This only happened when a quicklist has quicklist->compress * 2 + 1
nodes, then delete a node. For other scenarios like insert node and
iterate this will not happen.
2021-03-08 21:43:09 +02:00
Yossi Gottlieb 817894c012 Fix test false positive due to a race condition. (#8616) 2021-03-08 21:22:08 +02:00
Yossi Gottlieb 7d81f39222 Fix flaky unit/maxmemory test on MacOS/BSD. (#8619)
It seems like non-Linux sockets may be less greedy, resulting with more
transient client output buffers.

Haven't proven this but empirically when stressing this test on
non-Linux tends to exhibit increased mem_clients_normal values.
2021-03-08 20:53:53 +02:00
guybe7 e58118cda6 Fix edge-case when a module client is unblocked (#8618)
Scenario:
1. A module client is blocked on keys with a timeout
2. Shortly before the timeout expires, the key is being populated and signaled
   as ready
3. Redis calls moduleTryServeClientBlockedOnKey (which replies to client) and
   then moduleUnblockClient
4. moduleUnblockClient doesn't really unblock the client, it writes to
   server.module_blocked_pipe and only marks the BC as unblocked.
5. beforeSleep kics in, by this time the client still exists and techincally
   timed-out. beforeSleep replies to the timeout client (double reply) and
   only then moduleHandleBlockedClients is called, reading from module_blocked_pipe
   and calling unblockClient

The solution is similar to what was done in moduleTryServeClientBlockedOnKey: we
should avoid re-processing an already-unblocked client
2021-03-08 19:00:19 +02:00
Pavlo Yatsukhnenko 20377a6f3d Wrong usage sdscatprintf in redis-cli. (#8604)
The result of `sdscatprintf` is doubled when using argument twice.
2021-03-08 12:57:27 +02:00
Huang Zhw f491eee2a1 Cleanup: dictEncObjHash remove redundant conditional statement (#8488) 2021-03-07 18:09:12 +02:00
Yossi Gottlieb e8e6ca6309 Fix FreeBSD <12.x builds. (#8603) 2021-03-07 14:14:23 +02:00
Huang Zhw 367ba4125f Remove some dead code (#8605) 2021-03-05 09:54:34 -08:00
yanchaozhong 3d9c6a7ce6 Fix typo in sentinel.c (#8600) 2021-03-05 09:32:11 -08:00
uriyage 55c74617d7 remove unused latency variable (#8597)
Remove unused latency variable from redis-cli
2021-03-05 09:29:52 -08:00
Yossi Gottlieb 3c7d6a1853 Improve redis-cli non-binary safe string handling. (#8566)
* The `redis-cli --scan` output should honor output mode (set explicitly or implicitly), and quote key names when not in raw mode.
  * Technically this is a breaking change, but it should be very minor since raw mode is by default on for non-tty output.
  * It should only affect  TTY output (human users) or non-tty output if `--no-raw` is specified.

* Added `--quoted-input` option to treat all arguments as potentially quoted strings.
* Added `--quoted-pattern` option to accept a potentially quoted pattern.

Unquoting is applied to potentially quoted input only if single or double quotes are used. 

Fixes #8561, #8563
2021-03-04 15:03:49 +02:00
sundb f07b7393a0 Fix memory overlap in quicklistRotate (#8599)
When the length of the quicklist is 1(only one zipmap), the rotate operation will cause
memory overlap when moving an entity from the tail of the zipmap to the head.
quicklistRotate is a dead code, so it has no impact on the existing code.
2021-03-04 13:12:00 +02:00
YaacovHazan ac91822952 Fix RedisModule_IsAOFClient Redis Module API (#8596)
Since the API declared (as #define) in redismodule.h and uses
the CLIENT_ID_AOF that declared in the server.h, when
a module will want to make use of this API, it will get a compilation
error (module doesn't include the server.h).

The API was broken by d6eb3af (failed attempt for a cleanup).
Revert to the original version of RedisModule_IsAOFClient
that uses UINT64_MAX instead of CLIENT_ID_AOF
2021-03-04 13:02:23 +02:00
Yossi Gottlieb ea1b48bd12 Improve SSL cleanup handling. (#8589)
This solves the problem of /dev/random and /dev/urandom open file
descriptors leaking to childs with some versions of OpenSSL.
2021-03-03 10:08:06 +02:00
yoav-steinberg fba391ae66 Fix client_recent_max_input/output_buffer in 'INFO CLIENTS' when all clients drop. (#8588)
When no connected clients variables stopped being updated instead of being zeroed over time.

The other changes are cleanup and optimization (avoiding an unnecessary division per client)
2021-03-03 09:36:27 +02:00
Wen Hui 257009afff generalize error check for SENTINEL MONITOR rumtime config (#8590) 2021-03-03 08:36:08 +02:00
Yossi GottliebandOran Agra 5d180d2834 Fix potential replication-4 test race condition. (#8583)
Co-authored-by: Oran Agra <oran@redislabs.com>
2021-03-02 18:12:11 +02:00
Yossi Gottlieb 0bad00d049 Fix errors when loading RDB with missing modules. (#8579)
Fixes #8574
2021-03-02 09:39:37 +02:00
Oran Agra 92bde124ca Merge Redis 6.2.1
CI / test-ubuntu-latest (push) Has been cancelled
CI / build-ubuntu-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
Redis 6.2.1
2021-03-02 08:14:39 +02:00
Oran Agra 7018ad69a2 Redis 6.2.1 2021-03-01 21:02:05 +02:00
Oran Agra 46df4db87b Merge unstable into 6.2 2021-03-01 20:54:02 +02:00
YaacovHazan c19530bc71 fix new networking tests to work when the test suite is used in tls mode (#8582)
the tests were unable to connect to the server since the attempted to use normal tcp
2021-03-01 20:53:02 +02:00
Oran Agra 349ef3f6a0 fix stream deep sanitization with deleted records (#8568)
When sanitizing the stream listpack, we need to count the deleted records too.
otherwise the last line that checks the next pointer fails.

Add test to cover that state in the stream tests.
2021-03-01 17:23:29 +02:00
YaacovHazan a031d268b1 Make port, tls-port and bind configurations modifiable (#8510)
Add ability to modify port, tls-port and bind configurations by CONFIG SET command.

To simplify the code and make it cleaner, a new structure
added, socketFds, which contains the file descriptors array and its counter,
and used for TCP, TLS and Cluster sockets file descriptors.
2021-03-01 16:04:44 +02:00
BonsaiandViktor Söderqvist 81a55d026f fix: call CLIENT INFO from redis module will crash the server (#8560)
Because when the RM_Call is invoked. It will create a faker client.
The point is client connection is NULL, so server will crash in connGetInfo

Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2021-03-01 08:18:14 +02:00
David Gilman 1bf02e1ca9 Fix compliation on arm64 Mac with jemalloc (#8458)
The arm_thread_state64_get_pc used later in the file is defined
in mach kernel headers. Apparently they get included if you use
the system malloc but not if you use jemalloc.
2021-03-01 08:15:26 +02:00
Qu Chen a66814fd7a Make dbid range check for SWAPDB command consistent with SELECT, MOVE, and COPY. (#8555)
DB ID used to be parsed as a long for SWAPDB command, now
make it into an int to be consistent with other commands that parses
the DB ID argument like SELECT, MOVE, COPY. See #8085

The implication is that the error message when the provided db index
is greater than 4M changes slightly.
2021-03-01 07:54:52 +02:00
Pavlo Yatsukhnenko 18ff8cd1fb Fix div by 0 in redis-cli cluster creation (#8553)
This could happen on an invalid use, when trying to create a cluster with
a single node and provide it's address 3 time to satisfy redis-cli requirements.
2021-02-28 15:28:09 +02:00
Bonsai 17c226b070 Module API for getting user name of a client (#8508)
Adding RM_GetClientUserNameById to get the ACL user name of a client connection.
2021-02-28 14:36:37 +02:00
Viktor Söderqvist 6122f1c450 Shared reusable client for RM_Call() (#8516)
A single client pointer is added in the server struct. This is
initialized by the first RM_Call() and reused for every subsequent
RM_Call() except if it's already in use, which means that it's not
used for (recursive) module calls to modules. For these, a new
"fake" client is created each time.

Other changes:
* Avoid allocating a dict iterator in pubsubUnsubscribeAllChannels
  when not needed
2021-02-28 14:11:18 +02:00
Madelyn Olson 4a474843fb Moved requirepass and querybuf length to generic configs (#8557)
Moved additional configs to generic infrastructure.
2021-02-25 21:00:27 -08:00
Wen Hui 7d43159a7d redis-cli prompt: fix db number and transaction state inconsistencies after reconnect (#8551)
After reconnect, the prompt was showing the db ID and multi state of the previous connection.
2021-02-25 20:08:24 +02:00
Yossi Gottlieb 3ea4c43add Cleanup usage of malloc_usable_size. (#8554)
* Add better control of malloc_usable_size() usage.
* Use malloc_usable_size on alpine libc daily job.
* Add no-malloc-usable-size daily jobs.
* Fix zmalloc(0) when HAVE_MALLOC_SIZE is undefined.

In order to align with the jemalloc behavior, this should never return
NULL or OOM panic.
2021-02-25 09:24:41 +02:00
Madelyn Olson c6f0ea2c81 Allow stopped redis processes to be killed in tests (#8552) 2021-02-24 14:26:16 -08:00
sundb 60d5ef4d82 Use addReplyErrorObject with shared.noscripterr (#8544) 2021-02-24 08:45:13 -08:00
guybe7 21316d5709 XTRIM: Parse args before lookupKey (#8550)
This aligns better with other commands, specifically XADD
2021-02-24 16:41:50 +02:00
guybe7 aba61d9ebd XTRIM should take at least 4 args (#8549)
Fix command arity.
Other than key name it must at least take the trimming strategy and it's limit
2021-02-24 16:41:00 +02:00
Huang ZwandYoav Steinberg 965356ae63 Fix quicklistDelRange dead code, delete range wrong (#8257)
This commit fixes a bug in what's currently dead code in redis.

In quicklistDelRange when delete entry from entry.offset to node tail,
extent only need gte node->count - entry.offset, not node->count

Co-authored-by: Yoav Steinberg <yoav@redislabs.com>
2021-02-24 13:18:54 +02:00
Yossi Gottlieb d1b5767a82 Cleanup clang warnings. (#8546) 2021-02-24 10:10:02 +02:00
Yossi Gottlieb ae7d5bf617 Use malloc_usable_size() on FreeBSD. (#8545) 2021-02-24 09:48:04 +02:00
Huang Zw e3667cb908 In luaRedisGenericCommand check channel return is ACL_DENIED_CHANNEL (#8535)
not ACL_DENIED_AUTH
2021-02-23 16:55:10 -08:00
guybe7 f745c0181a Fix race in CONFIG REWRITE sanity (#8536)
server may still be LOADING the RDB when receiving the ping
2021-02-23 20:28:03 +02:00
Yossi Gottlieb dd885780d6 Fix compile errors with no HAVE_MALLOC_SIZE. (#8533)
Also adds a new daily CI test, relying on the fact that we don't use malloc_size() on alpine libmusl.

Fixes #8531
2021-02-23 17:08:49 +02:00
Yossi Gottlieb 95ea74549c Fix failed tests on Linux Alpine and add a CI job. (#8532)
* Remove linux/version.h dependency.

This introduces unnecessary dependencies, and generally not a good idea
as the platform we build on may be different than the platform we run
on.

To determine if sync_file_range exists we can simply rely on header file
hints.

* Fix setproctitle() on libmusl.

The previous ifdef checks were a bit too strict for no apparent
reason.

* Fix tests failure on Linux with no backtrace.

* Add alpine daily CI job.
2021-02-23 12:57:45 +02:00
208 changed files with 9849 additions and 2685 deletions
+6 -1
View File
@@ -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.
+6 -3
View File
@@ -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
+93 -7
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: make
run: make
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
run: |
sudo apt-get install tcl8.6
@@ -28,6 +28,8 @@ jobs:
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
- name: unittest
run: ./src/redis-server test all
test-ubuntu-libc-malloc:
runs-on: ubuntu-latest
@@ -48,6 +50,25 @@ jobs:
- name: cluster tests
run: ./runtest-cluster
test-ubuntu-no-malloc-usable-size:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make MALLOC=libc CFLAGS=-DNO_MALLOC_USABLE_SIZE
- name: test
run: |
sudo apt-get install tcl8.6
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-ubuntu-32bit:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
@@ -57,7 +78,7 @@ jobs:
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
make 32bit
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
run: |
sudo apt-get install tcl8.6
@@ -70,6 +91,8 @@ jobs:
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
- name: unittest
run: ./src/redis-server test all
test-ubuntu-tls:
runs-on: ubuntu-latest
@@ -123,12 +146,32 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: make
run: make valgrind
run: make valgrind REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
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
run: |
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/redis-server test all
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
test-valgrind-no-malloc-usable-size:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make valgrind CFLAGS="-DNO_MALLOC_USABLE_SIZE"
- name: test
run: |
sudo apt-get update
sudo apt-get install tcl8.6 valgrind -y
./runtest --valgrind --verbose --clients 1 --tags -large-memory --dump-logs
- name: module api test
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
@@ -204,20 +247,63 @@ jobs:
run: ./runtest-cluster
test-freebsd:
runs-on: macos-latest
runs-on: macos-12
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: test
uses: vmactions/freebsd-vm@v0.1.2
uses: vmactions/freebsd-vm@v0.3.0
with:
usesh: true
sync: rsync
copyback: false
prepare: pkg install -y bash gmake lang/tcl86
run: >
gmake &&
./runtest --accurate --verbose --no-latency --dump-logs &&
./runtest --accurate --verbose --no-latency --tags -large-memory --dump-logs &&
MAKE=gmake ./runtest-moduleapi --verbose &&
./runtest-sentinel &&
./runtest-cluster
test-alpine-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: alpine:latest
steps:
- uses: actions/checkout@v2
- name: make
run: |
apk add build-base
make REDIS_CFLAGS='-Werror'
- name: test
run: |
apk add tcl procps
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-alpine-libc-malloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: alpine:latest
steps:
- uses: actions/checkout@v2
- name: make
run: |
apk add build-base
make REDIS_CFLAGS='-Werror' USE_JEMALLOC=no CFLAGS=-DUSE_MALLOC_USABLE_SIZE
- name: test
run: |
apk add tcl procps
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
+479
View File
@@ -11,6 +11,485 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release.
--------------------------------------------------------------------------------
Redis Community Edition 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
================================================================================
+19 -19
View File
@@ -15,10 +15,10 @@ Another good example is to think of Redis as a more complex version of memcached
If you want to know more, this is a list of selected starting points:
* Introduction to Redis data types. http://redis.io/topics/data-types-intro
* Introduction to Redis data types. https://redis.io/topics/data-types-intro
* Try Redis directly inside your browser. http://try.redis.io
* The full list of Redis commands. http://redis.io/commands
* There is much more inside the official Redis documentation. http://redis.io/documentation
* The full list of Redis commands. https://redis.io/commands
* There is much more inside the official Redis documentation. https://redis.io/documentation
Building Redis
--------------
@@ -49,7 +49,7 @@ To append a suffix to Redis program names, use:
% make PROG_SUFFIX="-alt"
You can run a 32 bit Redis binary using:
You can build a 32 bit Redis binary using:
% make 32bit
@@ -184,7 +184,7 @@ then in another terminal try the following:
(integer) 2
redis>
You can find the list of all the available commands at http://redis.io/commands.
You can find the list of all the available commands at https://redis.io/commands.
Installing Redis
-----------------
@@ -294,19 +294,19 @@ the structure definition.
Another important Redis data structure is the one defining a client.
In the past it was called `redisClient`, now just `client`. The structure
has many fields, here we'll just show the main ones:
struct client {
int fd;
sds querybuf;
int argc;
robj **argv;
redisDb *db;
int flags;
list *reply;
char buf[PROTO_REPLY_CHUNK_BYTES];
... many other fields ...
}
```c
struct client {
int fd;
sds querybuf;
int argc;
robj **argv;
redisDb *db;
int flags;
list *reply;
char buf[PROTO_REPLY_CHUNK_BYTES];
// ... many other fields ...
}
```
The client structure defines a *connected client*:
* The `fd` field is the client socket file descriptor.
@@ -453,7 +453,7 @@ Other C files
* `scripting.c` implements Lua scripting. It is completely self-contained and isolated from the rest of the Redis implementation and is simple enough to understand if you are familiar with the Lua API.
* `cluster.c` implements the Redis Cluster. Probably a good read only after being very familiar with the rest of the Redis code base. If you want to read `cluster.c` make sure to read the [Redis Cluster specification][3].
[3]: http://redis.io/topics/cluster-spec
[3]: https://redis.io/topics/cluster-spec
Anatomy of a Redis command
---
+7
View File
@@ -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.
+6 -1
View File
@@ -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
+4
View File
@@ -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);
}
+5
View File
@@ -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);
}
+14
View File
@@ -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 */
+22
View File
@@ -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;
}
-1
View File
@@ -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;
+2 -1
View File
@@ -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 */
+1
View File
@@ -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);
+3
View File
@@ -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);
/* }====================================================================== */
+1
View File
@@ -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; }
+6 -3
View File
@@ -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");
+17 -14
View File
@@ -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);
+2
View File
@@ -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? */
+28 -81
View File
@@ -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:
*/
+19 -27
View File
@@ -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;
+53 -13
View File
@@ -150,6 +150,11 @@ tcp-keepalive 300
#
# tls-cert-file redis.crt
# tls-key-file redis.key
#
# If the key file is encrypted using a passphrase, it can be included here
# as well.
#
# tls-key-file-pass secret
# Normally Redis uses the same certificate for both server functions (accepting
# connections) and client functions (replicating from a master, establishing
@@ -162,8 +167,15 @@ tcp-keepalive 300
#
# tls-client-cert-file client.crt
# tls-client-key-file client.key
#
# If the key file is encrypted using a passphrase, it can be included here
# as well.
#
# 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
@@ -657,6 +669,18 @@ repl-disable-tcp-nodelay no
# By default the priority is 100.
replica-priority 100
# -----------------------------------------------------------------------------
# By default, Redis Sentinel includes all replicas in its reports. A replica
# can be excluded from Redis Sentinel's announcements. An unannounced replica
# will be ignored by the 'sentinel replicas <master>' command and won't be
# exposed to Redis Sentinel's clients.
#
# This option does not change the behavior of replica-priority. Even with
# replica-announced set to 'no', the replica can be promoted to master. To
# prevent this behavior, set replica-priority to 0.
#
# replica-announced yes
# It is possible for a master to stop accepting writes if there are less than
# N replicas connected, having a lag less or equal than M seconds.
#
@@ -895,7 +919,7 @@ acllog-max-len 128
# order to provide better out-of-the-box Pub/Sub security. Therefore, it is
# recommended that you explicitly define Pub/Sub permissions for all users
# rather then rely on implicit default values. Once you've set explicit
# Pub/Sub for all exisitn users, you should uncomment the following line.
# Pub/Sub for all existing users, you should uncomment the following line.
#
# acl-pubsub-default resetchannels
@@ -1225,7 +1249,7 @@ disable-thp yes
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
# Please check https://redis.io/topics/persistence for more information.
appendonly no
@@ -1434,12 +1458,21 @@ lua-time-limit 5000
# master in your cluster.
#
# Default is 1 (replicas migrate only if their masters remain with at least
# one replica). To disable migration just set it to a very large value.
# one replica). To disable migration just set it to a very large value or
# set cluster-allow-replica-migration to 'no'.
# A value of 0 can be set but is useful only for debugging and dangerous
# in production.
#
# cluster-migration-barrier 1
# Turning off this option allows to use less automatic cluster configuration.
# It both disables migration to orphaned masters and migration from masters
# that became empty.
#
# Default is 'yes' (allow automatic migrations).
#
# cluster-allow-replica-migration yes
# By default Redis Cluster nodes stop accepting queries if they detect there
# is at least a hash slot uncovered (no available node is serving it).
# This way if the cluster is partially down (for example a range of hash slots
@@ -1480,7 +1513,7 @@ lua-time-limit 5000
# cluster-allow-reads-when-down no
# In order to setup your cluster make sure to read the documentation
# available at http://redis.io web site.
# available at https://redis.io web site.
########################## CLUSTER DOCKER/NAT support ########################
@@ -1490,16 +1523,21 @@ lua-time-limit 5000
#
# In order to make Redis Cluster working in such environments, a static
# configuration where each node knows its public address is needed. The
# following two options are used for this scope, and are:
# following four options are used for this scope, and are:
#
# * cluster-announce-ip
# * cluster-announce-port
# * cluster-announce-tls-port
# * cluster-announce-bus-port
#
# Each instructs the node about its address, client port, and cluster message
# bus port. The information is then published in the header of the bus packets
# so that other nodes will be able to correctly map the address of the node
# publishing the information.
# Each instructs the node about its address, client ports (for connections
# without and with TLS) and cluster message bus port. The information is then
# published in the header of the bus packets so that other nodes will be able to
# correctly map the address of the node publishing the information.
#
# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set
# to zero, then cluster-announce-port refers to the TLS port. Note also that
# cluster-announce-tls-port has no effect if cluster-tls is set to no.
#
# If the above options are not used, the normal Redis Cluster auto-detection
# will be used instead.
@@ -1512,7 +1550,8 @@ lua-time-limit 5000
# Example:
#
# cluster-announce-ip 10.1.1.5
# cluster-announce-port 6379
# cluster-announce-tls-port 6379
# cluster-announce-port 0
# cluster-announce-bus-port 6380
################################## SLOW LOG ###################################
@@ -1563,7 +1602,7 @@ latency-monitor-threshold 0
############################# EVENT NOTIFICATION ##############################
# Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/notifications
# This feature is documented at https://redis.io/topics/notifications
#
# For instance if keyspace events notification is enabled, and a client
# performs a DEL operation on key "foo" stored in the Database 0, two
@@ -1586,8 +1625,9 @@ latency-monitor-threshold 0
# x Expired events (events generated every time a key expires)
# e Evicted events (events generated when a key is evicted for maxmemory)
# t Stream commands
# d Module key type events
# m Key-miss events (Note: It is not included in the 'A' class)
# A Alias for g$lshzxet, so that the "AKE" string means all the events
# A Alias for g$lshzxetd, so that the "AKE" string means all the events
# (Except key-miss events which are excluded from 'A' due to their
# unique nature).
#
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 \
"${@}"
+40 -18
View File
@@ -21,7 +21,12 @@ NODEPS:=clean distclean
# Default settings
STD=-pedantic -DREDIS_STATIC=''
# Use -Wno-c11-extensions on clang, either where explicitly used or on
# platforms we can assume it's being used.
ifneq (,$(findstring clang,$(CC)))
STD+=-Wno-c11-extensions
else
ifneq (,$(findstring FreeBSD,$(uname_S)))
STD+=-Wno-c11-extensions
endif
@@ -29,8 +34,10 @@ endif
WARN=-Wall -W -Wno-missing-field-initializers
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)
@@ -88,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
@@ -120,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)
@@ -187,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
@@ -251,6 +260,17 @@ endif
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
endif
ifndef V
define MAKE_INSTALL
@printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$(1)$(ENDCOLOR) 1>&2
@$(INSTALL) $(1) $(2)
endef
else
define MAKE_INSTALL
$(INSTALL) $(1) $(2)
endef
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@@ -346,9 +366,6 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
dict-benchmark: dict.c zmalloc.c sds.c siphash.c mt19937-64.c
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
-include $(DEP)
@@ -359,13 +376,15 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
$(REDIS_CC) -MMD -o $@ -c $<
clean:
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep
rm -f $(DEP)
.PHONY: clean
distclean: clean
-(cd ../deps && $(MAKE) distclean)
-(cd modules && $(MAKE) clean)
-(cd ../tests/modules && $(MAKE) clean)
-(rm -f .make-*)
.PHONY: distclean
@@ -373,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)
@@ -412,9 +434,9 @@ src/help.h:
install: all
@mkdir -p $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
$(call MAKE_INSTALL,$(REDIS_SERVER_NAME),$(INSTALL_BIN))
$(call MAKE_INSTALL,$(REDIS_BENCHMARK_NAME),$(INSTALL_BIN))
$(call MAKE_INSTALL,$(REDIS_CLI_NAME),$(INSTALL_BIN))
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
+52 -6
View File
@@ -245,7 +245,7 @@ user *ACLCreateUser(const char *name, size_t namelen) {
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
user *u = zmalloc(sizeof(*u));
u->name = sdsnewlen(name,namelen);
u->flags = USER_FLAG_DISABLED | server.acl_pubusub_default;
u->flags = USER_FLAG_DISABLED | server.acl_pubsub_default;
u->allowed_subcommands = NULL;
u->passwords = listCreate();
u->patterns = listCreate();
@@ -652,6 +652,7 @@ sds ACLDescribeUser(user *u) {
if (u->flags & USER_FLAG_ALLCHANNELS) {
res = sdscatlen(res,"&* ",3);
} else {
res = sdscatlen(res,"resetchannels ",14);
listRewind(u->channels,&li);
while((ln = listNext(&li))) {
sds thispat = listNodeValue(ln);
@@ -1000,6 +1001,8 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
if (server.acl_pubsub_default & USER_FLAG_ALLCHANNELS)
serverAssert(ACLSetUser(u,"allchannels",-1) == C_OK);
serverAssert(ACLSetUser(u,"off",-1) == C_OK);
serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
@@ -1054,7 +1057,6 @@ void ACLInit(void) {
UsersToLoad = listCreate();
ACLLog = listCreate();
ACLInitDefaultUser();
server.requirepass = NULL; /* Only used for backward compatibility. */
}
/* Check the username and password pair and return C_OK if they are valid,
@@ -1181,9 +1183,9 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
/* If there is no associated user, the connection can run anything. */
if (u == NULL) return ACL_OK;
/* Check if the user can execute this command. */
if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
c->cmd->proc != authCommand)
/* Check if the user can execute this command or if the command
* doesn't need to be authenticated (hello, auth). */
if (!(u->flags & USER_FLAG_ALLCOMMANDS) && !(c->cmd->flags & CMD_NO_AUTH))
{
/* If the bit is not set we have to check further, in case the
* command is allowed just with that specific subcommand. */
@@ -1282,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);
@@ -1361,6 +1370,22 @@ int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr)
}
/* Check whether the command is ready to be exceuted by ACLCheckCommandPerm.
* If check passes, then check whether pub/sub channels of the command is
* ready to be executed by ACLCheckPubsubPerm */
int ACLCheckAllPerm(client *c, int *idxptr) {
int acl_retval = ACLCheckCommandPerm(c,idxptr);
if (acl_retval != ACL_OK)
return acl_retval;
if (c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,idxptr);
else if (c->cmd->proc == subscribeCommand)
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,0,idxptr);
else if (c->cmd->proc == psubscribeCommand)
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,1,idxptr);
return acl_retval;
}
/* =============================================================================
* ACL loading / saving functions
* ==========================================================================*/
@@ -1874,6 +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) {
/* 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))) {
@@ -2223,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. */
@@ -2242,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) {
@@ -2251,3 +2285,15 @@ void authCommand(client *c) {
}
}
/* Set the password for the "default" ACL user. This implements supports for
* requirepass config, so passing in NULL will set the user to be nopass. */
void ACLUpdateDefaultUserPassword(sds password) {
ACLSetUser(DefaultUser,"resetpass",-1);
if (password) {
sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password));
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
} else {
ACLSetUser(DefaultUser,"nopass",-1);
}
}
+8 -9
View File
@@ -239,7 +239,7 @@ int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
return AE_ERR; /* NO event with the specified ID found */
}
/* How many milliseconds until the first timer should fire.
/* How many microseconds until the first timer should fire.
* If there are no timers, -1 is returned.
*
* Note that's O(N) since time events are unsorted.
@@ -248,7 +248,7 @@ int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
* Much better but still insertion or deletion of timers is O(N).
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
*/
static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
aeTimeEvent *te = eventLoop->timeEventHead;
if (te == NULL) return -1;
@@ -260,8 +260,7 @@ static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
}
monotime now = getMonotonicUs();
return (now >= earliest->when)
? 0 : (long)((earliest->when - now) / 1000);
return (now >= earliest->when) ? 0 : earliest->when - now;
}
/* Process time events */
@@ -361,14 +360,14 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
struct timeval tv, *tvp;
long msUntilTimer = -1;
int64_t usUntilTimer = -1;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
msUntilTimer = msUntilEarliestTimer(eventLoop);
usUntilTimer = usUntilEarliestTimer(eventLoop);
if (msUntilTimer >= 0) {
tv.tv_sec = msUntilTimer / 1000;
tv.tv_usec = (msUntilTimer % 1000) * 1000;
if (usUntilTimer >= 0) {
tv.tv_sec = usUntilTimer / 1000000;
tv.tv_usec = usUntilTimer % 1000000;
tvp = &tv;
} else {
/* If we have to check for events but need to return
+1 -1
View File
@@ -111,7 +111,7 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
int retval, numevents = 0;
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1);
if (retval > 0) {
int j;
+55 -7
View File
@@ -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;
+6 -78
View File
@@ -186,27 +186,6 @@ int anetDisableTcpNoDelay(char *err, int fd)
return anetSetTcpNoDelay(err, fd, 0);
}
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
/* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified
* number of milliseconds, or disable it if the 'ms' argument is zero. */
int anetSendTimeout(char *err, int fd, long long ms) {
@@ -378,23 +357,11 @@ end:
}
}
int anetTcpConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
}
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK);
}
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
const char *source_addr)
{
@@ -430,53 +397,16 @@ int anetUnixGenericConnect(char *err, const char *path, int flags)
return s;
}
int anetUnixConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
}
int anetUnixNonBlockConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, char *buf, int count)
{
ssize_t nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is written before to return
* (unless error is encountered) */
int anetWrite(int fd, char *buf, int count)
{
ssize_t nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
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);
@@ -520,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) {
@@ -557,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;
}
-7
View File
@@ -53,26 +53,19 @@
#define FD_TO_PEER_NAME 0
#define FD_TO_SOCK_NAME 1
int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
int anetUnixAccept(char *err, int serversock);
int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd);
int anetBlock(char *err, int fd);
int anetCloexec(int fd);
int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms);
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);
+20 -6
View File
@@ -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);
}
@@ -218,7 +220,7 @@ void killAppendOnlyChild(void) {
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
(long) server.child_pid);
if (kill(server.child_pid,SIGUSR1) != -1) {
while(wait3(&statloc,0,NULL) != server.child_pid);
while(waitpid(-1, &statloc, 0) != server.child_pid);
}
/* Reset the buffer accumulating changes while the child saves. */
aofRewriteBufferReset();
@@ -234,9 +236,12 @@ void killAppendOnlyChild(void) {
void stopAppendOnly(void) {
serverAssert(server.aof_state != AOF_OFF);
flushAppendOnlyFile(1);
redis_fsync(server.aof_fd);
server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime;
if (redis_fsync(server.aof_fd) == -1) {
serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno));
} else {
server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime;
}
close(server.aof_fd);
server.aof_fd = -1;
@@ -290,6 +295,15 @@ int startAppendOnly(void) {
server.aof_last_fsync = server.unixtime;
server.aof_fd = newfd;
/* If AOF fsync error in bio job, we just ignore it and log the event. */
int aof_bio_fsync_status;
atomicGet(server.aof_bio_fsync_status, aof_bio_fsync_status);
if (aof_bio_fsync_status == C_ERR) {
serverLog(LL_WARNING,
"AOF reopen, just ignore the AOF fsync error in bio job");
atomicSet(server.aof_bio_fsync_status,C_OK);
}
/* If AOF was in error state, we just ignore it and log the event. */
if (server.aof_last_write_status == C_ERR) {
serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
@@ -1590,7 +1604,7 @@ int rewriteAppendOnlyFile(char *filename) {
if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK)
goto werr;
/* We read the ACK from the server using a 10 seconds timeout. Normally
/* We read the ACK from the server using a 5 seconds timeout. Normally
* it should reply ASAP, but just in case we lose its reply, we are sure
* the child will eventually get terminated. */
if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
+2 -2
View File
@@ -31,13 +31,13 @@ const char *ascii_logo =
" _._ \n"
" _.-``__ ''-._ \n"
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
" ( ' , .-` | `, ) Running in %s mode\n"
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
" | `-._ `._ / _.-' | PID: %ld\n"
" `-._ `-._ `-./ _.-' _.-' \n"
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
" | `-._`-._ _.-'_.-' | http://redis.io \n"
" | `-._`-._ _.-'_.-' | https://redis.io \n"
" `-._ `-._`-.__.-'_.-' _.-' \n"
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
" | `-._`-._ _.-'_.-' | \n"
+17 -1
View File
@@ -220,7 +220,23 @@ void *bioProcessBackgroundJobs(void *arg) {
if (type == BIO_CLOSE_FILE) {
close(job->fd);
} else if (type == BIO_AOF_FSYNC) {
redis_fsync(job->fd);
/* The fd may be closed by main thread and reused for another
* socket, pipe, or file. We just ignore these errno because
* aof fsync did not really fail. */
if (redis_fsync(job->fd) == -1 &&
errno != EBADF && errno != EINVAL)
{
int last_status;
atomicGet(server.aof_bio_fsync_status,last_status);
atomicSet(server.aof_bio_fsync_status,C_ERR);
atomicSet(server.aof_bio_fsync_errno,errno);
if (last_status == C_OK) {
serverLog(LL_WARNING,
"Fail to fsync the AOF file: %s",strerror(errno));
}
} else {
atomicSet(server.aof_bio_fsync_status,C_OK);
}
} else if (type == BIO_LAZY_FREE) {
job->free_fn(job->free_args);
} else {
+19 -18
View File
@@ -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];
}
+42 -8
View File
@@ -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++;
@@ -106,12 +111,11 @@ void blockClient(client *c, int btype) {
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
c->lastcmd->microseconds += total_cmd_duration;
/* Log the command into the Slow log if needed. */
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) {
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
/* Log the reply duration event. */
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
}
slowlogPushCurrentCommand(c, c->lastcmd, total_cmd_duration);
/* Log the reply duration event. */
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
}
/* This function is called in the beforeSleep() function of the event loop
@@ -188,6 +192,16 @@ void unblockClient(client *c) {
} else {
serverPanic("Unknown btype in unblockClient().");
}
/* Reset the client for a new query since, for blocking commands
* we do not do it immediately after the command returns (when the
* client got blocked) in order to be still able to access the argument
* vector from module callbacks and updateStatsOnUnblock. */
if (c->btype != BLOCKED_PAUSE) {
freeClientOriginalArgv(c);
resetClient(c);
}
/* Clear the flags, and put the client in the unblocked list so that
* we'll process new commands in its query buffer ASAP. */
server.blocked_clients--;
@@ -279,8 +293,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
* freed by the next unblockClient()
* call. */
if (dstkey) incrRefCount(dstkey);
unblockClient(receiver);
client *old_client = server.current_client;
server.current_client = receiver;
monotime replyTimer;
elapsedStart(&replyTimer);
if (serveClientBlockedOnList(receiver,
@@ -292,6 +307,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
listTypePush(o,value,wherefrom);
}
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
unblockClient(receiver);
afterCommand(receiver);
server.current_client = old_client;
if (dstkey) decrRefCount(dstkey);
decrRefCount(value);
@@ -335,11 +353,15 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX;
unblockClient(receiver);
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. */
@@ -429,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
@@ -457,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;
}
}
}
@@ -471,6 +497,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
void serveClientsBlockedOnKeyByModule(readyList *rl) {
dictEntry *de;
/* Optimization: If no clients are in type BLOCKED_MODULE,
* we can skip this loop. */
if (!server.blocked_clients_by_type[BLOCKED_MODULE]) return;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
de = dictFind(rl->db->blocking_keys,rl->key);
@@ -497,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;
}
}
}
@@ -553,7 +587,7 @@ void handleClientsBlockedOnKeys(void) {
* way we can lookup an object multiple times (BLMOVE does
* that) without the risk of it being freed in the second
* lookup, invalidating the first one.
* See https://github.com/antirez/redis/pull/6554. */
* See https://github.com/redis/redis/pull/6554. */
server.fixed_time_expire++;
updateCachedTime(0);
+40 -13
View File
@@ -33,6 +33,7 @@
typedef struct {
size_t keys;
size_t cow;
monotime cow_updated;
double progress;
childInfoType information_type; /* Type of information */
} child_info_data;
@@ -69,29 +70,53 @@ void closeChildInfoPipe(void) {
void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, char *pname) {
if (server.child_info_pipe[1] == -1) return;
child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */
static monotime cow_updated = 0;
static uint64_t cow_update_cost = 0;
static size_t cow = 0;
child_info_data data = {0}; /* zero everything, including padding to satisfy valgrind */
/* When called to report current info, we need to throttle down CoW updates as they
* can be very expensive. To do that, we measure the time it takes to get a reading
* and schedule the next reading to happen not before time*CHILD_COW_COST_FACTOR
* passes. */
monotime now = getMonotonicUs();
if (info_type != CHILD_INFO_TYPE_CURRENT_INFO ||
!cow_updated ||
now - cow_updated > cow_update_cost * CHILD_COW_DUTY_CYCLE)
{
cow = zmalloc_get_private_dirty(-1);
cow_updated = getMonotonicUs();
cow_update_cost = cow_updated - now;
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, cow / (1024 * 1024));
}
}
data.information_type = info_type;
data.keys = keys;
data.cow = zmalloc_get_private_dirty(-1);
data.cow = cow;
data.cow_updated = cow_updated;
data.progress = progress;
if (data.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));
}
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);
}
}
/* Update Child info. */
void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, double progress) {
void updateChildInfo(childInfoType information_type, size_t cow, monotime cow_updated, size_t keys, double progress) {
if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) {
server.stat_current_cow_bytes = cow;
server.stat_current_cow_updated = cow_updated;
server.stat_current_save_keys_processed = keys;
if (progress != -1) server.stat_module_progress = progress;
} else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) {
@@ -107,7 +132,7 @@ void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, do
* if complete data read into the buffer,
* data is stored into *buffer, and returns 1.
* otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */
int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, double* progress) {
int readChildInfo(childInfoType *information_type, size_t *cow, monotime *cow_updated, size_t *keys, double* progress) {
/* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */
static child_info_data buffer;
ssize_t wlen = sizeof(buffer);
@@ -124,6 +149,7 @@ int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, do
if (server.child_info_nread == wlen) {
*information_type = buffer.information_type;
*cow = buffer.cow;
*cow_updated = buffer.cow_updated;
*keys = buffer.keys;
*progress = buffer.progress;
return 1;
@@ -137,12 +163,13 @@ void receiveChildInfo(void) {
if (server.child_info_pipe[0] == -1) return;
size_t cow;
monotime cow_updated;
size_t keys;
double progress;
childInfoType information_type;
/* Drain the pipe and update child info so that we get the final message. */
while (readChildInfo(&information_type, &cow, &keys, &progress)) {
updateChildInfo(information_type, cow, keys, progress);
while (readChildInfo(&information_type, &cow, &cow_updated, &keys, &progress)) {
updateChildInfo(information_type, cow, cow_updated, keys, progress);
}
}
+168 -124
View File
@@ -55,7 +55,7 @@ void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
void clusterUpdateState(void);
int clusterNodeGetSlotBit(clusterNode *n, int slot);
sds clusterGenNodesDescription(int filter);
sds clusterGenNodesDescription(int filter, int use_pport);
clusterNode *clusterLookupNode(const char *name);
int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
int clusterAddSlot(clusterNode *n, int slot);
@@ -190,6 +190,9 @@ int clusterLoadConfig(char *filename) {
* base port. */
n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR;
/* The plaintext port for client in a TLS cluster (n->pport) is not
* stored in nodes.conf. It is received later over the bus protocol. */
/* Parse flags */
p = s = argv[2];
while(p) {
@@ -336,7 +339,7 @@ int clusterSaveConfig(int do_fsync) {
/* Get the nodes description and concatenate our "vars" directive to
* save currentEpoch and lastVoteEpoch. */
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE);
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE, 0);
ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n",
(unsigned long long) server.cluster->currentEpoch,
(unsigned long long) server.cluster->lastVoteEpoch);
@@ -355,7 +358,7 @@ int clusterSaveConfig(int do_fsync) {
if (write(fd,ci,sdslen(ci)) != (ssize_t)sdslen(ci)) goto err;
if (do_fsync) {
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG;
fsync(fd);
if (fsync(fd) == -1) goto err;
}
/* Truncate the file if needed to remove the final \n padding that
@@ -437,6 +440,26 @@ int clusterLockConfig(char *filename) {
return C_OK;
}
/* Derives our ports to be announced in the cluster bus. */
void deriveAnnouncedPorts(int *announced_port, int *announced_pport,
int *announced_cport) {
int port = server.tls_cluster ? server.tls_port : server.port;
/* Default announced ports. */
*announced_port = port;
*announced_pport = server.tls_cluster ? server.port : 0;
*announced_cport = port + CLUSTER_PORT_INCR;
/* Config overriding announced ports. */
if (server.tls_cluster && server.cluster_announce_tls_port) {
*announced_port = server.cluster_announce_tls_port;
*announced_pport = server.cluster_announce_port;
} else if (server.cluster_announce_port) {
*announced_port = server.cluster_announce_port;
}
if (server.cluster_announce_bus_port) {
*announced_cport = server.cluster_announce_bus_port;
}
}
/* Some flags (currently just the NOFAILOVER flag) may need to be updated
* in the "myself" node based on the current configuration of the node,
* that may change at runtime via CONFIG SET. This function changes the
@@ -499,7 +522,7 @@ void clusterInit(void) {
if (saveconf) clusterSaveConfigOrDie(1);
/* We need a listening TCP port for our cluster messaging needs. */
server.cfd_count = 0;
server.cfd.count = 0;
/* Port sanity check II
* The other handshake port check is triggered too late to stop
@@ -512,19 +535,13 @@ void clusterInit(void) {
"Your Redis port number must be 55535 or less.");
exit(1);
}
if (listenToPort(port+CLUSTER_PORT_INCR,
server.cfd,&server.cfd_count) == C_ERR)
{
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);
} else {
int j;
for (j = 0; j < server.cfd_count; j++) {
if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,
clusterAcceptHandler, NULL) == AE_ERR)
serverPanic("Unrecoverable error creating Redis Cluster "
"file event.");
}
}
if (createSocketAcceptHandler(&server.cfd, clusterAcceptHandler) != C_OK) {
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
}
/* The slots -> keys map is a radix tree. Initialize it here. */
@@ -532,16 +549,12 @@ void clusterInit(void) {
memset(server.cluster->slots_keys_count,0,
sizeof(server.cluster->slots_keys_count));
/* Set myself->port / cport to my listening ports, we'll just need to
/* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
myself->port = port;
myself->cport = port+CLUSTER_PORT_INCR;
if (server.cluster_announce_port)
myself->port = server.cluster_announce_port;
if (server.cluster_announce_bus_port)
myself->cport = server.cluster_announce_bus_port;
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
server.cluster->mf_end = 0;
server.cluster->mf_slave = NULL;
resetManualFailover();
clusterUpdateMyselfFlags();
}
@@ -697,6 +710,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);
@@ -790,6 +804,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
memset(node->ip,0,sizeof(node->ip));
node->port = 0;
node->cport = 0;
node->pport = 0;
node->fail_reports = listCreate();
node->voted_time = 0;
node->orphaned_time = 0;
@@ -1496,6 +1511,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
if (node->link) freeClusterLink(node->link);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port);
node->pport = ntohs(g->pport);
node->cport = ntohs(g->cport);
node->flags &= ~CLUSTER_NODE_NOADDR;
}
@@ -1517,6 +1533,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
node = createClusterNode(g->nodename, flags);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port);
node->pport = ntohs(g->pport);
node->cport = ntohs(g->cport);
clusterAddNode(node);
}
@@ -1556,6 +1573,7 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
{
char ip[NET_IP_STR_LEN] = {0};
int port = ntohs(hdr->port);
int pport = ntohs(hdr->pport);
int cport = ntohs(hdr->cport);
/* We don't proceed if the link is the same as the sender link, as this
@@ -1567,12 +1585,13 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
if (link == node->link) return 0;
nodeIp2String(ip,link,hdr->myip);
if (node->port == port && node->cport == cport &&
if (node->port == port && node->cport == cport && node->pport == pport &&
strcmp(ip,node->ip) == 0) return 0;
/* IP / port is different, update it. */
memcpy(node->ip,ip,sizeof(ip));
node->port = port;
node->pport = pport;
node->cport = cport;
if (node->link) freeClusterLink(node->link);
node->flags &= ~CLUSTER_NODE_NOADDR;
@@ -1618,7 +1637,7 @@ void clusterSetNodeAsMaster(clusterNode *n) {
* case we receive the info via an UPDATE packet. */
void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
int j;
clusterNode *curmaster, *newmaster = NULL;
clusterNode *curmaster = NULL, *newmaster = NULL;
/* The dirty slots list is a list of slots for which we lose the ownership
* while having still keys inside. This usually happens after a failover
* or after a manual cluster reconfiguration operated by the admin.
@@ -1629,6 +1648,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
uint16_t dirty_slots[CLUSTER_SLOTS];
int dirty_slots_count = 0;
/* We should detect if sender is new master of our shard.
* We will know it if all our slots were migrated to sender, and sender
* has no slots except ours */
int sender_slots = 0;
int migrated_our_slots = 0;
/* Here we set curmaster to this node or the node this node
* replicates to if it's a slave. In the for loop we are
* interested to check if slots are taken away from curmaster. */
@@ -1641,6 +1666,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(slots,j)) {
sender_slots++;
/* The slot is already bound to the sender of this message. */
if (server.cluster->slots[j] == sender) continue;
@@ -1667,8 +1694,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
dirty_slots_count++;
}
if (server.cluster->slots[j] == curmaster)
if (server.cluster->slots[j] == curmaster) {
newmaster = sender;
migrated_our_slots++;
}
clusterDelSlot(j);
clusterAddSlot(sender,j);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
@@ -1691,7 +1720,9 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
* master.
* 2) We are a slave and our master is left without slots. We need
* to replicate to the new slots owner. */
if (newmaster && curmaster->numslots == 0) {
if (newmaster && curmaster->numslots == 0 &&
(server.cluster_allow_replica_migration ||
sender_slots == migrated_our_slots)) {
serverLog(LL_WARNING,
"Configuration change detected. Reconfiguring myself "
"as a replica of %.40s", sender->name);
@@ -1819,7 +1850,7 @@ int clusterProcessPacket(clusterLink *link) {
nodeIsSlave(myself) &&
myself->slaveof == sender &&
hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED &&
server.cluster->mf_master_offset == 0)
server.cluster->mf_master_offset == -1)
{
server.cluster->mf_master_offset = sender->repl_offset;
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
@@ -1870,6 +1901,7 @@ int clusterProcessPacket(clusterLink *link) {
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
nodeIp2String(node->ip,link,hdr->myip);
node->port = ntohs(hdr->port);
node->pport = ntohs(hdr->pport);
node->cport = ntohs(hdr->cport);
clusterAddNode(node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@@ -1932,6 +1964,7 @@ int clusterProcessPacket(clusterLink *link) {
link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0';
link->node->port = 0;
link->node->pport = 0;
link->node->cport = 0;
freeClusterLink(link);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@@ -2431,19 +2464,16 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
hdr->myip[NET_IP_STR_LEN-1] = '\0';
}
/* Handle cluster-announce-port as well. */
int port = server.tls_cluster ? server.tls_port : server.port;
int announced_port = server.cluster_announce_port ?
server.cluster_announce_port : port;
int announced_cport = server.cluster_announce_bus_port ?
server.cluster_announce_bus_port :
(port + CLUSTER_PORT_INCR);
/* Handle cluster-announce-[tls-|bus-]port. */
int announced_port, announced_pport, announced_cport;
deriveAnnouncedPorts(&announced_port, &announced_pport, &announced_cport);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
if (myself->slaveof != NULL)
memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN);
hdr->port = htons(announced_port);
hdr->pport = htons(announced_pport);
hdr->cport = htons(announced_cport);
hdr->flags = htons(myself->flags);
hdr->state = server.cluster->state;
@@ -2500,6 +2530,7 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
gossip->port = htons(n->port);
gossip->cport = htons(n->cport);
gossip->flags = htons(n->flags);
gossip->pport = htons(n->pport);
gossip->notused1 = 0;
}
@@ -3098,7 +3129,7 @@ void clusterFailoverReplaceYourMaster(void) {
/* This function is called if we are a slave node and our master serving
* a non-zero amount of hash slots is in FAIL state.
*
* The gaol of this function is:
* The goal of this function is:
* 1) To check if we are able to perform a failover, is our data updated?
* 2) Try to get elected by masters.
* 3) Perform the failover informing all the other nodes.
@@ -3143,7 +3174,7 @@ void clusterHandleSlaveFailover(void) {
return;
}
/* Set data_age to the number of seconds we are disconnected from
/* Set data_age to the number of milliseconds we are disconnected from
* the master. */
if (server.repl_state == REPL_STATE_CONNECTED) {
data_age = (mstime_t)(server.unixtime - server.master->lastinteraction)
@@ -3421,13 +3452,15 @@ 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;
server.cluster->mf_slave = NULL;
server.cluster->mf_master_offset = 0;
server.cluster->mf_master_offset = -1;
}
/* If a manual failover timed out, abort it. */
@@ -3448,7 +3481,7 @@ void clusterHandleManualFailover(void) {
* next steps are performed by clusterHandleSlaveFailover(). */
if (server.cluster->mf_can_start) return;
if (server.cluster->mf_master_offset == 0) return; /* Wait for offset... */
if (server.cluster->mf_master_offset == -1) return; /* Wait for offset... */
if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) {
/* Our replication offset matches the master replication offset
@@ -3638,7 +3671,6 @@ void clusterCron(void) {
now - node->link->ctime >
server.cluster_node_timeout && /* was not already reconnected */
node->ping_sent && /* we already sent a ping */
node->pong_received < node->ping_sent && /* still waiting pong */
/* and we are waiting for the pong more than timeout/2 */
ping_delay > server.cluster_node_timeout/2 &&
/* and in such interval we are not seeing any traffic at all. */
@@ -3721,7 +3753,8 @@ void clusterCron(void) {
* the orphaned masters. Note that it does not make sense to try
* a migration if there is no master with at least *two* working
* slaves. */
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves &&
server.cluster_allow_replica_migration)
clusterHandleSlaveMigration(max_slaves);
}
@@ -3831,7 +3864,7 @@ int clusterNodeSetSlotBit(clusterNode *n, int slot) {
* However new masters with slots assigned are considered valid
* migration targets if the rest of the cluster is not a slave-less.
*
* See https://github.com/antirez/redis/issues/3043 for more info. */
* See https://github.com/redis/redis/issues/3043 for more info. */
if (n->numslots == 1 && clusterMastersHaveSlaves())
n->flags |= CLUSTER_NODE_MIGRATE_TO;
}
@@ -4139,15 +4172,16 @@ sds representClusterNodeFlags(sds ci, uint16_t flags) {
* See clusterGenNodesDescription() top comment for more information.
*
* The function returns the string representation as an SDS string. */
sds clusterGenNodeDescription(clusterNode *node) {
sds clusterGenNodeDescription(clusterNode *node, int use_pport) {
int j, start;
sds ci;
int port = use_pport && node->pport ? node->pport : node->port;
/* Node coordinates */
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
ci = sdscatfmt(ci," %s:%i@%i ",
node->ip,
node->port,
port,
node->cport);
/* Flags */
@@ -4258,10 +4292,13 @@ void clusterGenNodesSlotsInfo(int filter) {
* include all the known nodes in the representation, including nodes in
* the HANDSHAKE state.
*
* Setting use_pport to 1 in a TLS cluster makes the result contain the
* plaintext client port rather then the TLS client port of each node.
*
* The representation obtained using this function is used for the output
* of the CLUSTER NODES function, and as format for the cluster
* configuration file (nodes.conf) for a given node. */
sds clusterGenNodesDescription(int filter) {
sds clusterGenNodesDescription(int filter, int use_pport) {
sds ci = sdsempty(), ni;
dictIterator *di;
dictEntry *de;
@@ -4274,7 +4311,7 @@ sds clusterGenNodesDescription(int filter) {
clusterNode *node = dictGetVal(de);
if (node->flags & filter) continue;
ni = clusterGenNodeDescription(node);
ni = clusterGenNodeDescription(node, use_pport);
ci = sdscatsds(ci,ni);
sdsfree(ni);
ci = sdscatlen(ci,"\n",1);
@@ -4321,7 +4358,37 @@ int getSlotOrReply(client *c, robj *o) {
return (int) slot;
}
void clusterReplyMultiBulkSlots(client *c) {
void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) {
int i, nested_elements = 3; /* slots (2) + master addr (1) */
void *nested_replylen = addReplyDeferredLen(c);
addReplyLongLong(c, start_slot);
addReplyLongLong(c, end_slot);
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
/* Report non-TLS ports to non-TLS client in TLS cluster if available. */
int use_pport = (server.tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
addReplyLongLong(c, use_pport && node->pport ? node->pport : node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation. */
if (nodeFailed(node->slaves[i])) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
/* Report slave's non-TLS port to non-TLS client in TLS cluster */
addReplyLongLong(c, (use_pport && node->slaves[i]->pport ?
node->slaves[i]->pport :
node->slaves[i]->port));
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredArrayLen(c, nested_replylen, nested_elements);
}
void clusterReplyMultiBulkSlots(client * c) {
/* Format: 1) 1) start slot
* 2) end slot
* 3) 1) master IP
@@ -4332,69 +4399,29 @@ void clusterReplyMultiBulkSlots(client *c) {
* 3) node ID
* ... continued until done
*/
int num_masters = 0;
clusterNode *n = NULL;
int num_masters = 0, start = -1;
void *slot_replylen = addReplyDeferredLen(c);
dictEntry *de;
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
int j = 0, start = -1;
int i, nested_elements = 0;
/* Skip slaves (that are iterated when producing the output of their
* master) and masters not serving any slot. */
if (!nodeIsMaster(node) || node->numslots == 0) continue;
for(i = 0; i < node->numslaves; i++) {
if (nodeFailed(node->slaves[i])) continue;
nested_elements++;
for (int i = 0; i <= CLUSTER_SLOTS; i++) {
/* Find start node and slot id. */
if (n == NULL) {
if (i == CLUSTER_SLOTS) break;
n = server.cluster->slots[i];
start = i;
continue;
}
for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit, i;
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
if (start == -1) start = j;
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
if (bit && j == CLUSTER_SLOTS-1) j++;
/* If slot exists in output map, add to it's list.
* else, create a new output map for this slot */
if (start == j-1) {
addReplyLongLong(c, start); /* only one slot; low==high */
addReplyLongLong(c, start);
} else {
addReplyLongLong(c, start); /* low */
addReplyLongLong(c, j-1); /* high */
}
start = -1;
/* First node reply position is always the master */
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
}
num_masters++;
}
/* Add cluster slots info when occur different node with start
* or end of slot. */
if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) {
addNodeReplyForClusterSlot(c, n, start, i-1);
num_masters++;
if (i == CLUSTER_SLOTS) break;
n = server.cluster->slots[i];
start = i;
}
}
dictReleaseIterator(di);
setDeferredArrayLen(c, slot_replylen, num_masters);
}
@@ -4483,7 +4510,11 @@ NULL
}
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */
sds nodes = clusterGenNodesDescription(0);
/* Report plaintext ports, only if cluster is TLS but client is known to
* be non-TLS). */
int use_pport = (server.tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
sds nodes = clusterGenNodesDescription(0, use_pport);
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
@@ -4859,9 +4890,12 @@ NULL
return;
}
/* Use plaintext port if cluster is TLS but client is non-TLS. */
int use_pport = (server.tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) {
sds ni = clusterGenNodeDescription(n->slaves[j]);
sds ni = clusterGenNodeDescription(n->slaves[j], use_pport);
addReplyBulkCString(c,ni);
sdsfree(ni);
}
@@ -5148,7 +5182,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;
@@ -5333,13 +5367,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,
@@ -5437,9 +5474,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,
@@ -5630,10 +5668,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;
}
@@ -5832,18 +5869,14 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
* cluster is down. */
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
return NULL;
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
&& !(cmd->proc == evalShaCommand))
{
/* The cluster is configured to allow read only commands
* but this command is neither readonly, nor EVAL or
* EVALSHA. */
} else if (cmd->flags & CMD_WRITE) {
/* The cluster is configured to allow read only commands */
if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE;
return NULL;
} else {
/* Fall through and allow the command to be executed:
* this happens when server.cluster_allow_reads_when_down is
* true and the command is a readonly command or EVAL / EVALSHA. */
* true and the command is not a write command */
}
}
@@ -5884,7 +5917,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
if (c->flags & CLIENT_READONLY &&
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
!is_write_command &&
nodeIsSlave(myself) &&
myself->slaveof == n)
{
@@ -5921,10 +5954,15 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
} else if (error_code == CLUSTER_REDIR_MOVED ||
error_code == CLUSTER_REDIR_ASK)
{
/* Redirect to IP:port. Include plaintext port if cluster is TLS but
* client is non-TLS. */
int use_pport = (server.tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
int port = use_pport && n->pport ? n->pport : n->port;
addReplyErrorSds(c,sdscatprintf(sdsempty(),
"-%s %d %s:%d",
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
hashslot,n->ip,n->port));
hashslot, n->ip, port));
} else {
serverPanic("getNodeByQuery() unknown error.");
}
@@ -5945,7 +5983,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;
@@ -5959,6 +5998,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) {
+8 -4
View File
@@ -135,7 +135,9 @@ typedef struct clusterNode {
mstime_t orphaned_time; /* Starting time of orphaned master condition */
long long repl_offset; /* Last known repl offset for this node. */
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
int port; /* Latest known clients port of this node */
int port; /* Latest known clients port (TLS or plain). */
int pport; /* Latest known clients plaintext port. Only used
if the main clients port is for TLS. */
int cport; /* Latest known cluster port of this node. */
clusterLink *link; /* TCP/IP link with this node */
list *fail_reports; /* List of nodes signaling this as failing */
@@ -168,7 +170,7 @@ typedef struct clusterState {
clusterNode *mf_slave; /* Slave performing the manual failover. */
/* Manual failover state of slave. */
long long mf_master_offset; /* Master offset the slave needs to start MF
or zero if still not received. */
or -1 if still not received. */
int mf_can_start; /* If non-zero signal that the manual failover
can start requesting masters vote. */
/* The following fields are used by masters to take state on elections. */
@@ -194,7 +196,8 @@ typedef struct {
uint16_t port; /* base port last time it was seen */
uint16_t cport; /* cluster port last time it was seen */
uint16_t flags; /* node->flags copy */
uint32_t notused1;
uint16_t pport; /* plaintext-port, when base port is TLS */
uint16_t notused1;
} clusterMsgDataGossip;
typedef struct {
@@ -267,7 +270,8 @@ typedef struct {
unsigned char myslots[CLUSTER_SLOTS/8];
char slaveof[CLUSTER_NAMELEN];
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
char notused1[34]; /* 34 bytes reserved for future usage. */
char notused1[32]; /* 32 bytes reserved for future usage. */
uint16_t pport; /* Sender TCP plaintext port, if base port is TLS */
uint16_t cport; /* Sender TCP cluster bus port */
uint16_t flags; /* Sender node flags */
unsigned char state; /* Cluster state from the POV of the sender */
+134 -127
View File
@@ -229,8 +229,6 @@ typedef union typeData {
typedef struct typeInterface {
/* Called on server start, to init the server with default value */
void (*init)(typeData data);
/* Called on server start, should return 1 on success, 0 on error and should set err */
int (*load)(typeData data, sds *argc, int argv, const char **err);
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
* and can set a verbose err string, update is true when called from CONFIG SET */
int (*set)(typeData data, sds value, int update, const char **err);
@@ -243,11 +241,16 @@ typedef struct typeInterface {
typedef struct standardConfig {
const char *name; /* The user visible name of this config */
const char *alias; /* An alias that can also be used for this config */
const int modifiable; /* Can this value be updated by CONFIG SET? */
const unsigned int flags; /* Flags for this specific config */
typeInterface interface; /* The function pointers that define the type interface */
typeData data; /* The type specific data exposed used by the interface */
} standardConfig;
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
* config, which is mutable. */
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
standardConfig configs[];
/*-----------------------------------------------------------------------------
@@ -385,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;
@@ -504,8 +508,6 @@ void loadServerConfigFromString(char *config) {
}
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1], 0, NULL);
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
server.client_max_querybuf_len = memtoll(argv[1],NULL);
} else if ((!strcasecmp(argv[0],"slaveof") ||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum;
@@ -521,26 +523,6 @@ void loadServerConfigFromString(char *config) {
err = "Invalid master port"; goto loaderr;
}
server.repl_state = REPL_STATE_CONNECT;
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (sdslen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
goto loaderr;
}
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sdsfree(server.requirepass);
server.requirepass = NULL;
if (sdslen(argv[1])) {
sds aclop = sdscatlen(sdsnew(">"), argv[1], sdslen(argv[1]));
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
server.requirepass = sdsdup(argv[1]);
} else {
ACLSetUser(DefaultUser,"nopass",-1);
}
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
@@ -607,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);
@@ -640,6 +621,10 @@ void loadServerConfigFromString(char *config) {
goto loaderr;
}
/* To ensure backward compatibility and work while hz is out of range */
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
sdsfreesplitres(lines,totlines);
return;
@@ -736,9 +721,13 @@ void configSetCommand(client *c) {
/* Iterate the configs that are standard */
for (standardConfig *config = configs; config->name != NULL; config++) {
if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
if (!(config->flags & IMMUTABLE_CONFIG) &&
(!strcasecmp(c->argv[2]->ptr,config->name) ||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
{
if (config->flags & SENSITIVE_CONFIG) {
redactClientCommandArgument(c,3);
}
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
goto badfmt;
}
@@ -750,23 +739,22 @@ void configSetCommand(client *c) {
if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */
config_set_special_field("requirepass") {
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sdsfree(server.requirepass);
server.requirepass = NULL;
if (sdslen(o->ptr)) {
sds aclop = sdscatlen(sdsnew(">"), o->ptr, sdslen(o->ptr));
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
server.requirepass = sdsdup(o->ptr);
} else {
ACLSetUser(DefaultUser,"nopass",-1);
config_set_special_field("bind") {
int vlen;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
if (vlen < 1 || vlen > CONFIG_BINDADDR_MAX) {
addReplyError(c, "Too many bind addresses specified.");
sdsfreesplitres(v, vlen);
return;
}
if (changeBindAddr(v, vlen) == C_ERR) {
addReplyError(c, "Failed to bind to specified addresses.");
sdsfreesplitres(v, vlen);
return;
}
sdsfreesplitres(v, vlen);
} config_set_special_field("save") {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
@@ -876,10 +864,6 @@ void configSetCommand(client *c) {
enableWatchdog(ll);
else
disableWatchdog();
/* Memory fields.
* config_set_memory_field(name,var) */
} config_set_memory_field(
"client-query-buffer-limit",server.client_max_querybuf_len) {
/* Everything else is an error... */
} config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
@@ -959,7 +943,6 @@ void configGetCommand(client *c) {
config_get_string_field("logfile",server.logfile);
/* Numerical values */
config_get_numerical_field("client-query-buffer-limit",server.client_max_querybuf_len);
config_get_numerical_field("watchdog-period",server.watchdog_period);
/* Everything we can't handle with macros follows. */
@@ -1046,16 +1029,6 @@ void configGetCommand(client *c) {
sdsfree(aux);
matches++;
}
if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass");
sds password = server.requirepass;
if (password) {
addReplyBulkCBuffer(c,password,sdslen(password));
} else {
addReplyBulkCString(c,"");
}
matches++;
}
if (stringmatch(pattern,"oom-score-adj-values",0)) {
sds buf = sdsempty();
@@ -1408,14 +1381,19 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
return;
}
/* Note that if there are no save parameters at all, all the current
* config line with "save" will be detected as orphaned and deleted,
* resulting into no RDB persistence as expected. */
for (j = 0; j < server.saveparamslen; j++) {
line = sdscatprintf(sdsempty(),"save %ld %d",
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
rewriteConfigRewriteLine(state,"save",line,1);
/* Rewrite save parameters, or an empty 'save ""' line to avoid the
* defaults from being used.
*/
if (!server.saveparamslen) {
rewriteConfigRewriteLine(state,"save",sdsnew("save \"\""),1);
} else {
for (j = 0; j < server.saveparamslen; j++) {
line = sdscatprintf(sdsempty(),"save %ld %d",
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
rewriteConfigRewriteLine(state,"save",line,1);
}
}
/* Mark "save" as processed in case server.saveparamslen is zero. */
rewriteConfigMarkAsProcessed(state,"save");
}
@@ -1564,26 +1542,6 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the requirepass option. */
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
int force = 1;
sds line;
sds password = server.requirepass;
/* If there is no password set, we don't want the requirepass option
* to be present in the configuration at all. */
if (password == NULL) {
rewriteConfigMarkAsProcessed(state,option);
return;
}
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatsds(line, password);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Glue together the configuration lines in the current configuration
* rewrite state into a single string, stripping multiple empty lines. */
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
@@ -1740,8 +1698,6 @@ int rewriteConfig(char *path, int force_all) {
rewriteConfigUserOption(state);
rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state,"replicaof");
rewriteConfigRequirepassOption(state,"requirepass");
rewriteConfigBytesOption(state,"client-query-buffer-limit",server.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigClientoutputbufferlimitOption(state);
@@ -1771,13 +1727,10 @@ int rewriteConfig(char *path, int force_all) {
#define LOADBUF_SIZE 256
static char loadbuf[LOADBUF_SIZE];
#define MODIFIABLE_CONFIG 1
#define IMMUTABLE_CONFIG 0
#define embedCommonConfig(config_name, config_alias, is_modifiable) \
#define embedCommonConfig(config_name, config_alias, config_flags) \
.name = (config_name), \
.alias = (config_alias), \
.modifiable = (is_modifiable),
.flags = (config_flags),
#define embedConfigInterface(initfn, setfn, getfn, rewritefn) .interface = { \
.init = (initfn), \
@@ -1828,8 +1781,8 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
rewriteConfigYesNoOption(state, name,*(data.yesno.config), data.yesno.default_value);
}
#define createBoolConfig(name, alias, modifiable, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, modifiable) \
#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite) \
.data.yesno = { \
.config = &(config_addr), \
@@ -1901,8 +1854,8 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
#define ALLOW_EMPTY_STRING 0
#define EMPTY_STRING_IS_NULL 1
#define createStringConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, modifiable) \
#define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite) \
.data.string = { \
.config = &(config_addr), \
@@ -1913,8 +1866,8 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
} \
}
#define createSDSConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, modifiable) \
#define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite) \
.data.sds = { \
.config = &(config_addr), \
@@ -1969,8 +1922,8 @@ static void enumConfigRewrite(typeData data, const char *name, struct rewriteCon
rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value);
}
#define createEnumConfig(name, alias, modifiable, enum, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, modifiable) \
#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, update) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite) \
.data.enumd = { \
.config = &(config_addr), \
@@ -2123,8 +2076,8 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
#define INTEGER_CONFIG 0
#define MEMORY_CONFIG 1
#define embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) { \
embedCommonConfig(name, alias, modifiable) \
#define embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite) \
.data.numeric = { \
.lower_bound = (lower), \
@@ -2134,71 +2087,71 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
.update_fn = (update), \
.is_memory = (memory),
#define createIntConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createIntConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_INT, \
.config.i = &(config_addr) \
} \
}
#define createUIntConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_UINT, \
.config.ui = &(config_addr) \
} \
}
#define createLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_LONG, \
.config.l = &(config_addr) \
} \
}
#define createULongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createULongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_ULONG, \
.config.ul = &(config_addr) \
} \
}
#define createLongLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_LONG_LONG, \
.config.ll = &(config_addr) \
} \
}
#define createULongLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_ULONG_LONG, \
.config.ull = &(config_addr) \
} \
}
#define createSizeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_SIZE_T, \
.config.st = &(config_addr) \
} \
}
#define createSSizeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_SSIZE_T, \
.config.sst = &(config_addr) \
} \
}
#define createTimeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_TIME_T, \
.config.tt = &(config_addr) \
} \
}
#define createOffTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
#define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
.numeric_type = NUMERIC_TYPE_OFF_T, \
.config.ot = &(config_addr) \
} \
@@ -2267,6 +2220,20 @@ static int updateHZ(long long val, long long prev, const char **err) {
return 1;
}
static int updatePort(long long val, long long prev, const char **err) {
/* Do nothing if port is unchanged */
if (val == prev) {
return 1;
}
if (changeListenPort(val, &server.ipfd, acceptTcpHandler) == C_ERR) {
*err = "Unable to listen on this port. Check server logs.";
return 0;
}
return 1;
}
static int updateJemallocBgThread(int val, int prev, const char **err) {
UNUSED(prev);
UNUSED(err);
@@ -2368,6 +2335,18 @@ static int updateOOMScoreAdj(int val, int prev, const char **err) {
return 1;
}
int updateRequirePass(sds val, sds prev, const char **err) {
UNUSED(prev);
UNUSED(err);
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLUpdateDefaultUserPassword(val);
return 1;
}
#ifdef USE_OPENSSL
static int updateTlsCfg(char *val, char *prev, const char **err) {
UNUSED(val);
@@ -2393,6 +2372,27 @@ static int updateTlsCfgInt(long long val, long long prev, const char **err) {
UNUSED(prev);
return updateTlsCfg(NULL, NULL, err);
}
static int updateTLSPort(long long val, long long prev, const char **err) {
/* Do nothing if port is unchanged */
if (val == prev) {
return 1;
}
/* Configure TLS if tls is enabled */
if (prev == 0 && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
*err = "Unable to update TLS configuration. Check server logs.";
return 0;
}
if (changeListenPort(val, &server.tlsfd, acceptTLSHandler) == C_ERR) {
*err = "Unable to listen on this port. Check server logs.";
return 0;
}
return 1;
}
#endif /* USE_OPENSSL */
standardConfig configs[] = {
@@ -2438,13 +2438,15 @@ standardConfig configs[] = {
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, server.memcheck_enabled, 1, NULL, NULL),
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, server.use_exit_on_panic, 0, NULL, NULL),
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, server.disable_thp, 1, NULL, NULL),
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL),
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL),
/* String Configs */
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL),
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, NULL),
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
@@ -2457,7 +2459,8 @@ standardConfig configs[] = {
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
/* SDS Configs */
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.requirepass, NULL, NULL, updateRequirePass),
/* Enum Configs */
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
@@ -2467,12 +2470,12 @@ standardConfig configs[] = {
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubusub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubsub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, server.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL),
/* Integer configs */
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
createIntConfig("port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */
createIntConfig("io-threads", NULL, IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
@@ -2494,6 +2497,7 @@ standardConfig configs[] = {
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */
createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.port */
createIntConfig("cluster-announce-tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.tls_port */
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
@@ -2534,13 +2538,14 @@ standardConfig configs[] = {
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
createSizeTConfig("client-query-buffer-limit", NULL, MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */
/* Other configs */
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
#ifdef USE_OPENSSL
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTlsCfgInt), /* TCP port. */
createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool),
@@ -2550,8 +2555,10 @@ standardConfig configs[] = {
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, updateTlsCfg),
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
+10 -18
View File
@@ -35,12 +35,16 @@
#endif
#ifdef __linux__
#include <linux/version.h>
#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
@@ -79,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
@@ -114,19 +118,7 @@
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
* the plain fsync() call. */
#ifdef __linux__
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
#define HAVE_SYNC_FILE_RANGE 1
#endif
#else
#if (LINUX_VERSION_CODE >= 0x020611)
#define HAVE_SYNC_FILE_RANGE 1
#endif
#endif
#endif
#ifdef HAVE_SYNC_FILE_RANGE
#if (defined(__linux__) && defined(SYNC_FILE_RANGE_WAIT_BEFORE))
#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
#else
#define rdb_fsync_range(fd,off,size) fsync(fd)
@@ -143,7 +135,7 @@
#define ESOCKTNOSUPPORT 0
#endif
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
#if (defined __linux || defined __APPLE__)
#define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT
void spt_init(int argc, char *argv[]);
@@ -260,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)
+1 -1
View File
@@ -427,7 +427,7 @@ int connGetState(connection *conn) {
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
*/
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
snprintf(buf, buf_len-1, "fd=%i", conn == NULL ? -1 : conn->fd);
return buf;
}
+2 -1
View File
@@ -127,9 +127,10 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
#include <stdio.h>
#define UNUSED(x) (void)(x)
int crc64Test(int argc, char *argv[]) {
int crc64Test(int argc, char *argv[], int accurate) {
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
crc64_init();
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
(uint64_t)_crc64(0, "123456789", 9));
+1 -1
View File
@@ -7,7 +7,7 @@ void crc64_init(void);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
#ifdef REDIS_TEST
int crc64Test(int argc, char *argv[]);
int crc64Test(int argc, char *argv[], int accurate);
#endif
#endif
+42 -19
View File
@@ -30,6 +30,7 @@
#include "server.h"
#include "cluster.h"
#include "atomicvar.h"
#include "latency.h"
#include <signal.h>
#include <ctype.h>
@@ -139,9 +140,9 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
keymiss:
if (!(flags & LOOKUP_NONOTIFY)) {
server.stat_keyspace_misses++;
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
}
server.stat_keyspace_misses++;
return NULL;
}
@@ -164,16 +165,24 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
void SentReplyOnKeyMiss(client *c, robj *reply){
serverAssert(sdsEncodedObject(reply));
sds rep = reply->ptr;
if (sdslen(rep) > 1 && rep[0] == '-'){
addReplyErrorObject(c, reply);
} else {
addReply(c,reply);
}
}
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReply(c,reply);
if (!o) SentReplyOnKeyMiss(c, reply);
return o;
}
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyWrite(c->db, key);
if (!o) addReply(c,reply);
if (!o) SentReplyOnKeyMiss(c, reply);
return o;
}
@@ -568,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) {
@@ -1315,7 +1324,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
*
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
* C_OK is returned. */
int dbSwapDatabases(long id1, long id2) {
int dbSwapDatabases(int id1, int id2) {
if (id1 < 0 || id1 >= server.dbnum ||
id2 < 0 || id2 >= server.dbnum) return C_ERR;
if (id1 == id2) return C_OK;
@@ -1356,7 +1365,7 @@ int dbSwapDatabases(long id1, long id2) {
/* SWAPDB db1 db2 */
void swapdbCommand(client *c) {
long id1, id2;
int id1, id2;
/* Not allowed in cluster mode: we have just DB 0 there. */
if (server.cluster_enabled) {
@@ -1365,11 +1374,11 @@ void swapdbCommand(client *c) {
}
/* Get the two DBs indexes. */
if (getLongFromObjectOrReply(c, c->argv[1], &id1,
if (getIntFromObjectOrReply(c, c->argv[1], &id1,
"invalid first DB index") != C_OK)
return;
if (getLongFromObjectOrReply(c, c->argv[2], &id2,
if (getIntFromObjectOrReply(c, c->argv[2], &id2,
"invalid second DB index") != C_OK)
return;
@@ -1429,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.
@@ -1445,7 +1470,12 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
incrRefCount(argv[0]);
incrRefCount(argv[1]);
/* If the master decided to expire a key we must propagate it to replicas no matter what..
* Even if module executed a command without asking for propagation. */
int prev_replication_allowed = server.replication_allowed;
server.replication_allowed = 1;
propagate(server.delCommand,db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
server.replication_allowed = prev_replication_allowed;
decrRefCount(argv[0]);
decrRefCount(argv[1]);
@@ -1467,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
@@ -1528,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;
}
/* -----------------------------------------------------------------------------
@@ -1599,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 {
+90 -35
View File
@@ -54,6 +54,10 @@ typedef ucontext_t sigcontext_t;
#endif
#endif
#if defined(__APPLE__) && defined(__arm64__)
#include <mach/mach.h>
#endif
/* Globals */
static int bug_report_start = 0; /* True if bug report header was already logged. */
static pthread_mutex_t bug_report_start_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -245,7 +249,7 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
}
streamIteratorStop(&si);
} else if (o->type == OBJ_MODULE) {
RedisModuleDigest md;
RedisModuleDigest md = {{0},{0}};
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
moduleInitDigestContext(md);
@@ -437,7 +441,7 @@ void debugCommand(client *c) {
" conflicting keys will generate an exception and kill the server."
" * NOSAVE: the database will be loaded from an existing RDB file.",
" Examples:",
" * DEBUG RELOAD: verify that the server is able to persist, flsuh and reload",
" * DEBUG RELOAD: verify that the server is able to persist, flush and reload",
" the database.",
" * DEBUG RELOAD NOSAVE: replace the current database with the contents of an",
" existing RDB file.",
@@ -463,13 +467,15 @@ 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);
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
*((char*)-1) = 'x';
} else if (!strcasecmp(c->argv[1]->ptr,"panic")) {
serverPanic("DEBUG PANIC called at Unix time %ld", time(NULL));
serverPanic("DEBUG PANIC called at Unix time %lld", (long long)time(NULL));
} else if (!strcasecmp(c->argv[1]->ptr,"restart") ||
!strcasecmp(c->argv[1]->ptr,"crash-and-recover"))
{
@@ -717,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")) {
@@ -733,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. */
@@ -881,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;
@@ -1013,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
@@ -1091,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,
@@ -1794,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);
@@ -1805,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_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
@@ -1853,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. */
@@ -1912,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
+3 -1
View File
@@ -347,7 +347,9 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
if ((newsds = activeDefragSds(sdsele))) {
/* When defragging an sds value, we need to update the dict key */
uint64_t hash = dictGetHash(d, newsds);
replaceSatelliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, sdsele, hash);
if (deref)
(*deref)->key = newsds;
ln->value = newsds;
defragged++;
}
+68 -44
View File
@@ -45,21 +45,17 @@
#include "dict.h"
#include "zmalloc.h"
#ifndef DICT_BENCHMARK_MAIN
#include "redisassert.h"
#else
#include <assert.h>
#endif
/* 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 ---------------------------- */
@@ -136,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;
@@ -158,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;
@@ -210,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;
@@ -534,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;
@@ -995,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);
}
@@ -1057,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) {
@@ -1105,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. */
@@ -1175,20 +1183,18 @@ void dictGetStats(char *buf, size_t bufsize, dict *d) {
/* ------------------------------- Benchmark ---------------------------------*/
#ifdef DICT_BENCHMARK_MAIN
#include "sds.h"
#ifdef REDIS_TEST
uint64_t hashCallback(const void *key) {
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
}
int compareCallback(void *privdata, const void *key1, const void *key2) {
int l1,l2;
DICT_NOTUSED(privdata);
l1 = sdslen((sds)key1);
l2 = sdslen((sds)key2);
l1 = strlen((char*)key1);
l2 = strlen((char*)key2);
if (l1 != l2) return 0;
return memcmp(key1, key2, l1) == 0;
}
@@ -1196,7 +1202,19 @@ int compareCallback(void *privdata, const void *key1, const void *key2) {
void freeCallback(void *privdata, void *val) {
DICT_NOTUSED(privdata);
sdsfree(val);
zfree(val);
}
char *stringFromLongLong(long long value) {
char buf[32];
int len;
char *s;
len = sprintf(buf,"%lld",value);
s = zmalloc(len+1);
memcpy(s, buf, len);
s[len] = '\0';
return s;
}
dictType BenchmarkDictType = {
@@ -1215,22 +1233,26 @@ dictType BenchmarkDictType = {
printf(msg ": %ld items in %lld ms\n", count, elapsed); \
} while(0)
/* dict-benchmark [count] */
int main(int argc, char **argv) {
/* ./redis-server test dict [<count> | --accurate] */
int dictTest(int argc, char **argv, int accurate) {
long j;
long long start, elapsed;
dict *dict = dictCreate(&BenchmarkDictType,NULL);
long count = 0;
if (argc == 2) {
count = strtol(argv[1],NULL,10);
if (argc == 4) {
if (accurate) {
count = 5000000;
} else {
count = strtol(argv[3],NULL,10);
}
} else {
count = 5000000;
count = 5000;
}
start_benchmark();
for (j = 0; j < count; j++) {
int retval = dictAdd(dict,sdsfromlonglong(j),(void*)j);
int retval = dictAdd(dict,stringFromLongLong(j),(void*)j);
assert(retval == DICT_OK);
}
end_benchmark("Inserting");
@@ -1243,28 +1265,28 @@ int main(int argc, char **argv) {
start_benchmark();
for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j);
char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict,key);
assert(de != NULL);
sdsfree(key);
zfree(key);
}
end_benchmark("Linear access of existing elements");
start_benchmark();
for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j);
char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict,key);
assert(de != NULL);
sdsfree(key);
zfree(key);
}
end_benchmark("Linear access of existing elements (2nd round)");
start_benchmark();
for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(rand() % count);
char *key = stringFromLongLong(rand() % count);
dictEntry *de = dictFind(dict,key);
assert(de != NULL);
sdsfree(key);
zfree(key);
}
end_benchmark("Random access of existing elements");
@@ -1277,17 +1299,17 @@ int main(int argc, char **argv) {
start_benchmark();
for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(rand() % count);
char *key = stringFromLongLong(rand() % count);
key[0] = 'X';
dictEntry *de = dictFind(dict,key);
assert(de == NULL);
sdsfree(key);
zfree(key);
}
end_benchmark("Accessing missing");
start_benchmark();
for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j);
char *key = stringFromLongLong(j);
int retval = dictDelete(dict,key);
assert(retval == DICT_OK);
key[0] += 17; /* Change first number to letter. */
@@ -1295,5 +1317,7 @@ int main(int argc, char **argv) {
assert(retval == DICT_OK);
}
end_benchmark("Removing and adding");
dictRelease(dict);
return 0;
}
#endif
+12 -3
View File
@@ -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);
@@ -201,4 +206,8 @@ extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue;
#ifdef REDIS_TEST
int dictTest(int argc, char *argv[], int accurate);
#endif
#endif /* __DICT_H */
+2 -1
View File
@@ -105,11 +105,12 @@ uint64_t intrev64(uint64_t v) {
#include <stdio.h>
#define UNUSED(x) (void)(x)
int endianconvTest(int argc, char *argv[]) {
int endianconvTest(int argc, char *argv[], int accurate) {
char buf[32];
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
sprintf(buf,"ciaoroma");
memrev16(buf);
+1 -1
View File
@@ -72,7 +72,7 @@ uint64_t intrev64(uint64_t v);
#endif
#ifdef REDIS_TEST
int endianconvTest(int argc, char *argv[]);
int endianconvTest(int argc, char *argv[], int accurate);
#endif
#endif
+9 -3
View File
@@ -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
View File
@@ -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);
-1
View File
@@ -60,7 +60,6 @@
#ifdef __linux__
/* features.h uses the defines above to set feature specific defines. */
#include <linux/version.h>
#include <features.h>
#endif
+37 -8
View File
@@ -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
View File
@@ -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) {
+1 -1
View File
@@ -184,7 +184,7 @@ struct commandHelp {
8,
"6.2.0" },
{ "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [SKIPME yes/no]",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [LADDR ip:port] [SKIPME yes/no]",
"Kill the connection of a client",
8,
"2.4.0" },
+16 -4
View File
@@ -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,10 +282,10 @@ 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 stracture.
/* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we make sure there are no duplicate or out of order records. */
int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) {
@@ -392,7 +393,7 @@ static void checkConsistency(intset *is) {
}
#define UNUSED(x) (void)(x)
int intsetTest(int argc, char **argv) {
int intsetTest(int argc, char **argv, int accurate) {
uint8_t success;
int i;
intset *is;
@@ -400,6 +401,7 @@ int intsetTest(int argc, char **argv) {
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
printf("Value encodings: "); {
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
@@ -424,6 +426,7 @@ int intsetTest(int argc, char **argv) {
is = intsetAdd(is,4,&success); assert(success);
is = intsetAdd(is,4,&success); assert(!success);
ok();
zfree(is);
}
printf("Large number of random adds: "); {
@@ -436,6 +439,7 @@ int intsetTest(int argc, char **argv) {
assert(intrev32ifbe(is->length) == inserts);
checkConsistency(is);
ok();
zfree(is);
}
printf("Upgrade from int16 to int32: "); {
@@ -447,6 +451,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,32));
assert(intsetFind(is,65535));
checkConsistency(is);
zfree(is);
is = intsetNew();
is = intsetAdd(is,32,NULL);
@@ -457,6 +462,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-65535));
checkConsistency(is);
ok();
zfree(is);
}
printf("Upgrade from int16 to int64: "); {
@@ -468,6 +474,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,32));
assert(intsetFind(is,4294967295));
checkConsistency(is);
zfree(is);
is = intsetNew();
is = intsetAdd(is,32,NULL);
@@ -478,6 +485,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-4294967295));
checkConsistency(is);
ok();
zfree(is);
}
printf("Upgrade from int32 to int64: "); {
@@ -489,6 +497,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,65535));
assert(intsetFind(is,4294967295));
checkConsistency(is);
zfree(is);
is = intsetNew();
is = intsetAdd(is,65535,NULL);
@@ -499,6 +508,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-4294967295));
checkConsistency(is);
ok();
zfree(is);
}
printf("Stress lookups: "); {
@@ -512,6 +522,7 @@ int intsetTest(int argc, char **argv) {
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
printf("%ld lookups, %ld element set, %lldusec\n",
num,size,usec()-start);
zfree(is);
}
printf("Stress add+delete: "); {
@@ -528,6 +539,7 @@ int intsetTest(int argc, char **argv) {
}
checkConsistency(is);
ok();
zfree(is);
}
return 0;
+1 -1
View File
@@ -49,7 +49,7 @@ size_t intsetBlobLen(intset *is);
int intsetValidateIntegrity(const unsigned char *is, size_t size, int deep);
#ifdef REDIS_TEST
int intsetTest(int argc, char *argv[]);
int intsetTest(int argc, char *argv[], int accurate);
#endif
#endif // __INTSET_H
+2 -2
View File
@@ -256,7 +256,7 @@ sds createLatencyReport(void) {
if (dictSize(server.latency_events) == 0 &&
server.latency_monitor_threshold == 0)
{
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n");
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at https://redis.io/topics/latency-monitor.\n");
return report;
}
@@ -426,7 +426,7 @@ sds createLatencyReport(void) {
}
if (advise_slowlog_inspect) {
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check https://redis.io/commands/slowlog for more information.\n");
}
/* Intrinsic latency. */
+3 -4
View File
@@ -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);
+32 -9
View File
@@ -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,7 +922,12 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
#undef OUT_OF_RANGE
}
/* Validate the integrity of the data stracture.
/* 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. */
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){
@@ -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)
+1
View File
@@ -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
View File
@@ -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;
}
+3 -3
View File
@@ -84,9 +84,9 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
* rows. */
float angle = 0;
if (y > 1) {
float r1 = (float)rand() / RAND_MAX / squares_per_col * y;
float r2 = (float)rand() / RAND_MAX / squares_per_col * y;
float r3 = (float)rand() / RAND_MAX / squares_per_col * y;
float r1 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
float r2 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
float r3 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
if (rand() % 2) r1 = -r1;
if (rand() % 2) r2 = -r2;
if (rand() % 2) r3 = -r3;
+1 -1
View File
@@ -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);
}
+348 -116
View File
File diff suppressed because it is too large Load Diff
+1 -6
View File
@@ -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
+123 -11
View File
@@ -1,3 +1,4 @@
# coding: utf-8
# gendoc.rb -- Converts the top-comments inside module.c to modules API
# reference documentation in markdown format.
@@ -21,15 +22,48 @@ def markdown(s)
l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`')
# Add backquotes around macro and var names containing underscores.
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
# Link URLs preceded by space (i.e. when not already linked)
l = l.gsub(/ (https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
' [\1](\1)')
# Link URLs preceded by space or newline (not already linked)
l = l.gsub(/(^| )(https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
'\1[\2](\2)')
# Replace double-dash with unicode ndash
l = l.gsub(/ -- /, ' ')
end
# Link function names to their definition within the page
l = l.gsub(/`(RedisModule_[A-z0-9]+)[()]*`/) {|x|
$index[$1] ? "[#{x}](\##{$1})" : x
}
newlines << l
}
return newlines.join("\n")
end
# Linebreak a prototype longer than 80 characters on the commas, but only
# between balanced parentheses so that we don't linebreak args which are
# function pointers, and then aligning each arg under each other.
def linebreak_proto(proto, indent)
if proto.bytesize <= 80
return proto
end
parts = proto.split(/,\s*/);
if parts.length == 1
return proto;
end
align_pos = proto.index("(") + 1;
align = " " * align_pos
result = parts.shift;
bracket_balance = 0;
parts.each{|part|
if bracket_balance == 0
result += ",\n" + indent + align
else
result += ", "
end
result += part
bracket_balance += part.count("(") - part.count(")")
}
return result;
end
# Given the source code array and the index at which an exported symbol was
# detected, extracts and outputs the documentation.
def docufy(src,i)
@@ -38,7 +72,11 @@ def docufy(src,i)
name = name.sub("RM_","RedisModule_")
proto = src[i].sub("{","").strip+";\n"
proto = proto.sub("RM_","RedisModule_")
puts "## `#{name}`\n\n"
proto = linebreak_proto(proto, " ");
# Add a link target with the function name. (We don't trust the exact id of
# the generated one, which depends on the Markdown implementation.)
puts "<span id=\"#{name}\"></span>\n\n"
puts "### `#{name}`\n\n"
puts " #{proto}\n"
comment = ""
while true
@@ -50,13 +88,87 @@ def docufy(src,i)
puts comment+"\n\n"
end
# Print a comment from line until */ is found, as markdown.
def section_doc(src, i)
name = get_section_heading(src, i)
comment = "<span id=\"#{section_name_to_id(name)}\"></span>\n\n"
while true
# append line, except if it's a horizontal divider
comment = comment + src[i] if src[i] !~ /^[\/ ]?\*{1,2} ?-{50,}/
break if src[i] =~ /\*\//
i = i+1
end
comment = markdown(comment)
puts comment+"\n\n"
end
# generates an id suitable for links within the page
def section_name_to_id(name)
return "section-" +
name.strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-+|-+$/, '')
end
# Returns the name of the first section heading in the comment block for which
# is_section_doc(src, i) is true
def get_section_heading(src, i)
if src[i] =~ /^\/\*\*? \#+ *(.*)/
heading = $1
elsif src[i+1] =~ /^ ?\* \#+ *(.*)/
heading = $1
end
return heading.gsub(' -- ', ' ')
end
# Returns true if the line is the start of a generic documentation section. Such
# section must start with the # symbol, i.e. a markdown heading, on the first or
# the second line.
def is_section_doc(src, i)
return src[i] =~ /^\/\*\*? \#/ ||
(src[i] =~ /^\/\*/ && src[i+1] =~ /^ ?\* \#/)
end
def is_func_line(src, i)
line = src[i]
return line =~ /RM_/ &&
line[0] != ' ' && line[0] != '#' && line[0] != '/' &&
src[i-1] =~ /\*\//
end
puts "# Modules API reference\n\n"
puts "<!-- This file is generated from module.c using gendoc.rb -->\n\n"
src = File.open("../module.c").to_a
src.each_with_index{|line,i|
if line =~ /RM_/ && line[0] != ' ' && line[0] != '#' && line[0] != '/'
if src[i-1] =~ /\*\//
docufy(src,i)
end
src = File.open(File.dirname(__FILE__) ++ "/../module.c").to_a
# Build function index
$index = {}
src.each_with_index do |line,i|
if is_func_line(src, i)
line =~ /RM_([A-z0-9]+)/
name = "RedisModule_#{$1}"
$index[name] = true
end
}
end
# Print TOC
puts "## Sections\n\n"
src.each_with_index do |_line,i|
if is_section_doc(src, i)
name = get_section_heading(src, i)
puts "* [#{name}](\##{section_name_to_id(name)})\n"
end
end
puts "* [Function index](#section-function-index)\n\n"
# Docufy: Print function prototype and markdown docs
src.each_with_index do |_line,i|
if is_func_line(src, i)
docufy(src, i)
elsif is_section_doc(src, i)
section_doc(src, i)
end
end
# Print function index
puts "<span id=\"section-function-index\"></span>\n\n"
puts "## Function index\n\n"
$index.keys.sort.each{|x| puts "* [`#{x}`](\##{x})\n"}
puts "\n"
+10
View File
@@ -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
View File
@@ -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;
+48 -34
View File
@@ -113,34 +113,34 @@ void discardCommand(client *c) {
addReply(c,shared.ok);
}
void beforePropagateMultiOrExec(int multi) {
if (multi) {
/* Propagating MULTI */
serverAssert(!server.propagate_in_transaction);
server.propagate_in_transaction = 1;
} else {
/* Propagating EXEC */
serverAssert(server.propagate_in_transaction == 1);
server.propagate_in_transaction = 0;
}
void beforePropagateMulti() {
/* Propagating MULTI */
serverAssert(!server.propagate_in_transaction);
server.propagate_in_transaction = 1;
}
void afterPropagateExec() {
/* Propagating EXEC */
serverAssert(server.propagate_in_transaction == 1);
server.propagate_in_transaction = 0;
}
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
* implementation for more information. */
void execCommandPropagateMulti(int dbid) {
beforePropagateMultiOrExec(1);
beforePropagateMulti();
propagate(server.multiCommand,dbid,&shared.multi,1,
PROPAGATE_AOF|PROPAGATE_REPL);
}
void execCommandPropagateExec(int dbid) {
beforePropagateMultiOrExec(0);
propagate(server.execCommand,dbid,&shared.exec,1,
PROPAGATE_AOF|PROPAGATE_REPL);
afterPropagateExec();
}
/* Aborts a transaction, with a specific error message.
* The transaction is always aboarted with -EXECABORT so that the client knows
* The transaction is always aborted with -EXECABORT so that the client knows
* the server exited the multi state, but the actual reason for the abort is
* included too.
* Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */
@@ -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;
@@ -202,11 +210,9 @@ void execCommand(client *c) {
c->cmd = c->mstate.commands[j].cmd;
/* ACL permissions are also checked at the time of execution in case
* they were changed after the commands were ququed. */
* they were changed after the commands were queued. */
int acl_errpos;
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
if (acl_retval != ACL_OK) {
char *reason;
switch (acl_retval) {
@@ -217,7 +223,8 @@ void execCommand(client *c) {
reason = "no permission to touch the specified keys";
break;
case ACL_DENIED_CHANNEL:
reason = "no permission to publish to the specified channel";
reason = "no permission to access one of the channels used "
"as arguments";
break;
default:
reason = "no permission";
@@ -254,7 +261,6 @@ void execCommand(client *c) {
if (server.propagate_in_transaction) {
int is_master = server.masterhost == NULL;
server.dirty++;
beforePropagateMultiOrExec(0);
/* If inside the MULTI/EXEC block this instance was suddenly
* switched from master to slave (using the SLAVEOF command), the
* initial MULTI was propagated into the replication backlog, but the
@@ -264,18 +270,10 @@ void execCommand(client *c) {
char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
feedReplicationBacklog(execcmd,strlen(execcmd));
}
afterPropagateExec();
}
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) ===================
@@ -353,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) {
+196 -48
View File
@@ -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));
@@ -154,10 +163,12 @@ client *createClient(connection *conn) {
c->read_reploff = 0;
c->repl_ack_off = 0;
c->repl_ack_time = 0;
c->repl_last_partial_write = 0;
c->slave_listening_port = 0;
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);
@@ -249,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. */
@@ -331,8 +344,9 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
memcpy(tail->buf, s, len);
listAddNodeTail(c->reply, tail);
c->reply_bytes += tail->size;
closeClientOnOutputBufferLimitReached(c, 1);
}
asyncCloseClientOnOutputBufferLimitReached(c);
}
/* -----------------------------------------------------------------------------
@@ -406,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
@@ -562,7 +588,7 @@ void *addReplyDeferredLen(client *c) {
void setDeferredReply(client *c, void *node, const char *s, size_t length) {
listNode *ln = (listNode*)node;
clientReplyBlock *next;
clientReplyBlock *next, *prev;
/* Abort when *node is NULL: when the client should not accept writes
* we return NULL in addReplyDeferredLen() */
@@ -571,14 +597,31 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
/* Normally we fill this dummy NULL node, added by addReplyDeferredLen(),
* with a new buffer structure containing the protocol needed to specify
* the length of the array following. However sometimes when there is
* little memory to move, we may instead remove this NULL node, and prefix
* our protocol in the node immediately after to it, in order to save a
* write(2) syscall later. Conditions needed to do it:
* the length of the array following. However sometimes there might be room
* in the previous/next node so we can instead remove this NULL node, and
* suffix/prefix our data in the node immediately before/after it, in order
* to save a write(2) syscall later. Conditions needed to do it:
*
* - The prev node is non-NULL and has space in it or
* - The next node is non-NULL,
* - It has enough room already allocated
* - And not too large (avoid large memmove) */
if (ln->prev != NULL && (prev = listNodeValue(ln->prev)) &&
prev->size - prev->used > 0)
{
size_t len_to_copy = prev->size - prev->used;
if (len_to_copy > length)
len_to_copy = length;
memcpy(prev->buf + prev->used, s, len_to_copy);
prev->used += len_to_copy;
length -= len_to_copy;
if (length == 0) {
listDelNode(c->reply, ln);
return;
}
s += len_to_copy;
}
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
next->size - next->used >= length &&
next->used < PROTO_REPLY_CHUNK_BYTES * 4)
@@ -596,8 +639,9 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
memcpy(buf->buf, s, length);
listNodeValue(ln) = buf;
c->reply_bytes += buf->size;
closeClientOnOutputBufferLimitReached(c, 1);
}
asyncCloseClientOnOutputBufferLimitReached(c);
}
/* Populate the length object and try gluing it to the next chunk. */
@@ -608,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);
@@ -629,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 */
@@ -665,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. */
@@ -691,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;
}
@@ -736,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) {
@@ -928,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.
@@ -944,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) {
@@ -1338,6 +1435,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
@@ -1531,9 +1630,7 @@ int writeToClient(client *c, int handler_installed) {
}
atomicIncr(server.stat_net_output_bytes, totwritten);
if (nwritten == -1) {
if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
nwritten = 0;
} else {
if (connGetState(c->conn) != CONN_STATE_CONNECTED) {
serverLog(LL_VERBOSE,
"Error writing to client: %s", connGetLastError(c->conn));
freeClientAsync(c);
@@ -1626,6 +1723,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)
@@ -1834,6 +1935,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;
@@ -1881,6 +1986,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;
@@ -1948,22 +2057,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 clients blocked in a
* module blocking command, so that the reply callback will
* still be able to access the client argv and argc field.
* The client will be reset in unblockClientFromModule(). */
if (!(c->flags & CLIENT_BLOCKED) ||
(c->btype != BLOCKED_MODULE && c->btype != BLOCKED_PAUSE))
{
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
@@ -1990,12 +2100,20 @@ void commandProcessed(client *c) {
* of processing the command, otherwise C_OK is returned. */
int processCommandAndResetClient(client *c) {
int deadclient = 0;
client *old_client = server.current_client;
server.current_client = c;
if (processCommand(c) == C_OK) {
commandProcessed(c);
}
if (server.current_client == NULL) deadclient = 1;
server.current_client = NULL;
/*
* Restore the old client, this is needed because when a script
* times out, we will get into this code from processEventsWhileBlocked.
* Which will cause to set the server.current_client. If not restored
* we will return 1 to our caller which will falsely indicate the client
* is dead and will stop reading from its buffer.
*/
server.current_client = old_client;
/* performEvictions may flush slave output buffers. This may
* result in a slave, that may be the active client, to be
* freed. */
@@ -2646,7 +2764,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");
@@ -2941,6 +3059,8 @@ void helloCommand(client *c) {
int moreargs = (c->argc-1) - j;
const char *opt = c->argv[j]->ptr;
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
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;
@@ -3007,7 +3127,7 @@ void securityWarningCommand(client *c) {
static time_t logged_time;
time_t now = time(NULL);
if (labs(now-logged_time) > 60) {
if (llabs(now-logged_time) > 60) {
serverLog(LL_WARNING,"Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to Redis. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your Redis instance. Connection aborted.");
logged_time = now;
}
@@ -3027,6 +3147,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. */
@@ -3196,18 +3325,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
@@ -3286,6 +3430,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);
@@ -3304,6 +3449,8 @@ int areClientsPaused(void) {
* if it has. Also returns true if clients are now paused and false
* otherwise. */
int checkClientPauseTimeoutAndReturnIfPaused(void) {
if (!areClientsPaused())
return 0;
if (server.client_pause_end_time < server.mstime) {
unpauseClients();
}
@@ -3332,7 +3479,7 @@ void processEventsWhileBlocked(void) {
/* Note: when we are processing events while blocked (for instance during
* busy Lua scripts), we set a global flag. When such flag is set, we
* avoid handling the read part of clients using threaded I/O.
* See https://github.com/antirez/redis/issues/6988 for more info. */
* See https://github.com/redis/redis/issues/6988 for more info. */
ProcessingEventsWhileBlocked = 1;
while (iterations--) {
long long startval = server.events_processed_while_blocked;
@@ -3603,7 +3750,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);
@@ -3667,6 +3814,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
+2
View File
@@ -56,6 +56,7 @@ int keyspaceEventsStringToFlags(char *classes) {
case 'E': flags |= NOTIFY_KEYEVENT; break;
case 't': flags |= NOTIFY_STREAM; break;
case 'm': flags |= NOTIFY_KEY_MISS; break;
case 'd': flags |= NOTIFY_MODULE; break;
default: return -1;
}
}
@@ -82,6 +83,7 @@ sds keyspaceEventsFlagsToString(int flags) {
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
}
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
+28 -8
View File
@@ -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);
}
}
@@ -727,7 +742,11 @@ int getRangeLongFromObjectOrReply(client *c, robj *o, long min, long max, long *
}
int getPositiveLongFromObjectOrReply(client *c, robj *o, long *target, const char *msg) {
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg);
if (msg) {
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg);
} else {
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, "value is out of range, must be positive");
}
}
int getIntFromObjectOrReply(client *c, robj *o, int *target, const char *msg) {
@@ -832,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");
}
@@ -877,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
@@ -890,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) {
@@ -902,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);
@@ -1227,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
View File
@@ -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
+26 -24
View File
@@ -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;
}
/*-----------------------------------------------------------------------------
@@ -250,18 +268,20 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
/* Unsubscribe from all the channels. Return the number of channels the
* client was subscribed to. */
int pubsubUnsubscribeAllChannels(client *c, int notify) {
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
int count = 0;
if (dictSize(c->pubsub_channels) > 0) {
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
robj *channel = dictGetKey(de);
while((de = dictNext(di)) != NULL) {
robj *channel = dictGetKey(de);
count += pubsubUnsubscribeChannel(c,channel,notify);
count += pubsubUnsubscribeChannel(c,channel,notify);
}
dictReleaseIterator(di);
}
/* We were subscribed to nothing? Still reply to the client. */
if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
dictReleaseIterator(di);
return count;
}
@@ -329,21 +349,6 @@ int pubsubPublishMessage(robj *channel, robj *message) {
return receivers;
}
/* This wraps handling ACL channel permissions for the given client. */
int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal) {
/* Check if the user can run the command according to the current
* ACLs. */
int acl_chanpos;
int acl_retval = ACLCheckPubsubPerm(c,idx,count,literal,&acl_chanpos);
if (acl_retval == ACL_DENIED_CHANNEL) {
addACLLogEntry(c,acl_retval,acl_chanpos,NULL);
addReplyError(c,
"-NOPERM this user has no permissions to access "
"one of the channels used as arguments");
}
return acl_retval;
}
/*-----------------------------------------------------------------------------
* Pubsub commands implementation
*----------------------------------------------------------------------------*/
@@ -351,7 +356,6 @@ int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal)
/* SUBSCRIBE channel [channel ...] */
void subscribeCommand(client *c) {
int j;
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,0) != ACL_OK) return;
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
/**
* A client that has CLIENT_DENY_BLOCKING flag on
@@ -385,7 +389,6 @@ void unsubscribeCommand(client *c) {
/* PSUBSCRIBE pattern [pattern ...] */
void psubscribeCommand(client *c) {
int j;
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,1) != ACL_OK) return;
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
/**
* A client that has CLIENT_DENY_BLOCKING flag on
@@ -418,7 +421,6 @@ void punsubscribeCommand(client *c) {
/* PUBLISH <channel> <message> */
void publishCommand(client *c) {
if (pubsubCheckACLPermissionsOrReply(c,1,1,0) != ACL_OK) return;
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
if (server.cluster_enabled)
clusterPropagatePublish(c->argv[1],c->argv[2]);
+190 -185
View File
@@ -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. */
@@ -315,7 +320,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
if (forward == node || reverse == node)
in_depth = 1;
if (forward == reverse)
/* We passed into compress depth of opposite side of the quicklist
* so there's no need to compress anything and we can exit. */
if (forward == reverse || forward->next == reverse)
return;
forward = forward->next;
@@ -325,11 +332,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
if (!in_depth)
quicklistCompressNode(node);
if (depth > 2) {
/* At this point, forward and reverse are one node beyond depth */
quicklistCompressNode(forward);
quicklistCompressNode(reverse);
}
/* At this point, forward and reverse are one node beyond depth */
quicklistCompressNode(forward);
quicklistCompressNode(reverse);
}
#define quicklistCompress(_ql, _node) \
@@ -380,10 +385,11 @@ REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
quicklist->head = quicklist->tail = new_node;
}
/* Update len first, so in __quicklistCompress we know exactly len */
quicklist->len++;
if (old_node)
quicklistCompress(quicklist, old_node);
quicklist->len++;
}
/* Wrappers for node inserting around existing node. */
@@ -443,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)
@@ -462,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)
@@ -481,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 =
@@ -504,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 =
@@ -602,15 +614,16 @@ REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
quicklist->head = node->next;
}
/* Update len first, so in __quicklistCompress we know exactly len */
quicklist->len--;
quicklist->count -= node->count;
/* If we deleted a node within our compress depth, we
* now have compressed nodes needing to be decompressed. */
__quicklistCompress(quicklist, NULL);
quicklist->count -= node->count;
zfree(node->zl);
zfree(node);
quicklist->len--;
}
/* Delete one entry from list given the node for the entry and a pointer
@@ -845,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 */
@@ -999,7 +1013,7 @@ int quicklistDelRange(quicklist *quicklist, const long start,
* can just delete the entire node without ziplist math. */
delete_entire_node = 1;
del = node->count;
} else if (entry.offset >= 0 && extent >= node->count) {
} else if (entry.offset >= 0 && extent + entry.offset >= node->count) {
/* If deleting more nodes after this one, calculate delete based
* on size of current node. */
del = node->count - entry.offset;
@@ -1296,17 +1310,24 @@ void quicklistRotate(quicklist *quicklist) {
/* First, get the tail entry */
unsigned char *p = ziplistIndex(quicklist->tail->zl, -1);
unsigned char *value;
unsigned char *value, *tmp;
long long longval;
unsigned int sz;
char longstr[32] = {0};
ziplistGet(p, &value, &sz, &longval);
ziplistGet(p, &tmp, &sz, &longval);
/* If value found is NULL, then ziplistGet populated longval instead */
if (!value) {
if (!tmp) {
/* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr;
} else if (quicklist->len == 1) {
/* Copy buffer since there could be a memory overlap when move
* entity from tail to head in the same ziplist. */
value = zmalloc(sz);
memcpy(value, tmp, sz);
} else {
value = tmp;
}
/* Add tail entry to head (must happen before tail is deleted). */
@@ -1321,6 +1342,8 @@ void quicklistRotate(quicklist *quicklist) {
/* Remove tail entry. */
quicklistDelIndex(quicklist, quicklist->tail, &p);
if (value != (unsigned char*)longstr && value != tmp)
zfree(value);
}
/* pop from quicklist and return result in 'data' ptr. Value of 'data'
@@ -1509,8 +1532,6 @@ void quicklistBookmarksClear(quicklist *ql) {
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
#define OK printf("\tOK\n")
#define ERROR \
do { \
printf("\tERROR!\n"); \
@@ -1630,7 +1651,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
}
if (ql->len == 0 && !errors) {
OK;
return errors;
}
@@ -1679,8 +1699,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
}
}
if (!errors)
OK;
return errors;
}
@@ -1692,9 +1710,10 @@ static char *genstr(char *prefix, int i) {
}
/* main test, but callable from other files */
int quicklistTest(int argc, char *argv[]) {
int quicklistTest(int argc, char *argv[], int accurate) {
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
unsigned int err = 0;
int optimize_start =
@@ -1703,11 +1722,14 @@ int quicklistTest(int argc, char *argv[]) {
printf("Starting optimization offset at: %d\n", optimize_start);
int options[] = {0, 1, 2, 3, 4, 5, 6, 10};
int fills[] = {-5, -4, -3, -2, -1, 0,
1, 2, 32, 66, 128, 999};
size_t option_count = sizeof(options) / sizeof(*options);
int fill_count = (int)(sizeof(fills) / sizeof(*fills));
long long runtime[option_count];
for (int _i = 0; _i < (int)option_count; _i++) {
printf("Testing Option %d\n", options[_i]);
printf("Testing Compression option %d\n", options[_i]);
long long start = mstime();
TEST("create list") {
@@ -1732,57 +1754,53 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql);
}
for (int f = optimize_start; f < 32; f++) {
TEST_DESC("add to tail 5x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("add to tail 5x at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 5; i++)
quicklistPushTail(ql, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 32; f++) {
TEST_DESC("add to head 5x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("add to head 5x at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 5; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 512; f++) {
TEST_DESC("add to tail 500x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("add to tail 500x at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i), 64);
if (ql->count != 500)
ERROR;
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 512; f++) {
TEST_DESC("add to head 500x at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("add to head 500x at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 500)
ERROR;
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 16, 500, 20, 32);
quicklistRelease(ql);
}
@@ -1795,9 +1813,9 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql);
}
for (int f = optimize_start; f < 32; f++) {
TEST("rotate one val once") {
quicklist *ql = quicklistNew(f, options[_i]);
TEST("rotate one val once") {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklistPushHead(ql, "hello", 6);
quicklistRotate(ql);
/* Ignore compression verify because ziplist is
@@ -1807,10 +1825,9 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 3; f++) {
TEST_DESC("rotate 500 val 5000 times at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklistPushHead(ql, "900", 3);
quicklistPushHead(ql, "7000", 4);
quicklistPushHead(ql, "-1200", 5);
@@ -1822,11 +1839,11 @@ int quicklistTest(int argc, char *argv[]) {
ql_info(ql);
quicklistRotate(ql);
}
if (f == 1)
if (fills[f] == 1)
ql_verify(ql, 504, 504, 1, 1);
else if (f == 2)
else if (fills[f] == 2)
ql_verify(ql, 252, 504, 2, 2);
else if (f == 32)
else if (fills[f] == 32)
ql_verify(ql, 16, 504, 32, 24);
quicklistRelease(ql);
}
@@ -1847,7 +1864,7 @@ int quicklistTest(int argc, char *argv[]) {
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))
@@ -1865,7 +1882,7 @@ int quicklistTest(int argc, char *argv[]) {
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);
@@ -2003,11 +2020,10 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql);
}
for (int f = optimize_start; f < 12; f++) {
TEST_DESC("insert once in elements while iterating at fill %d at "
"compress %d\n",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("insert once in elements while iterating at compress %d",
options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklistPushTail(ql, "abc", 3);
quicklistSetFill(ql, 1);
quicklistPushTail(ql, "def", 3); /* force to unique node */
@@ -2059,12 +2075,10 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 1024; f++) {
TEST_DESC(
"insert [before] 250 new in middle of 500 elements at fill"
" %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d",
options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) {
@@ -2072,17 +2086,16 @@ int quicklistTest(int argc, char *argv[]) {
quicklistIndex(ql, 250, &entry);
quicklistInsertBefore(ql, &entry, genstr("abc", i), 32);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 25, 750, 32, 20);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 1024; f++) {
TEST_DESC("insert [after] 250 new in middle of 500 elements at "
"fill %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d",
options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) {
@@ -2094,7 +2107,7 @@ int quicklistTest(int argc, char *argv[]) {
if (ql->count != 750)
ERR("List size not 750, but rather %ld", ql->count);
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 26, 750, 20, 32);
quicklistRelease(ql);
}
@@ -2132,70 +2145,58 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(copy);
}
for (int f = optimize_start; f < 512; f++) {
for (int f = 0; f < fill_count; f++) {
TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, 1, &entry);
if (!strcmp((char *)entry.value, "hello2"))
OK;
else
if (strcmp((char *)entry.value, "hello2") != 0)
ERR("Value: %s", entry.value);
quicklistIndex(ql, 200, &entry);
if (!strcmp((char *)entry.value, "hello201"))
OK;
else
if (strcmp((char *)entry.value, "hello201") != 0)
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d",
fills[f], options[_i]) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -1, &entry);
if (!strcmp((char *)entry.value, "hello500"))
OK;
else
if (strcmp((char *)entry.value, "hello500") != 0)
ERR("Value: %s", entry.value);
quicklistIndex(ql, -2, &entry);
if (!strcmp((char *)entry.value, "hello499"))
OK;
else
if (strcmp((char *)entry.value, "hello499") != 0)
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index -100 from 500 list at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("index -100 from 500 list at fill %d at compress %d",
fills[f], options[_i]) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -100, &entry);
if (!strcmp((char *)entry.value, "hello401"))
OK;
else
if (strcmp((char *)entry.value, "hello401") != 0)
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
fills[f], options[_i]) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 50; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry;
if (quicklistIndex(ql, 50, &entry))
ERR("Index found at 50 with 50 list: %.*s", entry.sz,
entry.value);
else
OK;
quicklistRelease(ql);
}
}
@@ -2238,6 +2239,17 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql);
}
TEST("delete less than fill but across nodes") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, 60, 10);
ql_verify(ql, 16, 490, 32, 20);
quicklistRelease(ql);
}
TEST("delete negative 1 from 500 list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistSetFill(ql, 32);
@@ -2356,12 +2368,11 @@ int quicklistTest(int argc, char *argv[]) {
quicklistReplaceAtIndex(ql, 1, "foo", 3);
quicklistReplaceAtIndex(ql, -1, "bar", 3);
quicklistRelease(ql);
OK;
}
for (int f = optimize_start; f < 16; f++) {
TEST_DESC("lrem test at fill %d at compress %d", f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("lrem test at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
char *words[] = {"abc", "foo", "bar", "foobar", "foobared",
"zap", "bar", "test", "foo"};
char *result[] = {"abc", "foo", "foobar", "foobared",
@@ -2386,14 +2397,12 @@ int quicklistTest(int argc, char *argv[]) {
/* check result of lrem 0 bar */
iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0;
int ok = 1;
while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test,
* foo */
if (strncmp((char *)entry.value, result[i], entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, result[i]);
ok = 0;
}
i++;
}
@@ -2430,23 +2439,18 @@ int quicklistTest(int argc, char *argv[]) {
entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, resultB[resB - 1 - i]);
ok = 0;
}
i++;
}
quicklistReleaseIterator(iter);
/* final result of all tests */
if (ok)
OK;
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 16; f++) {
TEST_DESC("iterate reverse + delete at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("iterate reverse + delete at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklistPushTail(ql, "abc", 3);
quicklistPushTail(ql, "def", 3);
quicklistPushTail(ql, "hij", 3);
@@ -2483,10 +2487,9 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 800; f++) {
TEST_DESC("iterator at index test at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("iterator at index test at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 760; i++) {
@@ -2510,10 +2513,9 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test A at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("ltrim test A at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 32; i++) {
@@ -2521,7 +2523,7 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 32, 32, 32);
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
quicklistDelRange(ql, 0, 25);
@@ -2534,18 +2536,17 @@ int quicklistTest(int argc, char *argv[]) {
"%lld",
entry.longval, nums[25 + i]);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 7, 7, 7);
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test B at fill %d at compress %d", f,
options[_i]) {
TEST_DESC("ltrim test B at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
/* Force-disable compression because our 33 sequential
* integers don't compress and the check always fails. */
quicklist *ql = quicklistNew(f, QUICKLIST_NOCOMPRESS);
quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS);
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
@@ -2553,24 +2554,20 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
quicklistDelRange(ql, 0, 5);
quicklistDelRange(ql, -16, 16);
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 12, 12, 12);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != 5)
ERR("A: longval not 5, but %lld", entry.longval);
else
OK;
quicklistIndex(ql, -1, &entry);
if (entry.longval != 16)
ERR("B! got instead: %lld", entry.longval);
else
OK;
quicklistPushTail(ql, "bobobob", 7);
quicklistIndex(ql, -1, &entry);
if (strncmp((char *)entry.value, "bobobob", 7))
@@ -2587,10 +2584,9 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test C at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("ltrim test C at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
@@ -2598,28 +2594,25 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
quicklistDelRange(ql, 0, 3);
quicklistDelRange(ql, -29,
4000); /* make sure not loop forever */
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 1, 1, 1, 1);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != -5157318210846258173)
ERROR;
else
OK;
quicklistRelease(ql);
}
}
for (int f = optimize_start; f < 40; f++) {
TEST_DESC("ltrim test D at fill %d at compress %d", f,
options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
TEST_DESC("ltrim test D at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
@@ -2627,7 +2620,7 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz);
}
if (f == 32)
if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1);
quicklistDelRange(ql, -12, 3);
if (ql->count != 30)
@@ -2637,9 +2630,8 @@ int quicklistTest(int argc, char *argv[]) {
}
}
for (int f = optimize_start; f < 72; f++) {
TEST_DESC("create quicklist from ziplist at fill %d at compress %d",
f, options[_i]) {
TEST_DESC("create quicklist from ziplist at compress %d", options[_i]) {
for (int f = 0; f < fill_count; f++) {
unsigned char *zl = ziplistNew();
long long nums[64];
char num[64];
@@ -2653,12 +2645,12 @@ int quicklistTest(int argc, char *argv[]) {
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i),
32, ZIPLIST_TAIL);
}
quicklist *ql = quicklistCreateFromZiplist(f, options[_i], zl);
if (f == 1)
quicklist *ql = quicklistCreateFromZiplist(fills[f], options[_i], zl);
if (fills[f] == 1)
ql_verify(ql, 66, 66, 1, 1);
else if (f == 32)
else if (fills[f] == 32)
ql_verify(ql, 3, 66, 32, 2);
else if (f == 66)
else if (fills[f] == 66)
ql_verify(ql, 1, 66, 66, 66);
quicklistRelease(ql);
}
@@ -2671,45 +2663,58 @@ int quicklistTest(int argc, char *argv[]) {
/* Run a longer test of compression depth outside of primary test loop. */
int list_sizes[] = {250, 251, 500, 999, 1000};
long long start = mstime();
for (int list = 0; list < (int)(sizeof(list_sizes) / sizeof(*list_sizes));
list++) {
for (int f = optimize_start; f < 128; f++) {
for (int depth = 1; depth < 40; depth++) {
/* skip over many redundant test cases */
TEST_DESC("verify specific compression of interior nodes with "
"%d list "
"at fill %d at compress %d",
list_sizes[list], f, depth) {
quicklist *ql = quicklistNew(f, depth);
int list_count = accurate ? (int)(sizeof(list_sizes) / sizeof(*list_sizes)) : 1;
for (int list = 0; list < list_count; list++) {
TEST_DESC("verify specific compression of interior nodes with %d list ",
list_sizes[list]) {
for (int f = 0; f < fill_count; f++) {
for (int depth = 1; depth < 40; depth++) {
/* skip over many redundant test cases */
quicklist *ql = quicklistNew(fills[f], depth);
for (int i = 0; i < list_sizes[list]; i++) {
quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64);
quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64);
}
quicklistNode *node = ql->head;
unsigned int low_raw = ql->compress;
unsigned int high_raw = ql->len - ql->compress;
for (unsigned int at = 0; at < ql->len;
at++, node = node->next) {
if (at < low_raw || at >= high_raw) {
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
ERR("Incorrect compression: node %d is "
"compressed at depth %d ((%u, %u); total "
"nodes: %lu; size: %u)",
at, depth, low_raw, high_raw, ql->len,
node->sz);
for (int step = 0; step < 2; step++) {
/* test remove node */
if (step == 1) {
for (int i = 0; i < list_sizes[list] / 2; i++) {
unsigned char *data;
assert(quicklistPop(ql, QUICKLIST_HEAD, &data,
NULL, NULL));
zfree(data);
assert(quicklistPop(ql, QUICKLIST_TAIL, &data,
NULL, NULL));
zfree(data);
}
} else {
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
ERR("Incorrect non-compression: node %d is NOT "
"compressed at depth %d ((%u, %u); total "
"nodes: %lu; size: %u; attempted: %d)",
at, depth, low_raw, high_raw, ql->len,
node->sz, node->attempted_compress);
}
quicklistNode *node = ql->head;
unsigned int low_raw = ql->compress;
unsigned int high_raw = ql->len - ql->compress;
for (unsigned int at = 0; at < ql->len;
at++, node = node->next) {
if (at < low_raw || at >= high_raw) {
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
ERR("Incorrect compression: node %d is "
"compressed at depth %d ((%u, %u); total "
"nodes: %lu; size: %u)",
at, depth, low_raw, high_raw, ql->len,
node->sz);
}
} else {
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
ERR("Incorrect non-compression: node %d is NOT "
"compressed at depth %d ((%u, %u); total "
"nodes: %lu; size: %u; attempted: %d)",
at, depth, low_raw, high_raw, ql->len,
node->sz, node->attempted_compress);
}
}
}
}
quicklistRelease(ql);
}
}
+1 -1
View File
@@ -199,7 +199,7 @@ quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
void quicklistBookmarksClear(quicklist *ql);
#ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]);
int quicklistTest(int argc, char *argv[], int accurate);
#endif
/* Directions for iterators */
+1
View File
@@ -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;
+178 -47
View File
@@ -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;
@@ -1073,8 +1081,7 @@ size_t rdbSavedObjectLen(robj *o, robj *key) {
/* Save a key-value pair, with expire time, type, key, value.
* On error -1 is returned.
* On success if the key was actually saved 1 is returned, otherwise 0
* is returned (the key was already expired). */
* On success if the key was actually saved 1 is returned. */
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;
int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
@@ -1517,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
@@ -1541,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,
@@ -1561,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 */
@@ -1624,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;
@@ -1664,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) {
@@ -1676,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;
@@ -1686,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();
@@ -1730,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);
}
@@ -1793,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);
@@ -1812,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 ||
@@ -1857,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);
@@ -1871,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);
@@ -1895,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);
@@ -1924,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;
@@ -1938,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;
@@ -2006,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) {
@@ -2095,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);
@@ -2159,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);
@@ -2176,16 +2264,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
return NULL;
}
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10];
if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2) {
char name[10];
moduleTypeNameByID(name,moduleid);
return rdbLoadCheckModuleValue(rdb,name);
}
if (mt == NULL) {
char name[10];
moduleTypeNameByID(name,moduleid);
rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module '%s'", name);
rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module type '%s'", name);
return NULL;
}
RedisModuleIO io;
@@ -2212,7 +2301,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
return NULL;
}
if (eof != RDB_MODULE_OPCODE_EOF) {
rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by "
"the proper module value EOF marker", moduleTypeModuleName(mt));
if (ptr) {
o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
decrRefCount(o);
@@ -2222,8 +2312,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
}
if (ptr == NULL) {
moduleTypeNameByID(name,moduleid);
rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible "
"module is not able to load. Check for modules log above for additional clues.",
moduleTypeModuleName(mt));
return NULL;
}
o = createModuleObject(mt,ptr);
@@ -2231,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
@@ -2332,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;
@@ -2421,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
@@ -2482,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);
@@ -2504,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 {
@@ -2531,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
@@ -2544,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.
@@ -2612,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():
@@ -2689,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;
@@ -2729,7 +2856,7 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
* the cleanup needed. */
void killRDBChild(void) {
kill(server.child_pid, SIGUSR1);
/* Because we are not using here wait4 (like we have in killAppendOnlyChild
/* Because we are not using here waitpid (like we have in killAppendOnlyChild
* and TerminateModuleForkChild), all the cleanup operations is done by
* checkChildrenDone, that later will find that the process killed.
* This includes:
@@ -2792,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);
+6 -1
View File
@@ -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);
+53 -46
View File
@@ -99,10 +99,8 @@ static struct config {
int randomkeys_keyspacelen;
int keepalive;
int pipeline;
int showerrors;
long long start;
long long totlatency;
long long *latency;
const char *title;
list *clients;
int quiet;
@@ -308,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;
@@ -367,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) {
@@ -514,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);
@@ -641,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);
@@ -1294,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 */
@@ -1471,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
@@ -1574,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"
@@ -1656,7 +1659,10 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
const float instantaneous_rps = (float)(requests_finished-previous_requests_finished)/instantaneous_dt;
config.previous_tick = current_tick;
atomicSet(config.previous_requests_finished,requests_finished);
config.last_printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f);
int printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f);
if (printed_bytes > config.last_printed_bytes){
config.last_printed_bytes = printed_bytes;
}
hdr_reset(config.current_sec_latency_histogram);
fflush(stdout);
return 250; /* every 250ms */
@@ -1697,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;
@@ -1780,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);
@@ -1944,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);
}
@@ -1972,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;
+4 -2
View File
@@ -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];
+3 -3
View File
@@ -192,6 +192,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
int closefile = (fp == NULL);
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
rioInitWithFile(&rdb,fp);
rdbstate.rio = &rdb;
rdb.update_cksum = rdbLoadProgressCallback;
@@ -208,7 +209,6 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
}
expiretime = -1;
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
while(1) {
robj *key, *val;
@@ -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++;
+232 -107
View File
@@ -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;
@@ -227,7 +229,7 @@ static struct config {
int scan_mode;
int intrinsic_latency_mode;
int intrinsic_latency_duration;
char *pattern;
sds pattern;
char *rdb_filename;
int bigkeys;
int memkeys;
@@ -237,6 +239,7 @@ static struct config {
char *auth;
int askpass;
char *user;
int quoted_input; /* Force input args to be treated as quoted strings */
int output; /* output mode, see OUTPUT_* defines */
int push_output; /* Should we display spontaneous PUSH replies */
sds mb_delim;
@@ -405,15 +408,17 @@ static void parseRedisUri(const char *uri) {
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) {
#ifdef USE_OPENSSL
config.tls = 1;
curr += strlen(tlsscheme);
#else
fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n");
exit(1);
#endif
} else if (strncasecmp(scheme, curr, strlen(scheme))) {
} else if (!strncasecmp(scheme, curr, strlen(scheme))) {
curr += strlen(scheme);
} else {
fprintf(stderr,"Invalid URI scheme\n");
exit(1);
}
curr += strlen(scheme);
if (curr == end) return;
/* Extract user info. */
@@ -442,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) {
@@ -660,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;
}
@@ -763,6 +768,23 @@ static void freeHintsCallback(void *ptr) {
* Networking / parsing
*--------------------------------------------------------------------------- */
/* Unquote a null-terminated string and return it as a binary-safe sds. */
static sds unquoteCString(char *str) {
int count;
sds *unquoted = sdssplitargs(str, &count);
sds res = NULL;
if (unquoted && count == 1) {
res = unquoted[0];
unquoted[0] = NULL;
}
if (unquoted)
sdsfreesplitres(unquoted, count);
return res;
}
/* Send AUTH command to the server */
static int cliAuth(redisContext *ctx, char *user, char *auth) {
redisReply *reply;
@@ -772,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. */
@@ -802,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:
@@ -819,9 +860,14 @@ static int cliConnect(int flags) {
if (context == NULL || flags & CC_FORCE) {
if (context != NULL) {
redisFree(context);
config.dbnum = 0;
config.in_multi = 0;
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);
@@ -840,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;
@@ -876,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);
@@ -1104,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++) {
@@ -1162,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++) {
@@ -1249,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;
@@ -1273,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)
@@ -1285,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);
@@ -1406,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();
@@ -1418,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);
@@ -1513,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")) {
@@ -1530,6 +1610,8 @@ static int parseOptions(int argc, char **argv) {
config.output = OUTPUT_RAW;
} else if (!strcmp(argv[i],"--no-raw")) {
config.output = OUTPUT_STANDARD;
} else if (!strcmp(argv[i],"--quoted-input")) {
config.quoted_input = 1;
} else if (!strcmp(argv[i],"--csv")) {
config.output = OUTPUT_CSV;
} else if (!strcmp(argv[i],"--latency")) {
@@ -1554,7 +1636,15 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--scan")) {
config.scan_mode = 1;
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
config.pattern = argv[++i];
sdsfree(config.pattern);
config.pattern = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"--quoted-pattern") && !lastarg) {
sdsfree(config.pattern);
config.pattern = unquoteCString(argv[++i]);
if (!config.pattern) {
fprintf(stderr,"Invalid quoted string specified for --quoted-pattern.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
config.intrinsic_latency_mode = 1;
config.intrinsic_latency_duration = atoi(argv[++i]);
@@ -1741,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");
@@ -1838,6 +1933,7 @@ static void usage(void) {
" --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n"
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
" --quoted-input Force input to be handled as quoted strings.\n"
" --csv Output in CSV format.\n"
" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
" STDOUT is a tty but can be overriden with --show-pushes no.\n"
@@ -1858,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"
@@ -1873,6 +1970,8 @@ static void usage(void) {
" --scan List all keys using the SCAN command.\n"
" --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n"
" options (default: *).\n"
" --quoted-pattern <pat> Same as --pattern, but the specified string can be\n"
" quoted, in order to pass an otherwise non binary-safe string.\n"
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
" The test will run for the specified amount of seconds.\n"
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
@@ -1898,6 +1997,7 @@ static void usage(void) {
" redis-cli get mypasswd\n"
" redis-cli -r 100 lpush mylist x\n"
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
" redis-cli --quoted-input set '\"null-\\x00-separated\"' value\n"
" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
" redis-cli --scan --pattern '*:12345*'\n"
"\n"
@@ -1927,43 +2027,58 @@ static int confirmWithYes(char *msg, int ignore_force) {
return (nread != 0 && !strcmp("yes", buf));
}
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
int j;
char **sds = zmalloc(sizeof(char*)*count);
/* Create an sds array from argv, either as-is or by dequoting every
* element. When quoted is non-zero, may return a NULL to indicate an
* invalid quoted string.
*/
static sds *getSdsArrayFromArgv(int argc, char **argv, int quoted) {
sds *res = sds_malloc(sizeof(sds) * argc);
for(j = 0; j < count; j++)
sds[j] = sdsnew(args[j]);
for (int j = 0; j < argc; j++) {
if (quoted) {
sds unquoted = unquoteCString(argv[j]);
if (!unquoted) {
while (--j >= 0) sdsfree(res[j]);
sds_free(res);
return NULL;
}
res[j] = unquoted;
} else {
res[j] = sdsnew(argv[j]);
}
}
return sds;
}
static void freeConvertedSds(int count, char **sds) {
int j;
for (j = 0; j < count; j++)
sdsfree(sds[j]);
zfree(sds);
return res;
}
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;
}
@@ -2175,17 +2290,19 @@ static void repl(void) {
static int noninteractive(int argc, char **argv) {
int retval = 0;
argv = convertToSds(argc, argv);
if (config.stdinarg) {
argv = zrealloc(argv, (argc+1)*sizeof(char*));
argv[argc] = readArgFromStdin();
retval = issueCommand(argc+1, argv);
sdsfree(argv[argc]);
} else {
retval = issueCommand(argc, argv);
sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input);
if (!sds_args) {
printf("Invalid quoted string\n");
return 1;
}
freeConvertedSds(argc, argv);
if (config.stdinarg) {
sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds));
sds_args[argc] = readArgFromStdin();
argc++;
}
retval = issueCommand(argc, sds_args);
sdsfreesplitres(sds_args, argc);
return retval;
}
@@ -2910,8 +3027,12 @@ static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
else types = sdsempty();
/* Master type 'm' is always set as the first character of the
* types string. */
if (!node->replicate) types = sdscatprintf(types, "m%s", types);
else types = sdscat(types, "s");
if (node->replicate) types = sdscat(types, "s");
else {
sds s = sdscatsds(sdsnew("m"), types);
sdsfree(types);
types = s;
}
dictReplace(related, key, types);
}
/* Now it's trivial to check, for each related group having the
@@ -2969,7 +3090,7 @@ static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
ip_count,
&offenders,
&offending_len);
if (score == 0) break; // Optimal anti affinity reached
if (score == 0 || offending_len == 0) break; // Optimal anti affinity reached
/* We'll try to randomly swap a slave's assigned master causing
* an affinity problem with another random slave, to see if we
* can improve the affinity. */
@@ -5432,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'. */
@@ -5445,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;
@@ -6485,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. */
@@ -6519,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 "
@@ -6822,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;
@@ -6936,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 */
@@ -6996,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) {
@@ -7101,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);
@@ -7144,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 {
@@ -7153,7 +7274,10 @@ static void getRDB(clusterManagerNode *node) {
redisFree(s); /* Close the connection ASAP as fsync() may take time. */
if (node)
node->context = NULL;
fsync(fd);
if (!write_to_stdout && fsync(fd) == -1) {
fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
exit(1);
}
close(fd);
if (node) {
sdsfree(filename);
@@ -7329,8 +7453,8 @@ static redisReply *sendScan(unsigned long long *it) {
redisReply *reply;
if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s",
*it,config.pattern);
reply = redisCommand(context, "SCAN %llu MATCH %b",
*it, config.pattern, sdslen(config.pattern));
else
reply = redisCommand(context,"SCAN %llu",*it);
@@ -7365,8 +7489,14 @@ static int getDbSize(void) {
reply = redisCommand(context, "DBSIZE");
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
fprintf(stderr, "Couldn't determine DBSIZE!\n");
if (reply == NULL) {
fprintf(stderr, "\nI/O error\n");
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "Couldn't determine DBSIZE: %s\n", reply->str);
exit(1);
} else if (reply->type != REDIS_REPLY_INTEGER) {
fprintf(stderr, "Non INTEGER response from DBSIZE!\n");
exit(1);
}
@@ -7942,23 +8072,16 @@ static void scanMode(void) {
unsigned long long cur = 0;
do {
if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s",
cur,config.pattern);
else
reply = redisCommand(context,"SCAN %llu",cur);
if (reply == NULL) {
printf("I/O error\n");
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
} else {
unsigned int j;
cur = strtoull(reply->element[0]->str,NULL,10);
for (j = 0; j < reply->element[1]->elements; j++)
reply = sendScan(&cur);
for (unsigned int j = 0; j < reply->element[1]->elements; j++) {
if (config.output == OUTPUT_STANDARD) {
sds out = sdscatrepr(sdsempty(), reply->element[1]->element[j]->str,
reply->element[1]->element[j]->len);
printf("%s\n", out);
sdsfree(out);
} else {
printf("%s\n", reply->element[1]->element[j]->str);
}
}
freeReplyObject(reply);
} while(cur != 0);
@@ -8152,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;
@@ -8162,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;
+18 -3
View File
@@ -160,13 +160,14 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
#define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
#define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */
/* Next notification flag, must be updated when adding new flags above!
This flag should not be used directly by the module.
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
#define _REDISMODULE_NOTIFY_NEXT (1<<13)
#define _REDISMODULE_NOTIFY_NEXT (1<<14)
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */
/* A special pointer that we can use between the core and the module to signal
* field deletion, and that is impossible to be a valid pointer. */
@@ -193,6 +194,12 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOT_USED(V) ((void) V)
/* Logging level strings */
#define REDISMODULE_LOGLEVEL_DEBUG "debug"
#define REDISMODULE_LOGLEVEL_VERBOSE "verbose"
#define REDISMODULE_LOGLEVEL_NOTICE "notice"
#define REDISMODULE_LOGLEVEL_WARNING "warning"
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
@@ -635,6 +642,8 @@ REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len
REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR;
REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
REDISMODULE_API mstime_t (*RedisModule_GetAbsExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetAbsExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) REDISMODULE_ATTR;
REDISMODULE_API unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
@@ -665,6 +674,7 @@ REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int
REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
@@ -826,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;
@@ -834,7 +845,7 @@ REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, un
REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR;
#endif
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
/* This is included inline inside each Redis module. */
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED;
@@ -906,6 +917,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(StringTruncate);
REDISMODULE_GET_API(GetExpire);
REDISMODULE_GET_API(SetExpire);
REDISMODULE_GET_API(GetAbsExpire);
REDISMODULE_GET_API(SetAbsExpire);
REDISMODULE_GET_API(ResetDataset);
REDISMODULE_GET_API(DbSize);
REDISMODULE_GET_API(RandomKey);
@@ -936,6 +949,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(IsKeysPositionRequest);
REDISMODULE_GET_API(KeyAtPos);
REDISMODULE_GET_API(GetClientId);
REDISMODULE_GET_API(GetClientUserNameById);
REDISMODULE_GET_API(GetContextFlags);
REDISMODULE_GET_API(AvoidReplicaTraffic);
REDISMODULE_GET_API(PoolAlloc);
@@ -1095,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);
+78 -35
View File
@@ -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 {
@@ -892,17 +894,34 @@ void syncCommand(client *c) {
}
/* REPLCONF <option> <value> <option> <value> ...
* This command is used by a slave in order to configure the replication
* This command is used by a replica in order to configure the replication
* process before starting it with the SYNC command.
* This command is also used by a master in order to get the replication
* offset from a replica.
*
* Currently the only use of this command is to communicate to the master
* what is the listening port of the Slave redis instance, so that the
* master can accurately list slaves and their listening ports in
* the INFO output.
* Currently we support these options:
*
* In the future the same command can be used in order to configure
* the replication to initiate an incremental replication instead of a
* full resync. */
* - listening-port <port>
* - ip-address <ip>
* What is the listening ip and port of the Replica redis instance, so that
* the master can accurately lists replicas and their listening ports in the
* INFO output.
*
* - capa <eof|psync2>
* What is the capabilities of this instance.
* eof: supports EOF-style RDB transfer for diskless replication.
* psync2: supports PSYNC v2, so understands +CONTINUE <new repl ID>.
*
* - ack <offset>
* Replica informs the master the amount of replication stream that it
* processed so far.
*
* - getack
* Unlike other subcommands, this is used by master to get the replication
* offset from a replica.
*
* - rdb-only
* Only wants RDB snapshot without replication buffer. */
void replconfCommand(client *c) {
int j;
@@ -1136,6 +1155,8 @@ void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
if (!connHasWriteHandler(conn))
return;
connSetWriteHandler(conn, NULL);
client *slave = connGetPrivateData(conn);
slave->repl_last_partial_write = 0;
server.rdb_pipe_numconns_writing--;
/* if there are no more writes for now for this conn, or write error: */
if (server.rdb_pipe_numconns_writing == 0) {
@@ -1163,8 +1184,10 @@ void rdbPipeWriteHandler(struct connection *conn) {
} else {
slave->repldboff += nwritten;
atomicIncr(server.stat_net_output_bytes, nwritten);
if (slave->repldboff < server.rdb_pipe_bufflen)
if (slave->repldboff < server.rdb_pipe_bufflen) {
slave->repl_last_partial_write = server.unixtime;
return; /* more data to write.. */
}
}
rdbPipeWriteHandlerConnRemoved(conn);
}
@@ -1245,6 +1268,7 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
/* If we were unable to write all the data to one of the replicas,
* setup write handler (and disable pipe read handler, below) */
if (nwritten != server.rdb_pipe_bufflen) {
slave->repl_last_partial_write = server.unixtime;
server.rdb_pipe_numconns_writing++;
connSetWriteHandler(conn, rdbPipeWriteHandler);
}
@@ -1281,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;
@@ -1289,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;
}
@@ -1334,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;
}
@@ -1346,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;
}
}
@@ -1873,8 +1900,7 @@ void readSyncBulkPayload(connection *conn) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections.\n");
redisCommunicateSystemd("READY=1\n");
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections in read-write mode.\n");
}
/* Send the initial ACK immediately to put this replica in online state. */
@@ -2434,8 +2460,7 @@ void syncWithMaster(connection *conn) {
if (psync_result == PSYNC_CONTINUE) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
redisCommunicateSystemd("READY=1\n");
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections in read-write mode.\n");
}
return;
}
@@ -2683,6 +2708,9 @@ void replicationUnsetMaster(void) {
* starting from now. Otherwise the backlog will be freed after a
* failover if slaves do not connect immediately. */
server.repl_no_slaves_since = server.unixtime;
/* Reset down time so it'll be ready for when we turn into replica again. */
server.repl_down_since = 0;
/* Fire the role change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
@@ -2758,7 +2786,7 @@ void replicaofCommand(client *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
return;
/* Check if we are already attached to the specified slave */
/* Check if we are already attached to the specified master */
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
&& server.masterport == port) {
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
@@ -3375,13 +3403,28 @@ void replicationCron(void) {
while((ln = listNext(&li))) {
client *slave = ln->value;
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
if (slave->flags & CLIENT_PRE_PSYNC) continue;
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
{
serverLog(LL_WARNING, "Disconnecting timedout replica: %s",
replicationGetSlaveName(slave));
freeClient(slave);
if (slave->replstate == SLAVE_STATE_ONLINE) {
if (slave->flags & CLIENT_PRE_PSYNC)
continue;
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout) {
serverLog(LL_WARNING, "Disconnecting timedout replica (streaming sync): %s",
replicationGetSlaveName(slave));
freeClient(slave);
continue;
}
}
/* We consider disconnecting only diskless replicas because disk-based replicas aren't fed
* by the fork child so if a disk-based replica is stuck it doesn't prevent the fork child
* from terminating. */
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END && server.rdb_child_type == RDB_CHILD_TYPE_SOCKET) {
if (slave->repl_last_partial_write != 0 &&
(server.unixtime - slave->repl_last_partial_write) > server.repl_timeout)
{
serverLog(LL_WARNING, "Disconnecting timedout replica (full sync): %s",
replicationGetSlaveName(slave));
freeClient(slave);
continue;
}
}
}
}
@@ -3546,7 +3589,7 @@ void abortFailover(const char *err) {
}
/*
* FAILOVER [TO <HOST> <IP> [FORCE]] [ABORT] [TIMEOUT <timeout>]
* FAILOVER [TO <HOST> <PORT> [FORCE]] [ABORT] [TIMEOUT <timeout>]
*
* This command will coordinate a failover between the master and one
* of its replicas. The happy path contains the following steps:
@@ -3649,7 +3692,7 @@ void failoverCommand(client *c) {
client *replica = findReplica(host, port);
if (replica == NULL) {
addReplyError(c,"FAILOVER target HOST and IP is not "
addReplyError(c,"FAILOVER target HOST and PORT is not "
"a replica.");
return;
}
+6 -4
View File
@@ -117,7 +117,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
r->io.file.buffered >= r->io.file.autosync)
{
fflush(r->io.file.fp);
redis_fsync(fileno(r->io.file.fp));
if (redis_fsync(fileno(r->io.file.fp)) == -1) return 0;
r->io.file.buffered = 0;
}
return retval;
@@ -160,7 +160,7 @@ void rioInitWithFile(rio *r, FILE *fp) {
}
/* ------------------- Connection implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from
* We use this RIO implementation when reading an RDB file directly from
* the connection to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a connection that is, normally,
* just a socket. */
@@ -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;
}
@@ -262,7 +264,7 @@ void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
sdsclear(r->io.conn.buf);
}
/* Release the RIO tream. Optionally returns the unread buffered data
/* Release the RIO stream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */
void rioFreeConn(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
+330 -117
View File
@@ -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;
@@ -604,9 +716,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Check the ACLs. */
int acl_errpos;
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_errpos,NULL);
switch (acl_retval) {
@@ -619,7 +729,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
"at least one of the keys mentioned in the "
"command arguments");
break;
case ACL_DENIED_AUTH:
case ACL_DENIED_CHANNEL:
luaPushError(lua, "The user executing the script can't publish "
"to the channel mentioned in the command");
break;
@@ -657,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;
}
}
@@ -1041,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.
@@ -1110,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
@@ -1227,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"
@@ -1272,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;
}
@@ -1306,7 +1485,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);
}
/* ---------------------------------------------------------------------------
@@ -1387,14 +1568,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",
@@ -1402,20 +1576,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
@@ -1429,7 +1595,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);
@@ -1453,6 +1619,14 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
if (server.lua_timedout) processEventsWhileBlocked();
if (server.lua_kill) {
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
/*
* Set the hook to invoke all the time so the user
         * will not be able to catch the error with pcall and invoke
         * pcall again which will prevent the script from ever been killed
*/
lua_sethook(lua, luaMaskCountHook, LUA_MASKLINE, 0);
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
lua_error(lua);
}
@@ -1498,7 +1672,6 @@ void evalGenericCommand(client *c, int evalsha) {
server.lua_replicate_commands = server.lua_always_replicate_commands;
server.lua_multi_emitted = 0;
server.lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
server.in_eval = 1;
/* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
@@ -1536,7 +1709,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
@@ -1544,7 +1717,7 @@ void evalGenericCommand(client *c, int evalsha) {
* return an error. */
if (evalsha) {
lua_pop(lua,1); /* remove the error handler from the stack. */
addReply(c, shared.noscripterr);
addReplyErrorObject(c, shared.noscripterr);
return;
}
if (luaCreateFunction(c,lua,c->argv[1]) == NULL) {
@@ -1554,7 +1727,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));
}
@@ -1570,9 +1743,11 @@ void evalGenericCommand(client *c, int evalsha) {
*
* If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */
server.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);
@@ -1602,6 +1777,7 @@ void evalGenericCommand(client *c, int evalsha) {
if (server.masterhost && server.master)
queueClientForReprocessing(server.master);
}
server.in_eval = 0;
server.lua_caller = NULL;
server.lua_cur_script = NULL;
@@ -1678,11 +1854,12 @@ void evalGenericCommand(client *c, int evalsha) {
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
}
}
server.in_eval = 0;
}
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
@@ -1690,12 +1867,15 @@ 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
* evalGenericCommand() can be implemented without string length
* sanity check */
addReply(c, shared.noscripterr);
addReplyErrorObject(c, shared.noscripterr);
return;
}
if (!(c->flags & CLIENT_LUA_DEBUG))
@@ -2068,7 +2248,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;
@@ -2085,7 +2266,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;
@@ -2094,12 +2275,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;
@@ -2109,6 +2294,8 @@ sds *ldbReplParseCommand(int *argcp) {
return argv;
protoerr:
*err = protocol_error;
keep_reading:
sdsfreesplitres(argv,argc);
sdsfree(copy);
return NULL;
@@ -2541,6 +2728,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 */
@@ -2597,12 +2795,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
@@ -2612,6 +2815,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. */
@@ -2724,7 +2936,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) {
@@ -2754,6 +2966,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();
}
}
+118 -40
View File
@@ -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'. */
@@ -1234,9 +1271,10 @@ static sds sdsTestTemplateCallback(sds varname, void *arg) {
else return NULL;
}
int sdsTest(int argc, char **argv) {
int sdsTest(int argc, char **argv, int accurate) {
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
{
sds x = sdsnew("foo"), y;
@@ -1352,6 +1390,18 @@ int sdsTest(int argc, char **argv) {
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");
@@ -1437,6 +1487,34 @@ int sdsTest(int argc, char **argv) {
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;
+4 -2
View File
@@ -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);
@@ -277,7 +279,7 @@ void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr);
#ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]);
int sdsTest(int argc, char *argv[], int accurate);
#endif
#endif
+124 -73
View File
@@ -215,6 +215,7 @@ typedef struct sentinelRedisInstance {
/* Slave specific. */
mstime_t master_link_down_time; /* Slave replication link down time. */
int slave_priority; /* Slave priority according to its INFO output. */
int replica_announced; /* Replica announcing according to its INFO output. */
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
char *slave_master_host; /* Master host as reported by INFO */
@@ -548,14 +549,12 @@ void initSentinel(void) {
server.sentinel_config = NULL;
}
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
/* This function is for checking whether sentinel config file has been set,
* also checking whether we have write permissions. */
void sentinelCheckConfigFile(void) {
if (server.configfile == NULL) {
serverLog(LL_WARNING,
"Sentinel started without a config file. Exiting...");
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
} else if (access(server.configfile,W_OK) == -1) {
serverLog(LL_WARNING,
@@ -563,6 +562,12 @@ void sentinelIsRunning(void) {
server.configfile,strerror(errno));
exit(1);
}
}
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
/* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this
@@ -588,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;
@@ -601,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);
@@ -629,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);
}
@@ -870,6 +891,7 @@ void sentinelRunPendingScripts(void) {
sj->pid = 0;
} else if (pid == 0) {
/* Child */
tlsCleanup();
execve(sj->argv[0],sj->argv,environ);
/* If we are here an error occurred. */
_exit(2); /* Don't retry execution. */
@@ -903,7 +925,7 @@ void sentinelCollectTerminatedScripts(void) {
int statloc;
pid_t pid;
while ((pid = wait3(&statloc,WNOHANG,NULL)) > 0) {
while ((pid = waitpid(-1, &statloc, WNOHANG)) > 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
listNode *ln;
@@ -915,7 +937,7 @@ void sentinelCollectTerminatedScripts(void) {
ln = sentinelGetScriptListNodeByPid(pid);
if (ln == NULL) {
serverLog(LL_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
serverLog(LL_WARNING,"waitpid() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
continue;
}
sj = ln->value;
@@ -1290,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. */
@@ -1334,6 +1356,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
ri->auth_pass = NULL;
ri->auth_user = NULL;
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
ri->replica_announced = 1;
ri->slave_reconf_sent_time = 0;
ri->slave_master_host = NULL;
ri->slave_master_port = 0;
@@ -1417,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);
@@ -1480,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);
@@ -1490,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;
@@ -1622,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.
@@ -1635,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);
@@ -1643,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);
}
@@ -1719,19 +1743,12 @@ void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
}
}
char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
if (ri->flags & SRI_MASTER) return "master";
else if (ri->flags & SRI_SLAVE) return "slave";
else if (ri->flags & SRI_SENTINEL) return "sentinel";
else return "unknown";
}
/* This function is used in order to send commands to Redis instances: the
* commands we send from Sentinel may be renamed, a common case is a master
* with CONFIG and SLAVEOF commands renamed for security concerns. In that
* case we check the ri->renamed_command table (or if the instance is a slave,
* we check the one of the master), and map the command that we should send
* to the set of renamed commads. However, if the command was not renamed,
* to the set of renamed commands. However, if the command was not renamed,
* we just return "command" itself. */
char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {
sds sc = sdsnew(command);
@@ -2045,12 +2062,12 @@ const char *sentinelHandleConfiguration(char **argv, int argc) {
} else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) {
/* resolve-hostnames <yes|no> */
if ((sentinel.resolve_hostnames = yesnotoi(argv[1])) == -1) {
return "Please specify yes or not for the resolve-hostnames option.";
return "Please specify yes or no for the resolve-hostnames option.";
}
} else if (!strcasecmp(argv[0],"announce-hostnames") && argc == 2) {
/* announce-hostnames <yes|no> */
if ((sentinel.announce_hostnames = yesnotoi(argv[1])) == -1) {
return "Please specify yes or not for the announce-hostnames option.";
return "Please specify yes or no for the announce-hostnames option.";
}
} else {
return "Unrecognized sentinel configuration statement.";
@@ -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",
@@ -2320,8 +2337,8 @@ void sentinelFlushConfig(void) {
return;
werr:
if (fd != -1) close(fd);
serverLog(LL_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
if (fd != -1) close(fd);
}
/* ====================== hiredis connection handling ======================= */
@@ -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,9 +2442,25 @@ 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->err) anetCloexec(link->cc->c.fd);
if (!link->cc->err && server.tls_replication &&
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");
} else if (!link->cc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
instanceLinkCloseConnection(link,link->cc);
@@ -2450,8 +2487,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (!link->pc->err) anetCloexec(link->pc->c.fd);
if (!link->pc->err && server.tls_replication &&
if (link->pc && !link->pc->err) anetCloexec(link->pc->c.fd);
if (!link->pc) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to establish connection");
} else if (!link->pc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
} else if (link->pc->err) {
@@ -2624,6 +2663,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
/* slave_repl_offset:<offset> */
if (sdslen(l) >= 18 && !memcmp(l,"slave_repl_offset:",18))
ri->slave_repl_offset = strtoull(l+18,NULL,10);
/* replica_announced:<announcement> */
if (sdslen(l) >= 18 && !memcmp(l,"replica_announced:",18))
ri->replica_announced = atoi(l+18);
}
}
ri->info_refresh = mstime();
@@ -2644,8 +2687,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
"+role-change" : "-role-change",
ri, "%@ new reported role is %s",
role == SRI_MASTER ? "master" : "slave",
ri->flags & SRI_MASTER ? "master" : "slave");
role == SRI_MASTER ? "master" : "slave");
}
/* None of the following conditions are processed when in tilt mode, so
@@ -3168,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",
@@ -3291,6 +3335,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
if (ri->flags & SRI_RECONF_SENT) flags = sdscat(flags,"reconf_sent,");
if (ri->flags & SRI_RECONF_INPROG) flags = sdscat(flags,"reconf_inprog,");
if (ri->flags & SRI_RECONF_DONE) flags = sdscat(flags,"reconf_done,");
if (ri->flags & SRI_FORCE_FAILOVER) flags = sdscat(flags,"force_failover,");
if (ri->flags & SRI_SCRIPT_KILL_SENT) flags = sdscat(flags,"script_kill_sent,");
if (sdslen(flags) != 0) sdsrange(flags,0,-2); /* remove last "," */
addReplyBulkCString(c,flags);
@@ -3343,7 +3389,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
/* Masters and Slaves */
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
addReplyBulkCString(c,"info-refresh");
addReplyBulkLongLong(c,mstime() - ri->info_refresh);
addReplyBulkLongLong(c,
ri->info_refresh ? (mstime() - ri->info_refresh) : 0);
fields++;
addReplyBulkCString(c,"role-reported");
@@ -3423,6 +3470,10 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
addReplyBulkCString(c,"slave-repl-offset");
addReplyBulkLongLong(c,ri->slave_repl_offset);
fields++;
addReplyBulkCString(c,"replica-announced");
addReplyBulkLongLong(c,ri->replica_announced);
fields++;
}
/* Only sentinels */
@@ -3448,15 +3499,20 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
void addReplyDictOfRedisInstances(client *c, dict *instances) {
dictIterator *di;
dictEntry *de;
long slaves = 0;
void *replylen = addReplyDeferredLen(c);
di = dictGetIterator(instances);
addReplyArrayLen(c,dictSize(instances));
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
/* don't announce unannounced replicas */
if (ri->flags & SRI_SLAVE && !ri->replica_announced) continue;
addReplySentinelRedisInstance(c,ri);
slaves++;
}
dictReleaseIterator(di);
setDeferredArrayLen(c, replylen, slaves);
}
/* Lookup the named master into sentinel.masters.
@@ -3663,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'",
@@ -3710,17 +3766,7 @@ NULL
ri = createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
c->argv[3]->ptr,port,quorum,NULL);
if (ri == NULL) {
switch(errno) {
case EBUSY:
addReplyError(c,"Duplicated master name");
break;
case EINVAL:
addReplyError(c,"Invalid port number");
break;
default:
addReplyError(c,"Unspecified error adding the instance");
break;
}
addReplyError(c,sentinelCheckCreateInstanceErrors(SRI_MASTER));
} else {
sentinelFlushConfig();
sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
@@ -3766,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;
@@ -3820,7 +3865,8 @@ NULL
addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
addReplyArrayLen(c,2);
addReplyLongLong(c, now - ri->info_refresh);
addReplyLongLong(c,
ri->info_refresh ? (now - ri->info_refresh) : 0);
if (ri->info)
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
else
@@ -3832,7 +3878,8 @@ NULL
while ((sde = dictNext(sdi)) != NULL) {
sentinelRedisInstance *sri = dictGetVal(sde);
addReplyArrayLen(c,2);
addReplyLongLong(c, now - sri->info_refresh);
addReplyLongLong(c,
ri->info_refresh ? (now - sri->info_refresh) : 0);
if (sri->info)
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
else
@@ -3978,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;
@@ -3988,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> */
@@ -4062,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;
@@ -4108,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;
}
}
@@ -4689,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;
+520 -201
View File
File diff suppressed because it is too large Load Diff
+100 -42
View File
@@ -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)
@@ -130,6 +137,11 @@ typedef long long ustime_t; /* microsecond time type. */
* special code. */
#define SERVER_CHILD_NOERROR_RETVAL 255
/* Reading copy-on-write info is sometimes expensive and may slow down child
* processes that report it continuously. We measure the cost of obtaining it
* and hold back additional reading based on this factor. */
#define CHILD_COW_DUTY_CYCLE 100
/* Instantaneous metrics tracking. */
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
@@ -138,7 +150,6 @@ typedef long long ustime_t; /* microsecond time type. */
#define STATS_METRIC_COUNT 3
/* Protocol and I/O related defines */
#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
#define PROTO_IOBUF_LEN (1024*16) /* Generic I/O buffer size */
#define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
#define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
@@ -275,6 +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_PUSHING (1ULL<<43) /* This client is pushing notifications. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
@@ -477,7 +489,8 @@ typedef enum {
#define NOTIFY_STREAM (1<<10) /* t */
#define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
#define NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
#define NOTIFY_MODULE (1<<13) /* d, module key space notification */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE) /* A flag */
/* Get the first bind addr or NULL */
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
@@ -736,8 +749,6 @@ typedef struct multiState {
int cmd_inv_flags; /* Same as cmd_flags, OR-ing the ~flags. so that it
is possible to know if all the commands have a
certain flag. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
/* This structure holds the blocking operation state for a client.
@@ -763,7 +774,6 @@ typedef struct blockingState {
size_t xread_count; /* XREAD COUNT option. */
robj *xread_group; /* XREADGROUP group name. */
robj *xread_consumer; /* XREADGROUP consumer name. */
mstime_t xread_retry_time, xread_retry_ttl;
int xread_group_noack;
/* BLOCKED_WAIT */
@@ -842,7 +852,7 @@ typedef struct {
the flag ALLKEYS is set in the user. */
list *channels; /* A list of allowed Pub/Sub channel patterns. If this
field is NULL the user cannot mention any channel in a
`PUBLISH` or [P][UNSUSBSCRIBE] command, unless the flag
`PUBLISH` or [P][UNSUBSCRIBE] command, unless the flag
ALLCHANNELS is set in the user. */
} user;
@@ -880,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. */
@@ -898,6 +909,7 @@ typedef struct client {
long long reploff; /* Applied replication offset if this is a master. */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
copying this slave output buffer
should use. */
@@ -982,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;
};
@@ -1105,6 +1119,11 @@ struct malloc_stats {
size_t allocator_resident;
};
typedef struct socketFds {
int fd[CONFIG_BINDADDR_MAX];
int count;
} socketFds;
/*-----------------------------------------------------------------------------
* TLS Context Configuration
*----------------------------------------------------------------------------*/
@@ -1112,8 +1131,10 @@ struct malloc_stats {
typedef struct redisTLSContextConfig {
char *cert_file; /* Server side and optionally client side cert file name */
char *key_file; /* Private key filename for cert_file */
char *key_file_pass; /* Optional password for key_file */
char *client_cert_file; /* Certificate to use as a client; if none, use cert_file */
char *client_key_file; /* Private key filename for client_cert_file */
char *client_key_file_pass; /* Optional password for client_key_file */
char *dh_params_file;
char *ca_cert_file;
char *ca_cert_dir;
@@ -1196,6 +1217,7 @@ struct redisServer {
to be processed. */
pid_t child_pid; /* PID of current child */
int child_type; /* Type of current child */
client *module_client; /* "Fake" client to call Redis from modules */
/* Networking */
int port; /* TCP listening port */
int tls_port; /* TLS listening port */
@@ -1204,13 +1226,10 @@ struct redisServer {
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
char *unixsocket; /* UNIX socket path */
mode_t unixsocketperm; /* UNIX socket permission */
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
int tlsfd_count; /* Used slots in tlsfd[] */
socketFds ipfd; /* TCP socket file descriptors */
socketFds tlsfd; /* TLS socket file descriptors */
int sofd; /* Unix socket file descriptor */
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */
socketFds cfd; /* Cluster bus listening socket */
list *clients; /* List of active clients */
list *clients_to_close; /* Clients to close asynchronously */
list *clients_pending_write; /* There is to write or install handler. */
@@ -1219,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 */
@@ -1279,6 +1299,7 @@ struct redisServer {
redisAtomic long long stat_net_input_bytes; /* Bytes read from network. */
redisAtomic long long stat_net_output_bytes; /* Bytes written to network. */
size_t stat_current_cow_bytes; /* Copy on write bytes while child is active. */
monotime stat_current_cow_updated; /* Last update time of stat_current_cow_bytes */
size_t stat_current_save_keys_processed; /* Processed keys while child is active. */
size_t stat_current_save_keys_total; /* Number of keys when child started. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
@@ -1325,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) */
@@ -1351,9 +1373,11 @@ struct redisServer {
int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */
int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */
int aof_last_write_status; /* C_OK or C_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
int aof_last_write_errno; /* Valid if aof write/fsync status is ERR */
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
int aof_use_rdb_preamble; /* Use RDB preamble on AOF rewrites. */
redisAtomic int aof_bio_fsync_status; /* Status of AOF fsync in bio job. */
redisAtomic int aof_bio_fsync_errno; /* Errno of AOF fsync in bio job. */
/* AOF pipes used to communicate between parent and child during rewrite. */
int aof_pipe_write_data_to_child;
int aof_pipe_read_data_from_parent;
@@ -1401,6 +1425,7 @@ struct redisServer {
int child_info_nread; /* Num of bytes of the last read from pipe */
/* Propagation of commands in AOF / replication */
redisOpArray also_propagate; /* Additional command to propagate. */
int replication_allowed; /* Are we allowed to replicate? */
/* Logging */
char *logfile; /* Path of log file */
int syslog_enabled; /* Is syslog enabled? */
@@ -1459,6 +1484,7 @@ struct redisServer {
time_t repl_down_since; /* Unix time at which link with master went down */
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
int slave_priority; /* Reported in INFO and used by Sentinel. */
int replica_announced; /* If true, replica is announced by Sentinel */
int slave_announce_port; /* Give the master this listening port. */
char *slave_announce_ip; /* Give the master this ip address. */
/* The following two fields is where we store master PSYNC replid/offset
@@ -1495,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;
@@ -1532,6 +1559,7 @@ struct redisServer {
char *cluster_configfile; /* Cluster auto-generated config file name. */
struct clusterState *cluster; /* State of the cluster */
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
int cluster_allow_replica_migration; /* Automatic replica migrations to orphaned masters and from empty masters */
int cluster_slave_validity_factor; /* Slave max data age for failover. */
int cluster_require_full_coverage; /* If true, put the cluster down if
there is at least an uncovered slot.*/
@@ -1539,6 +1567,7 @@ struct redisServer {
if the master is in failure state. */
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
int cluster_announce_port; /* base port to announce on cluster bus. */
int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
int cluster_module_flags; /* Set of flags that Redis modules are able
to set in order to suppress certain
@@ -1555,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
@@ -1583,7 +1613,7 @@ struct redisServer {
sds requirepass; /* Remember the cleartext password set with
the old "requirepass" directive for
backward compatibility with Redis <= 5. */
int acl_pubusub_default; /* Default ACL pub/sub channels flag */
int acl_pubsub_default; /* Default ACL pub/sub channels flag */
/* Assert & bug reporting */
int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* System hardware info */
@@ -1742,6 +1772,7 @@ void moduleLoadFromQueue(void);
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
moduleType *moduleTypeLookupModuleByID(uint64_t id);
void moduleTypeNameByID(char *name, uint64_t moduleid);
const char *moduleTypeModuleName(moduleType *mt);
void moduleFreeContext(struct RedisModuleCtx *ctx);
void unblockClientFromModule(client *c);
void moduleHandleBlockedClients(void);
@@ -1762,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);
@@ -1777,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);
@@ -1823,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);
@@ -1834,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);
@@ -1847,15 +1881,16 @@ 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);
void flushSlavesOutputBuffers(void);
void disconnectSlaves(void);
int listenToPort(int port, int *fds, int *count);
int listenToPort(int port, socketFds *fds);
void pauseClients(mstime_t duration, pause_type type);
void unpauseClients(void);
int areClientsPaused(void);
@@ -1877,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, ...)
@@ -1892,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);
@@ -1926,13 +1965,15 @@ 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);
void execCommandAbort(client *c, sds error);
void execCommandPropagateMulti(int dbid);
void execCommandPropagateExec(int dbid);
void beforePropagateMultiOrExec(int multi);
void beforePropagateMulti();
void afterPropagateExec();
/* Redis object implementation */
void decrRefCount(robj *o);
@@ -1949,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);
@@ -2081,7 +2124,7 @@ int isMutuallyExclusiveChildType(int type);
extern rax *Users;
extern user *DefaultUser;
void ACLInit(void);
/* Return values for ACLCheckCommandPerm() and ACLCheckPubsubPerm(). */
/* Return values for ACLCheckAllPerm(). */
#define ACL_OK 0
#define ACL_DENIED_CMD 1
#define ACL_DENIED_KEY 2
@@ -2092,8 +2135,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname);
void ACLClearCommandID(void);
user *ACLGetUserByName(const char *name, size_t namelen);
int ACLCheckCommandPerm(client *c, int *keyidxptr);
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr);
int ACLCheckAllPerm(client *c, int *idxptr);
int ACLSetUser(user *u, const char *op, ssize_t oplen);
sds ACLDefaultUserFirstPassword(void);
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
@@ -2106,25 +2148,23 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd);
user *ACLCreateUnlinkedUser();
void ACLFreeUserAndKillClients(user *u);
void addACLLogEntry(client *c, int reason, int keypos, sds username);
void ACLUpdateDefaultUserPassword(sds password);
/* Sorted sets data type */
/* Input flags. */
#define ZADD_NONE 0
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
#define ZADD_XX (1<<2) /* Only touch elements already existing. */
#define ZADD_GT (1<<7) /* Only update existing when new scores are higher. */
#define ZADD_LT (1<<8) /* Only update existing when new scores are lower. */
#define ZADD_IN_NONE 0
#define ZADD_IN_INCR (1<<0) /* Increment the score instead of setting it. */
#define ZADD_IN_NX (1<<1) /* Don't touch elements not already existing. */
#define ZADD_IN_XX (1<<2) /* Only touch elements already existing. */
#define ZADD_IN_GT (1<<3) /* Only update existing when new scores are higher. */
#define ZADD_IN_LT (1<<4) /* Only update existing when new scores are lower. */
/* Output flags. */
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/
#define ZADD_NAN (1<<4) /* Only touch elements already existing. */
#define ZADD_ADDED (1<<5) /* The element was new and was added. */
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */
/* Flags only used by the ZADD command but not by zsetAdd() API: */
#define ZADD_CH (1<<16) /* Return num of elements added or updated. */
#define ZADD_OUT_NOP (1<<0) /* Operation not performed because of conditionals.*/
#define ZADD_OUT_NAN (1<<1) /* Only touch elements already existing. */
#define ZADD_OUT_ADDED (1<<2) /* The element was new and was added. */
#define ZADD_OUT_UPDATED (1<<3) /* The element already existed, score updated. */
/* Struct to hold an inclusive/exclusive range spec by score comparison. */
typedef struct {
@@ -2152,10 +2192,10 @@ 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 *flags, double *newscore);
int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
long zsetRank(robj *zobj, sds ele, int reverse);
int zsetDel(robj *zobj, sds ele);
robj *zsetDup(robj *o);
@@ -2183,6 +2223,9 @@ int processCommand(client *c);
int processPendingCommandsAndResetClient(client *c);
void setupSignalHandlers(void);
void removeSignalHandlers(void);
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler);
int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler);
int changeBindAddr(sds *addrlist, int addrlist_len);
struct redisCommand *lookupCommand(sds name);
struct redisCommand *lookupCommandByCString(const char *s);
struct redisCommand *lookupCommandOrOriginal(sds name);
@@ -2195,12 +2238,15 @@ void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(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, ...)
void _serverLog(int level, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
void serverLog(int level, const char *fmt, ...);
void _serverLog(int level, const char *fmt, ...);
#endif
void serverLogRaw(int level, const char *msg);
void serverLogFromHandler(int level, const char *msg);
@@ -2296,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);
@@ -2310,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
@@ -2389,6 +2438,7 @@ const char *sentinelHandleConfiguration(char **argv, int argc);
void queueSentinelConfig(sds *argv, int argc, int linenum, sds line);
void loadSentinelConfigFromQueue(void);
void sentinelIsRunning(void);
void sentinelCheckConfigFile(void);
/* redis-check-rdb & aof */
int redis_check_rdb(char *rdbfilename, FILE *fp);
@@ -2706,8 +2756,16 @@ void killIOThreads(void);
void killThreads(void);
void makeThreadKillable(void);
/* Use macro for checking log level to avoid evaluating arguments in cases log
* should be ignored due to low level. */
#define serverLog(level, ...) do {\
if (((level)&0xff) < server.verbosity) break;\
_serverLog(level, __VA_ARGS__);\
} while(0)
/* TLS stuff */
void tlsInit(void);
void tlsCleanup(void);
int tlsConfigure(redisTLSContextConfig *ctx_config);
#define redisDebug(fmt, ...) \
+1 -1
View File
@@ -232,7 +232,7 @@ void spt_init(int argc, char *argv[]) {
if (!(SPT.arg0 = strdup(argv[0])))
goto syerr;
#if __GLIBC__
#if __linux__
if (!(tmp = strdup(program_invocation_name)))
goto syerr;
+2 -1
View File
@@ -201,7 +201,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
#define BUFSIZE 4096
#define UNUSED(x) (void)(x)
int sha1Test(int argc, char **argv)
int sha1Test(int argc, char **argv, int accurate)
{
SHA1_CTX ctx;
unsigned char hash[20], buf[BUFSIZE];
@@ -209,6 +209,7 @@ int sha1Test(int argc, char **argv)
UNUSED(argc);
UNUSED(argv);
UNUSED(accurate);
for(i=0;i<BUFSIZE;i++)
buf[i] = i;
+1 -1
View File
@@ -19,6 +19,6 @@ void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
#ifdef REDIS_TEST
int sha1Test(int argc, char **argv);
int sha1Test(int argc, char **argv, int accurate);
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More