Compare commits

...
82 Commits
Author SHA1 Message Date
033abd6f57 Async IO threads (#13665)
## Introduction
Redis introduced IO Thread in 6.0, allowing IO threads to handle client
request reading, command parsing and reply writing, thereby improving
performance. The current IO thread implementation has a few drawbacks.
- The main thread is blocked during IO thread read/write operations and
must wait for all IO threads to complete their current tasks before it
can continue execution. In other words, the entire process is
synchronous. This prevents the efficient utilization of multi-core CPUs
for parallel processing.

- When the number of clients and requests increases moderately, it
causes all IO threads to reach full CPU utilization due to the busy wait
mechanism used by the IO threads. This makes it challenging for us to
determine which part of Redis has reached its bottleneck.

- When IO threads are enabled with TLS and io-threads-do-reads, a
disconnection of a connection with pending data may result in it being
assigned to multiple IO threads simultaneously. This can cause race
conditions and trigger assertion failures. Related issue:
https://github.com/redis/redis/issues/12540

Therefore, we designed an asynchronous IO threads solution. The IO
threads adopt an event-driven model, with the main thread dedicated to
command processing, meanwhile, the IO threads handle client read and
write operations in parallel.

## Implementation
### Overall
As before, we did not change the fact that all client commands must be
executed on the main thread, because Redis was originally designed to be
single-threaded, and processing commands in a multi-threaded manner
would inevitably introduce numerous race and synchronization issues. But
now each IO thread has independent event loop, therefore, IO threads can
use a multiplexing approach to handle client read and write operations,
eliminating the CPU overhead caused by busy-waiting.

the execution process can be briefly described as follows:
the main thread assigns clients to IO threads after accepting
connections, IO threads will notify the main thread when clients
finish reading and parsing queries, then the main thread processes
queries from IO threads and generates replies, IO threads handle
writing reply to clients after receiving clients list from main thread,
and then continue to handle client read and write events.

### Each IO thread has independent event loop
We now assign each IO thread its own event loop. This approach
eliminates the need for the main thread to perform the costly
`epoll_wait` operation for handling connections (except for specific
ones). Instead, the main thread processes requests from the IO threads
and hands them back once completed, fully offloading read and write
events to the IO threads.

Additionally, all TLS operations, including handling pending data, have
been moved entirely to the IO threads. This resolves the issue where
io-threads-do-reads could not be used with TLS.

### Event-notified client queue
To facilitate communication between the IO threads and the main thread,
we designed an event-notified client queue. Each IO thread and the main
thread have two such queues to store clients waiting to be processed.
These queues are also integrated with the event loop to enable handling.
We use pthread_mutex to ensure the safety of queue operations, as well
as data visibility and ordering, and race conditions are minimized, as
each IO thread and the main thread operate on independent queues,
avoiding thread suspension due to lock contention. And we implemented an
event notifier based on `eventfd` or `pipe` to support event-driven
handling.

### Thread safety
Since the main thread and IO threads can execute in parallel, we must
handle data race issues carefully.

**client->flags**
The primary tasks of IO threads are reading and writing, i.e.
`readQueryFromClient` and `writeToClient`. However, IO threads and the
main thread may concurrently modify or access `client->flags`, leading
to potential race conditions. To address this, we introduced an io-flags
variable to record operations performed by IO threads, thereby avoiding
race conditions on `client->flags`.

**Pause IO thread**
In the main thread, we may want to operate data of IO threads, maybe
uninstall event handler, access or operate query/output buffer or resize
event loop, we need a clean and safe context to do that. We pause IO
thread in `IOThreadBeforeSleep`, do some jobs and then resume it. To
avoid thread suspended, we use busy waiting to confirm the target
status. Besides we use atomic variable to make sure memory visibility
and ordering. We introduce these functions to pause/resume IO Threads as
below.
```
pauseIOThread, resumeIOThread
pauseAllIOThreads, resumeAllIOThreads
pauseIOThreadsRange, resumeIOThreadsRange
```
Testing has shown that `pauseIOThread` is highly efficient, allowing the
main thread to execute nearly 200,000 operations per second during
stress tests. Similarly, `pauseAllIOThreads` with 8 IO threads can
handle up to nearly 56,000 operations per second. But operations
performed between pausing and resuming IO threads must be quick;
otherwise, they could cause the IO threads to reach full CPU
utilization.

**freeClient and freeClientAsync**
The main thread may need to terminate a client currently running on an
IO thread, for example, due to ACL rule changes, reaching the output
buffer limit, or evicting a client. In such cases, we need to pause the
IO thread to safely operate on the client.

**maxclients and maxmemory-clients updating**
When adjusting `maxclients`, we need to resize the event loop for all IO
threads. Similarly, when modifying `maxmemory-clients`, we need to
traverse all clients to calculate their memory usage. To ensure safe
operations, we pause all IO threads during these adjustments.

**Client info reading**
The main thread may need to read a client’s fields to generate a
descriptive string, such as for the `CLIENT LIST` command or logging
purposes. In such cases, we need to pause the IO thread handling that
client. If information for all clients needs to be displayed, all IO
threads must be paused.

**Tracking redirect**
Redis supports the tracking feature and can even send invalidation
messages to a connection with a specified ID. But the target client may
be running on IO thread, directly manipulating the client’s output
buffer is not thread-safe, and the IO thread may not be aware that the
client requires a response. In such cases, we pause the IO thread
handling the client, modify the output buffer, and install a write event
handler to ensure proper handling.

**clientsCron**
In the `clientsCron` function, the main thread needs to traverse all
clients to perform operations such as timeout checks, verifying whether
they have reached the soft output buffer limit, resizing the
output/query buffer, or updating memory usage. To safely operate on a
client, the IO thread handling that client must be paused.
If we were to pause the IO thread for each client individually, the
efficiency would be very low. Conversely, pausing all IO threads
simultaneously would be costly, especially when there are many IO
threads, as clientsCron is invoked relatively frequently.
To address this, we adopted a batched approach for pausing IO threads.
At most, 8 IO threads are paused at a time. The operations mentioned
above are only performed on clients running in the paused IO threads,
significantly reducing overhead while maintaining safety.

### Observability
In the current design, the main thread always assigns clients to the IO
thread with the least clients. To clearly observe the number of clients
handled by each IO thread, we added the new section in INFO output. The
`INFO THREADS` section can show the client count for each IO thread.
```
# Threads
io_thread_0:clients=0
io_thread_1:clients=2
io_thread_2:clients=2
```

Additionally, in the `CLIENT LIST` output, we also added a field to
indicate the thread to which each client is assigned.

`id=244 addr=127.0.0.1:41870 laddr=127.0.0.1:6379 ... resp=2 lib-name=
lib-ver= io-thread=1`

## Trade-off
### Special Clients
For certain special types of clients, keeping them running on IO threads
would result in severe race issues that are difficult to resolve.
Therefore, we chose not to offload these clients to the IO threads.

For replica, monitor, subscribe, and tracking clients, main thread may
directly write them a reply when conditions are met. Race issues are
difficult to resolve, so we have them processed in the main thread. This
includes the Lua debug clients as well, since we may operate connection
directly.

For blocking client, after the IO thread reads and parses a command and
hands it over to the main thread, if the client is identified as a
blocking type, it will be remained in the main thread. Once the blocking
operation completes and the reply is generated, the client is
transferred back to the IO thread to send the reply and wait for event
triggers.

### Clients Eviction
To support client eviction, it is necessary to update each client’s
memory usage promptly during operations such as read, write, or command
execution. However, when a client operates on an IO thread, it is not
feasible to update the memory usage immediately due to the risk of data
races. As a result, memory usage can only be updated either in the main
thread while processing commands or in the `ClientsCron` periodically.
The downside of this approach is that updates might experience a delay
of up to one second, which could impact the precision of memory
management for eviction.

To avoid incorrectly evicting clients. We adopted a best-effort
compensation solution, when we decide to eviction a client, we update
its memory usage again before evicting, if the memory used by the client
does not decrease or memory usage bucket is not changed, then we will
evict it, otherwise, not evict it.

However, we have not completely solved this problem. Due to the delay in
memory usage updates, it may lead us to make incorrect decisions about
the need to evict clients.

### Defragment
In the majority of cases we do NOT use the data from argv directly in
the db.
1. key names
We store a copy that we allocate in the main thread, see `sdsdup()` in
`dbAdd()`.
2. hash key and value
We store key as hfield and store value as sds, see `hfieldNew()` and
`sdsdup()` in `hashTypeSet()`.
3. other datatypes
   They don't even use SDS, so there is no reference issues.

But in some cases client the data from argv may be retain by the main
thread.
As a result, during fragmentation cleanup, we need to move allocations
from the IO thread’s arena to the main thread’s arena. We always
allocate new memory in the main thread’s arena, but the memory released
by IO threads may not yet have been reclaimed. This ultimately causes
the fragmentation rate to be higher compared to creating and allocating
entirely within a single thread.
The following cases below will lead to memory allocated by the IO thread
being kept by the main thread.
1. string related command: `append`, `getset`, `mset` and `set`.
If `tryObjectEncoding()` does not change argv, we will keep it directly
in the main thread, see the code in `tryObjectEncoding()`(specifically
`trimStringObjectIfNeeded()`)
2. block related command.
    the key names will be kept in `c->db->blocking_keys`.
3. watch command
    the key names will be kept in `c->db->watched_keys`.
4. [s]subscribe command
    channel name will be kept in `serverPubSubChannels`.
5. script load command
    script will be kept in `server.lua_scripts`.
7. some module API: `RM_RetainString`, `RM_HoldString`

Those issues will be handled in other PRs.

## Testing
### Functional Testing
The commit with enabling IO Threads has passed all TCL tests, but we did
some changes:
**Client query buffer**: In the original code, when using a reusable
query buffer, ownership of the query buffer would be released after the
command was processed. However, with IO threads enabled, the client
transitions from an IO thread to the main thread for processing. This
causes the ownership release to occur earlier than the command
execution. As a result, when IO threads are enabled, the client's
information will never indicate that a shared query buffer is in use.
Therefore, we skip the corresponding query buffer tests in this case.
**Defragment**: Add a new defragmentation test to verify the effect of
io threads on defragmentation.
**Command delay**: For deferred clients in TCL tests, due to clients
being assigned to different threads for execution, delays may occur. To
address this, we introduced conditional waiting: the process proceeds to
the next step only when the `client list` contains the corresponding
commands.

### Sanitizer Testing
The commit passed all TCL tests and reported no errors when compiled
with the `fsanitizer=thread` and `fsanitizer=address` options enabled.
But we made the following modifications: we suppressed the sanitizer
warnings for clients with watched keys when updating `client->flags`, we
think IO threads read `client->flags`, but never modify it or read the
`CLIENT_DIRTY_CAS` bit, main thread just only modifies this bit, so
there is no actual data race.

## Others
### IO thread number
In the new multi-threaded design, the main thread is primarily focused
on command processing to improve performance. Typically, the main thread
does not handle regular client I/O operations but is responsible for
clients such as replication and tracking clients. To avoid breaking
changes, we still consider the main thread as the first IO thread.

When the io-threads configuration is set to a low value (e.g., 2),
performance does not show a significant improvement compared to a
single-threaded setup for simple commands (such as SET or GET), as the
main thread does not consume much CPU for these simple operations. This
results in underutilized multi-core capacity. However, for more complex
commands, having a low number of IO threads may still be beneficial.
Therefore, it’s important to adjust the `io-threads` based on your own
performance tests.

Additionally, you can clearly monitor the CPU utilization of the main
thread and IO threads using `top -H -p $redis_pid`. This allows you to
easily identify where the bottleneck is. If the IO thread is the
bottleneck, increasing the `io-threads` will improve performance. If the
main thread is the bottleneck, the overall performance can only be
scaled by increasing the number of shards or replicas.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: oranagra <oran@redislabs.com>
2024-12-22 19:30:37 +08:00
779af3ab92 Dynamic event loop binding for connection structure (#13642)
The IO thread has an independent event loop, so we can no longer
hard-code the event loop to the connection, instead, we should
dynamically select the event loop for the connection.

- configure the event loop during connection creation.
- add a new interface to allow dynamic event loop binding.

For TLS connection, we need to check for any pending data on the
connection and handle it accordingly when changing connection cross IO
thread and main thread. This commit doesn't handle it, @sundb will
overall support for TLS connection later.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-11-08 15:58:10 +08:00
guybe7andGitHub ded8d993b7 Modules: defrag CB should take robj, not sds (#13627)
Added a log of the keyname in the test modules to reproduce the problem
(tests crash without the fix)
2024-10-30 17:32:51 +08:00
Moti CohenandGitHub 6437d07b03 Fix memory leak on rdbload error (#13626)
On RDB load error, if an invalid `expireAt` value is read,
`dupSearchDict` is not released.
2024-10-30 10:03:31 +02:00
debing.sunandGitHub 4b29be3f36 Avoid redundant lpGet to boost quicklistCompare (#11533)
`lpCompare()` in `quicklistCompare()` will call `lpGet()` again, which
would be a waste.
The change will result in a boost for all commands that use
`quicklistCompre()`, including `linsert`, `lpos` and `lrem`.
2024-10-30 08:45:25 +08:00
Moti CohenandGitHub 2ec78d262d Add KEYSIZES section to INFO (#13592)
This PR adds a new section to the `INFO` command output, called
`keysizes`. This section provides detailed statistics on the
distribution of key sizes for each data type (strings, lists, sets,
hashes and zsets) within the dataset. The distribution is tracked using
a base-2 logarithmic histogram.

# Motivation
Currently, Redis lacks a built-in feature to track key sizes and item
sizes per data type at a granular level. Understanding the distribution
of key sizes is critical for monitoring memory usage and optimizing
performance, particularly in large datasets. This enhancement will allow
users to inspect the size distribution of keys directly from the `INFO`
command, assisting with performance analysis and capacity planning.

# Changes
New Section in `INFO` Command: A new section called `keysizes` has been
added to the `INFO` command output. This section reports a per-database,
per-type histogram of key sizes. It provides insights into how many keys
fall into specific size ranges (represented in powers of 2).

**Example output:**
```
127.0.0.1:6379> INFO keysizes
# Keysizes
db0_distrib_strings_sizes:1=19,2=655,512=100899,1K=31,2K=29,4K=23,8K=16,16K=3,32K=2
db0_distrib_lists_items:1=5784492,32=3558,64=1047,128=676,256=533,512=218,4K=1,8K=42
db0_distrib_sets_items:1=735564=50612,8=21462,64=1365,128=974,2K=292,4K=154,8K=89,
db0_distrib_hashes_items:2=1,4=544,32=141169,64=207329,128=4349,256=136226,1K=1
```
## Future Use Cases:
The key size distribution is collected per slot as well, laying the
groundwork for future enhancements related to Redis Cluster.
2024-10-29 13:07:26 +02:00
Shockingly GoodandGitHub 611c950293 Fix crash in RM_GetCurrentUserName() when the user isn't accessible (#13619)
The crash happens whenever the user isn't accessible, for example, it
isn't set for the context (when it is temporary) or in some other cases
like `notifyKeyspaceEvent`. To properly check for the ACL compliance, we
need to get the user name and the user to invoke other APIs. However, it
is not possible if it crashes, and it is impossible to work that around
in the code since we don't know (and **shouldn't know**!) when it is
available and when it is not.
2024-10-28 21:26:29 +08:00
0a8e546957 Fix get # option in sort command (#13608)
From 7.4, Redis allows `GET` options in cluster mode when the pattern maps to
the same slot as the key, but GET # pattern that represents key itself is missed.
This commit resolves it, bug report #13607.

---------

Co-authored-by: Yuan Wang <yuan.wang@redis.com>
2024-10-22 09:55:00 +08:00
4f8cdc2a1e Fix compilation on compilers that do not support target attribute (#13609)
introduced by https://github.com/redis/redis/pull/13359
failure CI on ARM64:
https://github.com/redis/redis-extra-ci/actions/runs/11377893230/job/31652773710

---------

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
Co-authored-by: ShooterIT <wangyuancode@163.com>
2024-10-18 09:11:23 +08:00
3788a055fe Optimize bitcount command by using popcnt (#13359)
Nowadays popcnt instruction is almost supported by X86 machine, which is
used to calculate "Hamming weight", it can bring much performance boost
in redis bitcount comand.

---------

Signed-off-by: hanhui365(hanhui@hygon.cn)
Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: oranagra <oran@redislabs.com>
Co-authored-by: Nugine <nugine@foxmail.com>
2024-10-17 09:13:19 +08:00
Yuan WangandGitHub b71a610f5c Clean up .rediscli_history_test temporary file (#13601)
After running test in local, there will be a file named
`.rediscli_history_test`, and it is not in `.gitignore` file, so this is
considered to have changed the code base. It is a little annoying, this
commit just clean up the temporary file.

We should delete `.rediscli_history_test` in the end since the second
server tests also write somethings into it, to make it corresponding, i
put `set ::env(REDISCLI_HISTFILE) ".rediscli_history_test"` at the
beginning.

Maybe we also can add this file into `.gitignore`
2024-10-17 09:12:11 +08:00
efcfffc528 Update modules with latest version (#13606)
Update redisbloom, redisjson and redistimeseries versions to 7.99.1

Co-authored-by: YaacovHazan <yaacov.hazan@redislabs.com>
2024-10-15 19:58:42 +03:00
paoloredisandGitHub 99d09c824c Only run redis_docs_sync.yaml on latest release (#13603)
We only want to trigger the workflow on the documentation repository for
the latest release
2024-10-15 16:02:11 +03:00
6c5e263d7b Temporarily hide the new SFLUSH command by marking it as experimental (#13600)
- Add a new 'EXPERIMENTAL' command flag, which causes the command
generator to skip over it and make the command to be unavailable for
execution
- Skip experimental tests by default
- Move the SFLUSH tests from the old framework to the new one

---------

Co-authored-by: YaacovHazan <yaacov.hazan@redislabs.com>
2024-10-15 11:02:51 +03:00
debing.sunandGitHub 3fc7ef8f81 Fix race in stream-cgroups test (#13593)
failed CI:
https://github.com/redis/redis/actions/runs/11171608362/job/31056659165
https://github.com/redis/redis/actions/runs/11226025974/job/31205787575
2024-10-12 09:23:19 +08:00
guybe7andGitHub a38c29b6c8 Cleanups related to expiry/eviction (#13591)
1. `dbRandomKey`: excessive call to `dbFindExpires` (will always return
1 if `allvolatile` + anyway called inside `expireIfNeeded`
2. Add `deleteKeyAndPropagate` that is used by both expiry/eviction
3. Change the order of calls in `expireIfNeeded` to save redundant calls
to `keyIsExpired`
4. `expireIfNeeded`: move `OBJ_STATIC_REFCOUNT` to
`deleteKeyAndPropagate`
5. `performEvictions` now uses `deleteEvictedKeyAndPropagate`
6. active-expire: moved `postExecutionUnitOperations` inside
`activeExpireCycleTryExpire`
7. `activeExpireCycleTryExpire`: less indentation + expire a key if `now
== t`
8. rename `lazy_expire_disabled` to `allow_access_expired`
2024-10-10 16:58:52 +08:00
Oran AgraandYaacovHazan 472d8a0df5 Prevent pattern matching abuse (CVE-2024-31228) 2024-10-08 20:55:44 +03:00
Oran AgraandYaacovHazan 8ec5da785b Fix ACL SETUSER Read/Write key pattern selector (CVE-2024-31227)
The '%' rule must contain one or both of R/W
2024-10-08 20:55:44 +03:00
Oran AgraandYaacovHazan 3a2669e8ae Fix lua bit.tohex (CVE-2024-31449)
INT_MIN value must be explicitly checked, and cannot be negated.
2024-10-08 20:55:44 +03:00
alonre24andGitHub f39e51178e Update target module in search (#13578)
Update search target path and version from M02
2024-10-08 13:58:28 +03:00
chx9andGitHub 5f7d7ce8b0 fix typo in test_helper.tcl (#13576)
fix typo in test_helper.tcl: even driven => event driven
2024-10-08 14:15:48 +08:00
Moti CohenandGitHub d092d64d7a Add new SFLUSH command to cluster for slot-based FLUSH (#13564)
This PR introduces a new `SFLUSH` command to cluster mode that allows
partial flushing of nodes based on specified slot ranges. Current
implementation is designed to flush all slots of a shard, but future
extensions could allow for more granular flushing.

**Command Usage:**
`SFLUSH <start-slot> <end-slot> [<start-slot> <end-slot>]* [SYNC|ASYNC]`

This command removes all data from the specified slots, either
synchronously or asynchronously depending on the optional SYNC/ASYNC
argument.

**Functionality:**
Current imp of `SFLUSH` command verifies that the provided slot ranges
are valid and cover all of the node's slots before proceeding. If slots
are partially or incorrectly specified, the command will fail and return
an error, ensuring that all slots of a node must be fully covered for
the flush to proceed.

The function supports both synchronous (default) and asynchronous
flushing. In addition, if possible, SFLUSH SYNC will be run as blocking
ASYNC as an optimization.
2024-09-29 09:13:21 +03:00
99c40ab53d Use hashtable as the default type of temp set object during sunion/sdiff (#13567)
This PR is based on https://github.com/valkey-io/valkey/pull/996


Currently, for operations like SUNION or SDIFF, temporary set object can
be intset or listpack. Search operation is costly for these encodings.
This patch tries to set the temporary set object as hash table by
default. It also tries to determine correct encoding for the temporary
set object to reduce the unnecessary conversation.

This change is supposed to give performance boost for tests like:
-
[memtier_benchmark-2keys-set-10-100-elements-sdiff](https://github.com/redis/redis-benchmarks-specification/blob/main/redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sdiff.yml)
66.2% IMPROVEMENT
-
[memtier_benchmark-2keys-set-10-100-elements-sunion](https://github.com/redis/redis-benchmarks-specification/blob/main/redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sunion.yml)
126.5% IMPROVEMENT

-------
Co-authored-by: Lipeng Zhu <lipeng.zhu@intel.com>
Co-authored-by: Wangyang Guo <wangyang.guo@intel.com>

Co-authored-by: Lipeng Zhu <lipeng.zhu@intel.com>
Co-authored-by: Wangyang Guo <wangyang.guo@intel.com>
2024-09-25 12:41:17 +03:00
Moti CohenGitHubRayacoo zisong.cw@alibaba-inc.com
26ef28467a Optimize ZUNION[STORE] by avoiding redundant temporary dict usage (#13566)
This PR is based on valkey-io/valkey#829

Previously, ZUNION and ZUNIONSTORE commands used a temporary accumulator dict
and at the end copied it as-is to dstzset->dict. This PR removes accumulator and directly
stores into dstzset->dict, eliminating the extra copy.

Co-authored-by: Rayacoo zisong.cw@alibaba-inc.com
2024-09-25 11:55:00 +03:00
Moti CohenandGitHub 5f28bd96db Fix race in HFE tests (#13563)
Test 1 - give more time for expiration
Test 2 - Evaluate expiration time boundaries [+1,+2] before setting expiration [+1]
Test 3 - Avoid race on test HFEs propagated to replica
2024-09-23 10:30:29 +03:00
debing.sunandGitHub 438cfed70a Replace wrongly free with zfree in redis-cli (#13560)
#13258 Incorrect use of free instead of zfree
2024-09-23 09:40:47 +08:00
Moti CohenandGitHub 3a3cacfefa Extend modules API to read also expired keys and subkeys (#13526)
The PR extends `RedisModule_OpenKey`'s flags to include
`REDISMODULE_OPEN_KEY_ACCESS_EXPIRED`, which allows to access expired
keys.

It also allows to access expired subkeys. Currently relevant only for
hash fields
and has its impact on `RM_HashGet` and `RM_Scan`.
2024-09-19 20:47:00 +03:00
debing.sunandGitHub 617909e943 Align the offset in ASCII logo (#13557)
Since `\\` is only one character, we need to add an extra space to the right.
2024-09-18 14:42:32 +08:00
adamiBsandGitHub e9cbfccec6 Support musl Rust Installation in Modules Makefile (#13549)
This PR introduces the installation of the `musl`-based version of Rust,
in order to support alpine-based runtime environments (Rust is used by
[RedisJSON](https://github.com/RedisJSON/RedisJSON)).
2024-09-15 20:23:05 +03:00
7b69183a8d Replace usage of _addReplyLongLongWithPrefix with specific bulk/mbulk functions to reduce condition checks in hotpath. (#13520)
Instead of adding runtime logic to decide which prefix/shared object to
use when doing the reply we can simply use an inline method to avoid runtime
overhead of condition checks, and also keep the code change small.
Preliminary data show improvements on commands that heavily rely on
bulk/mbulk replies (example of LRANGE).

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-15 21:40:09 +08:00
af7fca797a Using fast_float library for faster parsing of 64 decimal strings. (#11884)
Fixes #8825 

We're using the fast_float library[1] in our (compiled-in)
floating-point fast_float_strtod implementation for faster and more
portable parsing of 64 decimal strings.

The single file fast_float.h is an amalgamation of the entire library,
which can be (re)generated with the amalgamate.py script (from the
fast_float repository) via the command:

```
python3 ./script/amalgamate.py --license=MIT > $REDIS_SRC/deps/fast_float/fast_float.h
```

[1]: https://github.com/fastfloat/fast_float

The used commit from fast_float library was the one from
https://github.com/fastfloat/fast_float/releases/tag/v3.10.1

---------

Co-authored-by: fcostaoliveira <filipe@redis.com>
2024-09-15 21:37:29 +08:00
9146ac050b Optimize HSCAN/ZSCAN command in case of listpack encoding: avoid the usage of intermediate list (#13531)
Similar to #13530 , applied to HSCAN and ZSCAN in case of listpack
encoding.

**Preliminary benchmark results showcase an improvement of 108% on the
achievable ops/sec for ZSCAN and 65% for HSCAN**.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-13 20:36:19 +08:00
debing.sunandGitHub ef3a5f58a8 Fix missing initialization of EbucketsIterator->isRax (#13545)
in https://github.com/redis/redis/pull/13519, when `eb` is empty,
`isRax` is not correctly initialized to 0, which can lead to `ebStop()`
potentially entering the wrong rax branch.
2024-09-13 17:12:27 +08:00
Filipe Oliveira (Redis)andGitHub f2f85ba354 Optimize SSCAN command in case of listpack or intset encoding: avoid the usage of intermediate list. From 2N to N iterations (#13530)
On SSCAN, in case of listpack and intset encoding we actually reply the
entire set, and always reply with the cursor 0.

For those cases, we don't need to accumulate the replies in a list and
can completely avoid the overhead of list appending and then iterating
over the list again -- meaning we do N iterations instead of 2N
iterations over the SET and save intermediate memory as well.

Preliminary benchmarks, `SSCAN set:100 0`, showcased an improvement of
60% as visible bellow on a SET with 100 string elements (listpack
encoded).
2024-09-12 22:36:54 +08:00
Moti CohenandGitHub c115c5230e Add iterator capability to ebuckets DS (#13519)
Add basic iterator API for ebuckets of start, next, nextBucket and stop.
2024-09-12 15:02:32 +03:00
Moti CohenandGitHub 65a87cb773 Correct spelling error at t_hash.c comment (#13540)
spell check error : ./src/t_hash.c:1141: RESOTRE ==> RESTORE
2024-09-12 12:50:44 +03:00
Moti CohenandGitHub 9a89e32a95 HFE - Fix key ref by the hash on RENAME/MOVE/SWAPDB/RESTORE (#13539)
If the hash previously had HFEs (hash-fields with expiration) but later no longer
does, the key ref in the hash might become outdated after a MOVE, COPY,
RENAME or RESTORE operation. These commands maintain the key ref only
if HFEs are present. That is, we can only be sure that key ref is valid as long as the
hash has HFEs.
2024-09-12 12:40:12 +03:00
610eb26c11 RED-129256, Fix TOUCH command from script in no-touch mode (#13512)
When a client in no-touch mode issues a TOUCH command on a key, the
key’s access time should be updated, but in scripts, and module's
RM_Call, it isn’t updated.
Command proc should be matched to the executing client, not the current
client.

Co-authored-by: Udi Ron <udi@speedb.io>
2024-09-12 11:33:26 +03:00
d265a61438 Avoid cluster.nodes load corruption due to shard-id generation (#13468)
PR #13428 doesn't fully resolve an issue where corruption errors can
still occur on loading of cluster.nodes file - seen on upgrade where
there were no shard_ids (from old Redis), 7.2.5 loading generated new
random ones, and persisted them to the file before gossip/handshake
could propagate the correct ones (or some other nodes unreachable).
This results in a primary/replica having differing shard_id in the
cluster.nodes and then the server cannot startup - reports corruption.

This PR builds on #13428 by simply ignoring the replica's shard_id in
cluster.nodes (if it exists), and uses the replica's primary's shard_id.
Additional handling was necessary to cover the case where the replica
appears before the primary in cluster.nodes, where it will first use a
generated shard_id for the primary, and then correct after it loads the
primary cluster.nodes entry.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-12 14:01:09 +08:00
2dd4cca363 Increment kvstore's non_empty_dicts only on first insert (#13528)
Found by @oranagra 

Currently, when the size of dict becomes 1, we do not check whether
`delta` is positive or negative.
As a result, `non_empty_dicts` is still incremented when the size of
dict changes from 2 to 1.
We should only increment `non_empty_dicts` when `delta` is positive, as
this indicates the first time an element is inserted into the dict.

---------

Co-authored-by: oranagra <oran@redislabs.com>
2024-09-11 09:36:01 +08:00
Filipe Oliveira (Redis)andGitHub bcae770819 Optimize LREM, LPOS, LINSERT, LINDEX: Avoid N-1 sdslen() calls on listTypeEqual (#13529)
This is a very easy optimization, that avoids duplicate computation of
the object length for LREM, LPOS, LINSERT na LINDEX.

We can see that sdslen takes 7.7% of the total CPU cycles of the
benchmarks.

Function Stack | CPU Time: Total | CPU Time: Self | Module | Function
(Full) | Source File | Start Address
-- | -- | -- | -- | -- | -- | --
listTypeEqual | 15.50% | 2.346s | redis-server | listTypeEqual |
t_list.c | 0x845dd
sdslen | 7.70% | 2.300s | redis-server | sdslen | sds.h | 0x845e4

Preliminary data showcases 4% improvement in the achievable ops/sec of
LPOS in string elements, and 2% in int elements.
2024-09-10 20:26:36 +08:00
bf802b0764 Add the option to build Redis with modules (#13524)
A new BUILD_WITH_MODULES flag was added to the Makefile to control
building the module directory.

The new module directory includes a general Makefile that iterates
over each module, fetch a specific version, and build it.

Co-authored-by: YaacovHazan <yaacov.hazan@redislabs.com>
2024-09-09 15:47:02 +03:00
Ozan TezcanandGitHub ac03e3721d Fix flaky replication tests (#13518)
#13495 introduced a change to reply -LOADING while flushing existing db on a replica. Some of our tests are
 sensitive to this change and do no expect -LOADING reply.

Fixing a couple of tests that fail time to time.
2024-09-08 12:54:01 +03:00
31227f4faf Optimize client type check on reply hot code paths (#13516)
## Proposed improvement

This PR introduces the static inlined function `clientTypeIsSlave` which
is doing only 1 condition check vs 3 checks of `getClientType`, and also
uses the `unlikely` to tell the compiler that the most common outcome is
for the client not to be a slave.
Preliminary data show 3% improvement on the achievable ops/sec on the
specific LRANGE benchmark. After running the entire suite we see up to
5% improvement in 2 tests.
https://github.com/redis/redis/pull/13516#issuecomment-2331326052

## Context

This optimization efforts comes from analyzing the profile info from the
[memtier_benchmark-1key-list-1K-elements-lrange-all-elements](https://github.com/redis/redis-benchmarks-specification/blob/main/redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements.yml)
benchmark.
 
By going over it, we can see that `getClientType` consumes 2% of the cpu
time, strictly to check if the client is a slave (
https://github.com/redis/redis/blob/unstable/src/networking.c#L397 , and
https://github.com/redis/redis/blob/unstable/src/networking.c#L1254 )


Function | CPU Time: Total | CPU Time: Self | Module | Function (Full)
-- | -- | -- | -- | --
_addReplyToBufferOrList->getClientType | 1.20% | 0.728s | redis-server |
getClientType
clientHasPendingReplies->getClientType | 0.80% | 0.482s | redis-server |
getClientType

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-06 10:24:30 +08:00
Max MalekzadehandGitHub f6f11f3ef1 Remove outdated "Try Redis" link in README.md (#13498) 2024-09-05 22:04:49 +08:00
Moti CohenandGitHub 569584d463 HFE - Simplify logic of HGETALL command (#13425) 2024-09-05 12:48:44 +03:00
ea3e8b79a1 Introduce reusable query buffer for client reads (#13488)
This PR is based on the commits from PR
https://github.com/valkey-io/valkey/pull/258,
https://github.com/valkey-io/valkey/pull/593,
https://github.com/valkey-io/valkey/pull/639

This PR optimizes client query buffer handling in Redis by introducing
a reusable query buffer that is used by default for client reads. This
reduces memory usage by ~20KB per client by avoiding allocations for
most clients using short (<16KB) complete commands. For larger or
partial commands, the client still gets its own private buffer.

The primary changes are:

* Adding a reusable query buffer `thread_shared_qb` that clients use by
default.
* Modifying client querybuf initialization and reset logic.
* Freeing idle client query buffers when empty to allow reuse of the
reusable query buffer.
* Master client query buffers are kept private as their contents need to
be preserved for replication stream.
* When nested commands is executed, only the first user uses the reuse
buffer, and subsequent users will still use the private buffer.

In addition to the memory savings, this change shows a 3% improvement in
latency and throughput when running with 1000 active clients.

The memory reduction may also help reduce the need to evict clients when
reaching max memory limit, as the query buffer is the main memory
consumer per client.

This PR is different from https://github.com/valkey-io/valkey/pull/258
1. When a client is in the mid of requiring a reused buffer and
returning it, regardless of whether the query buffer has changed
(expanded), we do not update the reused query buffer in the middle, but
return the reused query buffer (expanded or with data remaining) or
reset it at the end.
2. Adding a new thread variable `thread_shared_qb_used` to avoid
multiple clients requiring the reusable query buffer at the same time.

---------

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
Signed-off-by: Madelyn Olson <matolson@amazon.com>
Co-authored-by: Uri Yagelnik <uriy@amazon.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: oranagra <oran@redislabs.com>
2024-09-04 19:10:40 +08:00
debing.sunandGitHub 74609d44cd Fix set with invalid length causes smembers to hang (#13515)
After https://github.com/redis/redis/pull/13499, If the length set by
`addReplySetLen()` does not match the actual number of elements in the
reply, it will cause protocol broken and result in the client hanging.
2024-09-04 17:35:46 +08:00
Ozan TezcanandGitHub ea05c6ac47 Fix RM_RdbLoad() to enable AOF after loading is completed (#13510)
RM_RdbLoad() disables AOF temporarily while loading RDB.
Later, it does not enable it back as it checks AOF state (disabled by then) 
rather than AOF config parameter.

Added a change to restart AOF according to config parameter.
2024-09-04 11:11:04 +03:00
05aed4cab9 Optimize SET/INCR*/DECR*/SETRANGE/APPEND by reducing duplicate computation (#13505)
- Avoid addReplyLongLong (which converts back to string) the value we
already have as a robj, by using addReplyProto + addReply
- Avoid doing dbFind Twice for the same dictEntry on
INCR*/DECR*/SETRANGE/APPEND commands.
- Avoid multiple sdslen calls with the same input on setrangeCommand and
appendCommand
- Introduce setKeyWithDictEntry, which is like setKey(), but accepts an
optional dictEntry input: Avoids the second dictFind in SET command

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-04 14:51:21 +08:00
de7f2f87f7 Avoid overhead of comparision function pointer calls in lpFind() (#13503)
In #13279 (found by @filipecosta90), for custom lookups, we introduce a
comparison function for `lpFind()` to compare entry, but it also
introduces some overhead.

To avoid the overhead of function pointer calls:
1. Extract the lpFindCb() method into a lpFindCbInternal() method that
is easier to inline.
2. Use unlikely to annotate the comparison method, as can only success
once.

---------

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
2024-09-03 22:53:13 +08:00
Filipe Oliveira (Redis)andGitHub fb8755a636 changed addReplyHumanLongDouble to addReplyDouble in georadiusGeneric and geoposCommand (#13494)
# Summary 

- Addresses https://github.com/redis/redis/issues/11565
- Measured improvements of 30% and 37% on the simple use-case (GEOSEARCH
and GEOPOS) (check
https://github.com/redis/redis/pull/13494#issuecomment-2313668934), and
of 66% on a dataset with >60M datapoints and pipeline 10 benchmark.
2024-09-03 20:54:20 +08:00
Meir Shpilraien (Spielrein)andGitHub d3d94ccf2e Added new defrag API to allocate and free raw memory. (#13509)
All the defrag allocations API expects to get a value and replace it, leaving the old value untouchable.
In some cases a value might be shared between multiple keys, in such cases we can not simply replace
it when the defrag callback is called.

To allow support such use cases, the PR adds two new API's to the defrag API:

1. `RM_DefragAllocRaw` - allocate memory base on a given size.
2. `RM_DefragFreeRaw` - Free the given pointer.

Those API's avoid using tcache so they operate just like `RM_DefragAlloc` but allows the user to split
the allocation and the memory free operations into two stages and control when those happen.

In addition the PR adds new API to allow the module to receive notifications when defrag start and end: `RM_RegisterDefragCallbacks`
Those callbacks are the same as `RM_RegisterDefragFunc` but promised to be called and the start
and the end of the defrag process.
2024-09-03 15:03:19 +03:00
00a8e72cfc Created specific SMEMBERS command logic which avoids sinterGenericCommand, and minimizes processing and memory overhead (#13499)
This PR introduces a dedicated implementation for the SMEMBERS command
that avoids using the more generalized sinterGenericCommand function.
By tailoring the logic specifically for SMEMBERS, we reduce unnecessary
processing and memory overheads that were previously incurred by
handling more complex cases like set intersections.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-03 18:32:43 +08:00
Filipe Oliveira (Redis)andGitHub a31b516e25 Optimize the HELLO command reply (#13490)
# Overall improvement

TBD ( current is approximately 6% on the achievable ops/sec), coming
from:

- In case of no module we can skip 1.3% CPU cycles on dict Iterator
creation/deletion
- Use addReplyBulkCBuffer instead of addReplyBulkCString to avoid
runtime strlen overhead within HELLO reply on string constants.

## Optimization 1: In case of no module we can skip 1.3% CPU cycles on
dict Iterator creation/deletion.

## Optimization 2: Use addReplyBulkCBuffer instead of
addReplyBulkCString to avoid runtime strlen overhead within HELLO reply
on string constants.
2024-09-03 17:27:35 +08:00
c77b8f45e9 Fixed variable parameter formatting issues in serverPanic function (#13504)
Currently aeApiPoll panic does not record error code information. Added
variable parameter formatting to _serverPanic to fix the issue

---------

Co-authored-by: yingyin.chen <15816602944@163.com>
2024-09-03 15:51:46 +08:00
Ozan TezcanandGitHub a7afd1d2b2 Reply LOADING on replica while flushing the db (#13495)
On a full sync, replica starts discarding existing db. If the existing 
db is huge and flush is happening synchronously, replica may become 
unresponsive. 

Adding a change to yield back to event loop while flushing db on 
a replica. Replica will reply -LOADING in this case. Note that while 
replica is loading the new rdb, it may get an error and start flushing
the partial db. This step may take a long time as well. Similarly, 
replica will reply -LOADING in this case. 

To call processEventsWhileBlocked() and reply -LOADING, we need to do:
- Set connSetReadHandler() null not to process further data from the master
- Set server.loading flag
- Call blockingOperationStarts()

rdbload() already does these steps and calls processEventsWhileBlocked()
while loading the rdb. Added a new call rdbLoadWithEmptyFunc() which 
accepts callback to flush db before loading rdb or when an error 
happens while loading. 

For diskless replication, doing something similar and calling emptyData()
after setting required flags.

Additional changes:
- Allow `appendonly` config change during loading. 
 Config can be changed while loading data on startup or on replication 
 when slave is loading RDB. We allow config change command to update 
 `server.aof_enabled` and then lazily apply config change after loading
 operation is completed.
 
 - Added a test for `replica-lazy-flush` config
2024-09-03 09:48:44 +03:00
Oran AgraandGitHub 3fcddfb61f testsuite --dump-logs works on servers started before the test (#13500)
so far ./runtest --dump-logs used work for servers started within the
test proc.
now it'll also work on servers started outside the test proc scope.
the downside is that these logs can be huge if they served many tests
and not just the failing one.
but for some rare failures, we rather have that than nothing.
this feature isn't enabled y default, but is used by our GH actions.
2024-08-29 07:27:23 +01:00
CoolThiandGitHub 3c9f5954b5 Remove variable expired in expireSlaveKeys() to prevent confusing the compiler (#13299)
This change prevents missed optimization for some compilers:
https://godbolt.org/z/W66h86E13 (the reduced intermediate form in
optimization).
2024-08-28 21:52:23 +08:00
Raz MonsonegoandGitHub 3b1b1d1486 MOD-7645: Return module commands in ACL CAT (#13489)
Currently, module commands are not returned for the `ACL CAT <category>`
command, but skipped instead. Since now modules can add ACL categories
they should no longer be skipped.
2024-08-26 19:25:22 +01:00
paoloredisandGitHub 60f22ca830 Add redis_docs_sync workflow (#13426)
This workflow executes on releases. It invokes the `redis_docs_sync`
workflow on `redis/docs`.
2024-08-21 09:41:14 +01:00
6ceadfb580 Improve GETRANGE command behavior (#12272)
Fixed the issue about GETRANGE and SUBSTR command
return unexpected result caused by the `start` and `end` out of
definition range of string.

---
## break change
Before this PR, when negative `end` was out of range (i.e., end <
-strlen), we would fix it to 0 to get the substring, which also resulted
in the first character still being returned for this kind of out of
range.
After this PR, we ensure that `GETRANGE` returns an empty bulk when the
negative end index is out of range.

Closes #11738

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-20 12:34:43 +08:00
judengandGitHub 7f0a7f0a69 improve performance for scan command when matching data type (#12395)
Move the TYPE filtering to the scan callback so that avoided the
`lookupKey` operation. This is the follow-up to #12209 . In this thread
we introduced two breaking changes:
1. we will not attempt to do lazy expire (delete) a key that was
filtered by not matching the TYPE (like we already do for MATCH
pattern).
2. when the specified key TYPE filter is an unknown type, server will
reply a error immediately instead of doing a full scan that comes back
empty handed.
2024-08-20 10:47:51 +08:00
Meir Shpilraien (Spielrein)andGitHub 3264deb24e Avoid used_memory contention when update from multiple threads. (#13431)
The PR attempt to avoid contention on the `used_memory` global variable
when allocate or free memory from multiple threads at the same time.

Each time a thread is allocating or releasing a memory, it needs to
update the `used_memory` global variable. This update might cause a
contention when done aggressively from multiple threads.

### The solution

Instead of having a single global variable that need to be updated from
multiple thread. We create an array of used_memory, each entry in the
array is updated by a single thread and the main thread summarizes all
the values to accumulate the memory usage.

This solution, though reduces the contention between threads on updating
the `used_memory` global variable, it adds work to the main thread that
need to summarize all the entries at the `used_memory` array. To avoid
increasing the work done by the main thread by too much, we limit the
size of the used memory array to 16. This means that up to 16 threads
can run without any contention between them. If there are more than 16
threads, we will reuse entries on the used_memory array, in this case we
might still have contention between threads, but it will be much less
significant.

Notice, that in order to really avoid contention, the entries in the
`used_memory` array must reside on different cache lines. To achieve
that we create a struct with padding such that its size will be exactly
cache_line size. In addition we make sure the address of the
`used_memory` array will be aligned to cache_line size.

### Benchmark

Some benchmark shows improvement (up to 15%):

| Test Case |Baseline unstable (median obs. +- std.dev)|Comparison
test_used_memory_per_thread_array (median obs. +- std.dev)|% change
(higher-better)| Note |

|-------------------------------------------------------------------------------|------------------------------------------|--------------------------------------------------------------------:|------------------------|------------------------------------|
|memtier_benchmark-1key-list-100-elements-lrange-all-elements | 92657 +-
2.0% (2 datapoints) | 101445|9.5% |IMPROVEMENT |
|memtier_benchmark-1key-list-1K-elements-lrange-all-elements | 14965 +-
1.3% (2 datapoints) | 16296|8.9% |IMPROVEMENT |
|memtier_benchmark-1key-set-10-elements-smembers-pipeline-10 | 431019 +-
5.2% (2 datapoints) | 461039|7.0% |waterline=5.2%. IMPROVEMENT |
|memtier_benchmark-1key-set-100-elements-smembers | 74367 +- 0.0% (2
datapoints) | 80190|7.8% |IMPROVEMENT |
|memtier_benchmark-1key-set-1K-elements-smembers | 11730 +- 0.4% (2
datapoints) | 13519|15.3% |IMPROVEMENT |


Full results:

| Test Case |Baseline unstable (median obs. +- std.dev)|Comparison
test_used_memory_per_thread_array (median obs. +- std.dev)|% change
(higher-better)| Note |

|-------------------------------------------------------------------------------|------------------------------------------|--------------------------------------------------------------------:|------------------------|------------------------------------|
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-1000B-values | 88613
+- 1.0% (2 datapoints) | 88688|0.1% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10
| 124786 +- 1.2% (2 datapoints) | 123671|-0.9% |No Change |
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values | 122460
+- 1.4% (2 datapoints) | 122990|0.4% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values-pipeline-10
| 333384 +- 5.1% (2 datapoints) | 319221|-4.2% |waterline=5.1%.
potential REGRESSION|
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values | 137354
+- 0.3% (2 datapoints) | 138759|1.0% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values-pipeline-10
| 401261 +- 4.3% (2 datapoints) | 398524|-0.7% |No Change |
|memtier_benchmark-1Mkeys-100B-expire-use-case | 179058 +- 0.4% (2
datapoints) | 180114|0.6% |No Change |
|memtier_benchmark-1Mkeys-10B-expire-use-case | 180390 +- 0.2% (2
datapoints) | 180401|0.0% |No Change |
|memtier_benchmark-1Mkeys-1KiB-expire-use-case | 175993 +- 0.7% (2
datapoints) | 175147|-0.5% |No Change |
|memtier_benchmark-1Mkeys-4KiB-expire-use-case | 165771 +- 0.0% (2
datapoints) | 164434|-0.8% |No Change |
|memtier_benchmark-1Mkeys-bitmap-getbit-pipeline-10 | 931339 +- 2.1% (2
datapoints) | 929487|-0.2% |No Change |
|memtier_benchmark-1Mkeys-generic-exists-pipeline-10 | 999462 +- 0.4% (2
datapoints) | 963226|-3.6% |potential REGRESSION |
|memtier_benchmark-1Mkeys-generic-expire-pipeline-10 | 905333 +- 1.4% (2
datapoints) | 896673|-1.0% |No Change |
|memtier_benchmark-1Mkeys-generic-expireat-pipeline-10 | 885015 +- 1.0%
(2 datapoints) | 865010|-2.3% |No Change |
|memtier_benchmark-1Mkeys-generic-pexpire-pipeline-10 | 897115 +- 1.2%
(2 datapoints) | 887544|-1.1% |No Change |
|memtier_benchmark-1Mkeys-generic-scan-pipeline-10 | 451103 +- 3.2% (2
datapoints) | 465571|3.2% |potential IMPROVEMENT |
|memtier_benchmark-1Mkeys-generic-touch-pipeline-10 | 996809 +- 0.6% (2
datapoints) | 984478|-1.2% |No Change |
|memtier_benchmark-1Mkeys-generic-ttl-pipeline-10 | 979570 +- 1.7% (2
datapoints) | 958752|-2.1% |No Change |

|memtier_benchmark-1Mkeys-hash-hget-hgetall-hkeys-hvals-with-100B-values
| 180888 +- 0.5% (2 datapoints) | 182295|0.8% |No Change |

|memtier_benchmark-1Mkeys-hash-hmget-5-fields-with-100B-values-pipeline-10
| 717881 +- 1.0% (2 datapoints) | 724814|1.0% |No Change |
|memtier_benchmark-1Mkeys-hash-transactions-multi-exec-pipeline-20 |
1055447 +- 0.4% (2 datapoints) | 1065836|1.0% |No Change |
|memtier_benchmark-1Mkeys-lhash-hexists | 164332 +- 0.1% (2 datapoints)
| 163636|-0.4% |No Change |
|memtier_benchmark-1Mkeys-lhash-hincbry | 171674 +- 0.3% (2 datapoints)
| 172737|0.6% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-100B-values | 180904 +-
1.1% (2 datapoints) | 179467|-0.8% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-10B-values | 181746 +-
0.8% (2 datapoints) | 182416|0.4% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-1KiB-values | 182004 +-
0.7% (2 datapoints) | 180237|-1.0% |No Change |
|memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values | 105191
+- 0.9% (2 datapoints) | 105058|-0.1% |No Change |

|memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10
| 150683 +- 0.9% (2 datapoints) | 153597|1.9% |No Change |
|memtier_benchmark-1Mkeys-load-hash-hmset-5-fields-with-1000B-values |
104122 +- 0.7% (2 datapoints) | 105236|1.1% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-100B-values | 149770 +- 0.9% (2
datapoints) | 150510|0.5% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-10B-values | 165537 +- 1.9% (2
datapoints) | 164329|-0.7% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-1KiB-values | 113315 +- 0.5% (2
datapoints) | 114110|0.7% |No Change |
|memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values | 131201
+- 0.7% (2 datapoints) | 129545|-1.3% |No Change |

|memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values-pipeline-10
| 352891 +- 2.8% (2 datapoints) | 348338|-1.3% |No Change |
|memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values | 104386
+- 0.7% (2 datapoints) | 105796|1.4% |No Change |

|memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values-pipeline-10
| 227593 +- 5.5% (2 datapoints) | 218783|-3.9% |waterline=5.5%.
potential REGRESSION|
|memtier_benchmark-1Mkeys-load-string-with-100B-values | 167552 +- 0.2%
(2 datapoints) | 170282|1.6% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-100B-values-pipeline-10 |
646888 +- 0.5% (2 datapoints) | 639680|-1.1% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-10B-values | 174891 +- 0.7%
(2 datapoints) | 174382|-0.3% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-10 |
749988 +- 5.1% (2 datapoints) | 769986|2.7% |waterline=5.1%. No Change |
|memtier_benchmark-1Mkeys-load-string-with-1KiB-values | 155929 +- 0.1%
(2 datapoints) | 156387|0.3% |No Change |
|memtier_benchmark-1Mkeys-load-zset-with-10-elements-double-score |
92241 +- 0.2% (2 datapoints) | 92189|-0.1% |No Change |
|memtier_benchmark-1Mkeys-load-zset-with-10-elements-int-score | 114328
+- 1.3% (2 datapoints) | 113154|-1.0% |No Change |
|memtier_benchmark-1Mkeys-string-get-100B | 180685 +- 0.2% (2
datapoints) | 180359|-0.2% |No Change |
|memtier_benchmark-1Mkeys-string-get-100B-pipeline-10 | 991291 +- 3.1%
(2 datapoints) | 1020086|2.9% |No Change |
|memtier_benchmark-1Mkeys-string-get-10B | 181183 +- 0.3% (2 datapoints)
| 177868|-1.8% |No Change |
|memtier_benchmark-1Mkeys-string-get-10B-pipeline-10 | 1032554 +- 0.8%
(2 datapoints) | 1023120|-0.9% |No Change |
|memtier_benchmark-1Mkeys-string-get-1KiB | 180479 +- 0.9% (2
datapoints) | 182215|1.0% |No Change |
|memtier_benchmark-1Mkeys-string-get-1KiB-pipeline-10 | 979286 +- 0.9%
(2 datapoints) | 989888|1.1% |No Change |
|memtier_benchmark-1Mkeys-string-mget-1KiB | 121950 +- 0.4% (2
datapoints) | 120996|-0.8% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geodist | 179404 +- 1.0% (2
datapoints) | 181232|1.0% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geodist-pipeline-10 | 1023797
+- 0.5% (2 datapoints) | 1014980|-0.9% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geohash | 180808 +- 1.2% (2
datapoints) | 180606|-0.1% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geohash-pipeline-10 | 1056458
+- 1.6% (2 datapoints) | 1040050|-1.6% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geopos | 181808 +- 0.2% (2
datapoints) | 175945|-3.2% |potential REGRESSION |
|memtier_benchmark-1key-geo-60M-elements-geopos-pipeline-10 | 1038180 +-
3.4% (2 datapoints) | 1033005|-0.5% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat | 142614
+- 0.3% (2 datapoints) | 144259|1.2% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-bybox |
141008 +- 0.4% (2 datapoints) | 139602|-1.0% |No Change |

|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-pipeline-10
| 560698 +- 0.8% (2 datapoints) | 548806|-2.1% |No Change |
|memtier_benchmark-1key-list-10-elements-lrange-all-elements | 166132 +-
0.9% (2 datapoints) | 170259|2.5% |No Change |
|memtier_benchmark-1key-list-100-elements-lrange-all-elements | 92657 +-
2.0% (2 datapoints) | 101445|9.5% |IMPROVEMENT |
|memtier_benchmark-1key-list-1K-elements-lrange-all-elements | 14965 +-
1.3% (2 datapoints) | 16296|8.9% |IMPROVEMENT |
|memtier_benchmark-1key-pfadd-4KB-values-pipeline-10 | 264156 +- 0.2% (2
datapoints) | 262582|-0.6% |No Change |
|memtier_benchmark-1key-set-10-elements-smembers | 138916 +- 1.7% (2
datapoints) | 138016|-0.6% |No Change |
|memtier_benchmark-1key-set-10-elements-smembers-pipeline-10 | 431019 +-
5.2% (2 datapoints) | 461039|7.0% |waterline=5.2%. IMPROVEMENT |
|memtier_benchmark-1key-set-10-elements-smismember | 173545 +- 1.1% (2
datapoints) | 173488|-0.0% |No Change |
|memtier_benchmark-1key-set-100-elements-smembers | 74367 +- 0.0% (2
datapoints) | 80190|7.8% |IMPROVEMENT |
|memtier_benchmark-1key-set-100-elements-smismember | 155682 +- 1.6% (2
datapoints) | 151367|-2.8% |No Change |
|memtier_benchmark-1key-set-1K-elements-smembers | 11730 +- 0.4% (2
datapoints) | 13519|15.3% |IMPROVEMENT |
|memtier_benchmark-1key-set-200K-elements-sadd-constant | 181070 +- 1.1%
(2 datapoints) | 180214|-0.5% |No Change |
|memtier_benchmark-1key-set-2M-elements-sadd-increasing | 166364 +- 0.1%
(2 datapoints) | 166944|0.3% |No Change |
|memtier_benchmark-1key-zincrby-1M-elements-pipeline-1 | 46071 +- 0.6%
(2 datapoints) | 44979|-2.4% |No Change |
|memtier_benchmark-1key-zrank-1M-elements-pipeline-1 | 48429 +- 0.4% (2
datapoints) | 49265|1.7% |No Change |
|memtier_benchmark-1key-zrem-5M-elements-pipeline-1 | 48528 +- 0.4% (2
datapoints) | 48869|0.7% |No Change |
|memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-1 |
100580 +- 1.5% (2 datapoints) | 101782|1.2% |No Change |
|memtier_benchmark-1key-zrevrank-1M-elements-pipeline-1 | 48621 +- 2.0%
(2 datapoints) | 48473|-0.3% |No Change |
|memtier_benchmark-1key-zset-10-elements-zrange-all-elements | 83485 +-
0.6% (2 datapoints) | 83095|-0.5% |No Change |

|memtier_benchmark-1key-zset-10-elements-zrange-all-elements-long-scores
| 118673 +- 0.8% (2 datapoints) | 118006|-0.6% |No Change |
|memtier_benchmark-1key-zset-100-elements-zrange-all-elements | 19009 +-
1.1% (2 datapoints) | 19293|1.5% |No Change |
|memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements |
18957 +- 0.5% (2 datapoints) | 19419|2.4% |No Change |

|memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements-long-scores|
171693 +- 0.5% (2 datapoints) | 172432|0.4% |No Change |
|memtier_benchmark-1key-zset-1K-elements-zrange-all-elements | 3566 +-
0.6% (2 datapoints) | 3672|3.0% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zcard-pipeline-10 | 1067713 +-
0.4% (2 datapoints) | 1071550|0.4% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zrevrange-5-elements | 169195
+- 0.7% (2 datapoints) | 169620|0.3% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zscore-pipeline-10 | 914338 +-
0.2% (2 datapoints) | 905540|-1.0% |No Change |
|memtier_benchmark-2keys-lua-eval-hset-expire | 88346 +- 1.7% (2
datapoints) | 87259|-1.2% |No Change |
|memtier_benchmark-2keys-lua-evalsha-hset-expire | 103273 +- 1.2% (2
datapoints) | 102393|-0.9% |No Change |
|memtier_benchmark-2keys-set-10-100-elements-sdiff | 15418 +- 10.9%
UNSTABLE (2 datapoints) | 14369|-6.8% |UNSTABLE (very high variance) |
|memtier_benchmark-2keys-set-10-100-elements-sinter | 83601 +- 3.6% (2
datapoints) | 82508|-1.3% |No Change |
|memtier_benchmark-2keys-set-10-100-elements-sunion | 14942 +- 11.2%
UNSTABLE (2 datapoints) | 14001|-6.3% |UNSTABLE (very high variance) |
|memtier_benchmark-2keys-stream-5-entries-xread-all-entries | 75938 +-
0.4% (2 datapoints) | 76565|0.8% |No Change |
|memtier_benchmark-2keys-stream-5-entries-xread-all-entries-pipeline-10
| 120781 +- 1.1% (2 datapoints) | 119142|-1.4% |No Change |
2024-08-19 13:22:16 +03:00
debing.sunandGitHub 6c6489280c Fix a race condition issue in the cache_memory of functionsLibCtx (#13476)
This is a missing of the PR https://github.com/redis/redis/pull/13383.
We will call `functionsLibCtxClear()` in bio, so we shouldn't touch
`curr_functions_lib_ctx` in it.
2024-08-19 10:11:45 +08:00
debing.sunandGitHub 2b88db90aa Fix incorrect lag due to trimming stream via XTRIM command (#13473)
## Describe
When using the `XTRIM` command to trim a stream, it does not update the
maximal tombstone (`max_deleted_entry_id`). This leads to an issue where
the lag calculation incorrectly assumes that there are no tombstones
after the consumer group's last_id, resulting in an inaccurate lag.

The reason XTRIM doesn't need to update the maximal tombstone is that it
always trims from the beginning of the stream. This means that it
consistently changes the position of the first entry, leading to the
following scenarios:

1) First entry trimmed after maximal tombstone:
If the first entry is trimmed to a position after the maximal tombstone,
all tombstones will be before the first entry, so they won't affect the
consumer group's lag.

2) First entry trimmed before maximal tombstone:
If the first entry is trimmed to a position before the maximal
tombstone, the maximal tombstone will not be updated.

## Solution
Therefore, this PR optimizes the lag calculation by ensuring that when
both the consumer group's last_id and the maximal tombstone are behind
the first entry, the consumer group's lag is always equal to the number
of remaining elements in the stream.

Supplement to PR https://github.com/redis/redis/pull/13338
2024-08-16 23:13:31 +08:00
debing.sunandGitHub b94b714f81 Fix error message for XREAD command with wrong parameter (#13474)
Fixed a missing from #13117.
When the number of streams is incorrect, the error message for `XREAD`
needs to include the '+' symbol.
2024-08-14 21:40:43 +08:00
Moti CohenandGitHub 806459f481 On HDEL last field with expiry, update global HFE DS (#13470)
Hash field expiration is optimized to avoid frequent update global HFE DS for
each field deletion. Eventually active-expiration will run and update or remove
the hash from global HFE DS gracefully. Nevertheless, statistic "subexpiry"
might reflect wrong number of hashes with HFE to the user if HDEL deletes
the last field with expiration in hash (yet there are more fields without expiration).

Following this change, if HDEL the last field with expiration in the hash then
take care to remove the hash from global HFE DS as well.
2024-08-11 16:39:03 +03:00
3a08819f51 Fix some memory leaks in redis-cli (#13258)
Fix memory leak related to variable slot_nodes  in the
clusterManagerFixSlotsCoverage() function.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-08 10:51:33 +08:00
6f0ddc9d92 Pass extensions to node if extension processing is handled by it (#13465)
This PR is based on the commits from PR
https://github.com/valkey-io/valkey/pull/52.
Ref: https://github.com/redis/redis/pull/12760
Close https://github.com/redis/redis/issues/13401
This PR will replace https://github.com/redis/redis/pull/13449

Fixes compatibilty of Redis cluster (7.2 - extensions enabled by
default) with older Redis cluster (< 7.0 - extensions not handled) .

With some of the extensions enabled by default in 7.2 version, new nodes
running 7.2 and above start sending out larger clusterbus message
payload including the ping extensions. This caused an incompatibility
with node running engine versions < 7.0. Old nodes (< 7.0) would receive
the payload from new nodes (> 7.2) would observe a payload length
(totlen) > (estlen) and would perform an early exit and won't process
the message.

This fix does the following things:
1. Always set `CLUSTERMSG_FLAG0_EXT_DATA`, because during the meet
phase, we do not know whether the connected node supports ext data, we
need to make sure that it knows and send back its ext data if it has.
2. If another node does not support ext data, we will not send it ext
data to avoid the handshake failure due to the incorrect payload length.

Note: A successful `PING`/`PONG` is required as a sender for a given
node to be marked as `CLUSTERMSG_FLAG0_EXT_DATA` and then extensions
message
will be sent to it. This could cause a slight delay in receiving the
extensions message(s).

---------

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>

---------

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
2024-08-08 10:48:03 +08:00
731f2dc5c7 Make some commets more friendly (#13319)
Close  #13316

---------

Co-authored-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-07 01:20:06 +08:00
debing.sunandGitHub bf643a63c8 Ensure validity of myself as master or replica when loading cluster config (#13443)
First, we need to ensure that `curmaster` in
`clusterUpdateSlotsConfigWith()` is not NULL in the line
https://github.com/redis/redis/blob/82f00f5179720c8cee6cd650763d184ba943be92/src/cluster_legacy.c#L2320
otherwise, it will crash in the
https://github.com/redis/redis/blob/82f00f5179720c8cee6cd650763d184ba943be92/src/cluster_legacy.c#L2395

So when loading cluster node config, we need to ensure that the
following conditions are met:
1. A node must be at least one of the master or replica.
2. If a node is a replica, its master can't be NULL.
2024-08-06 20:40:46 +08:00
YaacovHazanandGitHub e4ddc34463 Keep cluster shards command implementation generic (#13440)
Make the clusterCommandShards function use only cluster API functions
instead of accessing cluster implementation details.
This way the cluster API implementation doesn't have to have intimate
knowledge of the command reply format, and doesn't need to interact with
the client directly (the addReply function family).
The PR has two commits, one moves the function from cluster_legacy.c to
cluster.c, and the other modifies it's implementation.


**better merge without squashing.**
2024-08-05 11:08:48 +03:00
Josh Hershberg 6d5d754119 Make cluster shards cmd implementation generic
This and the previous commit make the cluster
shards command a generic implementation instead of a
specific implementation for each cluster API implementation.
This commit (a) adds functions to the cluster API
and (b) modifies the cluster shards cmd implementation
to use cluster API functions instead of directly
accessing the legacy clustering implementation.

Signed-off-by: Josh Hershberg <yehoshua@redis.com>
2024-08-05 10:31:40 +03:00
Josh Hershberg e3e631f394 Prep to make cluster shards cmd generic
This and the next following commit makes the cluster
shards command a generic implementation instead of a
specific implementation for each cluster API implementation.
This commit simply moves the cluster shards implementation
from cluster_legacy.c to cluster.c without changing the
implementation at all. The reason for doing so was to
help with reviewing the changes in the diff.

Signed-off-by: Josh Hershberg <yehoshua@redis.com>
2024-08-05 10:27:03 +03:00
6263823e54 Replace bit shift with __builtin_ctzll in HyperLogLog (#13218)
## Replace bit shift with `__builtin_ctzll` in HyperLogLog

Builtin function `__builtin_ctzll` is more effective than bit shift even
though "in the average case there are high probabilities to find a 1
after a few iterations" mentioned in the source file comment.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-05 10:51:43 +08:00
Moti CohenandGitHub 4dd8b1faa9 Fix HTTL/HPTTL to be NONDETERMINISTIC_OUTPUT (#13461)
H[P]TTL should be marked as NONDETERMINISTIC_OUTPUT just like [P]TTL.
2024-08-04 17:42:50 +03:00
8038eb3147 Fix wrong dbnum showed in redis-cli after client reconnected (#13411)
When the server restarts while the CLI is connecting, the reconnection
does not automatically select the previous db.
This may lead users to believe they are still in the previous db, in
fact, they are in db0.

This PR will automatically reset the current dbnum and `cliSelect()`
again when reconnecting.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-03 12:06:02 +08:00
c8efandGitHub 89742a95db Fix typo in hyperloglog.c (#13458) 2024-08-02 07:49:52 +08:00
debing.sunandGitHub 60e9e630bd Fix CLUSTER SHARDS command returns empty array (#13422)
Close https://github.com/redis/redis/issues/13414

When the cluster's master node fails and is switched to another node,
the first node in the shard node list (the old master) is no longer
valid.
Add a new method clusterGetMasterFromShard() to obtain the current
master.
2024-08-02 07:22:13 +08:00
e750c619b2 Fix some test failures caused by key being deleted due to premature expiration (#13453)
1. Fix fuzzer test failure when the key was deleted due to expiration
before sending random traffic for the key.

After HFE, when all fields in a hash are expired, the hash might be
deleted due to expiration.

If the key was expired in the mid of `RESTORE` command and sending rand
trafic, `fuzzer` test will fail in the following code because the 'TYPE
key' will return `none` and then throw an exception because it cannot be
found in `$commands`

https://github.com/redis/redis/blob/94b9072e44af0bae8cfe2de5cfa4af7b8e399759/tests/support/util.tcl#L712-L713

This PR adds a `None` check for the reply of `KEY TYPE` command and adds
a print of `err` to avoid false positives in the future.

failed CI:
https://github.com/redis/redis/actions/runs/10127334121/job/28004985388

2. Fix the issue where key was deleted due to expiration before the
`scan.scan_key` command could execute, caused by premature enabling of
`set-active-expire`.

failed CI:
https://github.com/redis/redis/actions/runs/10153722715/job/28077610552

---------

Co-authored-by: oranagra <oran@redislabs.com>
2024-07-31 08:15:39 +08:00
debing.sunandGitHub 93fb83b4cb Fix incorrect lag field in XINFO when tombstone is after the last_id of consume group (#13338)
Fix #13337

Ths PR fixes fixed two bugs that caused lag calculation errors.
1. When the latest tombstone is before the first entry, the tombstone
may stil be after the last id of consume group.
2. When a tombstone is after the last id of consume group, the group's
counter will be invalid, we should caculate the entries_read by using
estimates.
2024-07-30 22:31:31 +08:00
132 changed files with 9421 additions and 1387 deletions
+4 -3
View File
@@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v4
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib g++-multilib
make REDIS_CFLAGS='-Werror' 32bit
build-libc-malloc:
@@ -79,7 +79,7 @@ jobs:
- uses: actions/checkout@v4
- name: make
run: |
dnf -y install which gcc make
dnf -y install which gcc gcc-c++ make
make REDIS_CFLAGS='-Werror'
build-old-chain-jemalloc:
@@ -96,6 +96,7 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8
apt-get install -y make gcc-4.8 g++-4.8
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc REDIS_CFLAGS='-Werror'
+19 -12
View File
@@ -94,8 +94,9 @@ jobs:
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
apt-get update && apt-get install -y make gcc-13
apt-get update && apt-get install -y make gcc-13 g++-13
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
- name: testprep
run: apt-get install -y tcl8.6 tclx procps
@@ -211,7 +212,7 @@ jobs:
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
sudo apt-get update && sudo apt-get install libc6-dev-i386 g++ gcc-multilib g++-multilib
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: testprep
run: sudo apt-get install tcl8.6 tclx
@@ -456,7 +457,7 @@ jobs:
- name: testprep
run: |
sudo apt-get update
sudo apt-get install tcl8.6 tclx valgrind -y
sudo apt-get install tcl8.6 tclx valgrind g++ -y
- name: test
if: true && !contains(github.event.inputs.skiptests, 'redis')
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
@@ -521,7 +522,7 @@ jobs:
- name: testprep
run: |
sudo apt-get update
sudo apt-get install tcl8.6 tclx valgrind -y
sudo apt-get install tcl8.6 tclx valgrind g++ -y
- name: test
if: true && !contains(github.event.inputs.skiptests, 'redis')
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
@@ -678,7 +679,7 @@ jobs:
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make
dnf -y install which gcc make g++
make REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -720,7 +721,7 @@ jobs:
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make openssl-devel openssl
dnf -y install which gcc make openssl-devel openssl g++
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -767,7 +768,7 @@ jobs:
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make openssl-devel openssl
dnf -y install which gcc make openssl-devel openssl g++
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -907,6 +908,9 @@ jobs:
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
!contains(github.event.inputs.skipjobs, 'freebsd')
timeout-minutes: 14400
env:
CC: clang
CXX: clang++
steps:
- name: prep
if: github.event_name == 'workflow_dispatch'
@@ -925,7 +929,7 @@ jobs:
version: 13.2
shell: bash
run: |
sudo pkg install -y bash gmake lang/tcl86 lang/tclx
sudo pkg install -y bash gmake lang/tcl86 lang/tclx gcc
gmake
./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol
@@ -1080,8 +1084,9 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8
apt-get install -y make gcc-4.8 g++-4.8
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc REDIS_CFLAGS='-Werror'
- name: testprep
run: apt-get install -y tcl tcltls tclx
@@ -1128,9 +1133,10 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 openssl libssl-dev
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
make CC=gcc BUILD_TLS=module REDIS_CFLAGS='-Werror'
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc CXX=g++ BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
apt-get install -y tcl tcltls tclx
@@ -1182,8 +1188,9 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 openssl libssl-dev
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make BUILD_TLS=module CC=gcc REDIS_CFLAGS='-Werror'
- name: testprep
run: |
+35
View File
@@ -0,0 +1,35 @@
name: redis_docs_sync
on:
release:
types: [published]
jobs:
redis_docs_sync:
if: github.repository == 'redis/redis'
runs-on: ubuntu-latest
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.DOCS_APP_ID }}
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
- name: Invoke workflow on redis/docs
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
RELEASE_NAME: ${{ github.event.release.tag_name }}
run: |
LATEST_RELEASE=$(
curl -Ls \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/redis/redis/releases/latest \
| jq -r '.tag_name'
)
if [[ "${LATEST_RELEASE}" == "${RELEASE_NAME}" ]]; then
gh workflow run -R redis/docs redis_docs_sync.yaml -f release="${RELEASE_NAME}"
fi
+1
View File
@@ -30,6 +30,7 @@ deps/lua/src/luac
deps/lua/src/liblua.a
deps/hdr_histogram/libhdrhistogram.a
deps/fpconv/libfpconv.a
deps/fast_float/libfast_float.a
tests/tls/*
.make-*
.prerequisites
+8 -3
View File
@@ -1,11 +1,16 @@
# Top level makefile, the real shit is at src/Makefile
# Top level makefile, the real stuff is at ./src/Makefile and in ./modules/Makefile
SUBDIRS = src
ifeq ($(BUILD_WITH_MODULES), yes)
SUBDIRS += modules
endif
default: all
.DEFAULT:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
install:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
.PHONY: install
-1
View File
@@ -16,7 +16,6 @@ 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. https://redis.io/topics/data-types-intro
* Try Redis directly inside your browser. https://try.redis.io
* The full list of Redis commands. https://redis.io/commands
* There is much more inside the official Redis documentation. https://redis.io/documentation
+7
View File
@@ -42,6 +42,7 @@ distclean:
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(cd fpconv && $(MAKE) clean) > /dev/null || true
-(cd fast_float && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*)
.PHONY: distclean
@@ -74,6 +75,12 @@ fpconv: .make-prerequisites
.PHONY: fpconv
fast_float: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd fast_float && $(MAKE) libfast_float
.PHONY: fast_float
ifeq ($(uname_S),SunOS)
# Make isinf() available
LUA_CFLAGS= -D__C99FEATURES__=1
+24
View File
@@ -0,0 +1,24 @@
# Fallback to gcc/g++ when $CC or $CXX is not in $PATH.
CC ?= gcc
CXX ?= g++
CFLAGS=-Wall -O3
# This avoids loosing the fastfloat specific compile flags when we override the CFLAGS via the main project
FASTFLOAT_CFLAGS=-std=c++11 -DFASTFLOAT_ALLOWS_LEADING_PLUS
LDFLAGS=
libfast_float: fast_float_strtod.o
$(AR) -r libfast_float.a fast_float_strtod.o
32bit: CFLAGS += -m32
32bit: LDFLAGS += -m32
32bit: libfast_float
fast_float_strtod.o: fast_float_strtod.cpp
$(CXX) $(CFLAGS) $(FASTFLOAT_CFLAGS) -c fast_float_strtod.cpp $(LDFLAGS)
clean:
rm -f *.o
rm -f *.a
rm -f *.h.gch
rm -rf *.dSYM
+21
View File
@@ -0,0 +1,21 @@
README for fast_float v6.1.4
----------------------------------------------
We're using the fast_float library[1] in our (compiled-in)
floating-point fast_float_strtod implementation for faster and more
portable parsing of 64 decimal strings.
The single file fast_float.h is an amalgamation of the entire library,
which can be (re)generated with the amalgamate.py script (from the
fast_float repository) via the command
```
git clone https://github.com/fastfloat/fast_float
cd fast_float
git checkout v6.1.4
python3 ./script/amalgamate.py --license=MIT \
> $REDIS_SRC/deps/fast_float/fast_float.h
```
[1]: https://github.com/fastfloat/fast_float
+3838
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
#include "fast_float.h"
#include <iostream>
#include <string>
#include <system_error>
#include <cerrno>
/* Convert NPTR to a double using the fast_float library.
*
* This function behaves similarly to the standard strtod function, converting
* the initial portion of the string pointed to by `nptr` to a `double` value,
* using the fast_float library for high performance. If the conversion fails,
* errno is set to EINVAL error code.
*
* @param nptr A pointer to the null-terminated byte string to be interpreted.
* @param endptr A pointer to a pointer to character. If `endptr` is not NULL,
* it will point to the character after the last character used
* in the conversion.
* @return The converted value as a double. If no valid conversion could
* be performed, returns 0.0.
* If ENDPTR is not NULL, a pointer to the character after the last one used
* in the number is put in *ENDPTR. */
extern "C" double fast_float_strtod(const char *nptr, char **endptr) {
double result = 0.0;
auto answer = fast_float::from_chars(nptr, nptr + strlen(nptr), result);
if (answer.ec != std::errc()) {
errno = EINVAL; // Fallback to for other errors
}
if (endptr != NULL) {
*endptr = (char *)answer.ptr;
}
return result;
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef __FAST_FLOAT_STRTOD_H__
#define __FAST_FLOAT_STRTOD_H__
#if defined(__cplusplus)
extern "C"
{
#endif
double fast_float_strtod(const char *in, char **out);
#if defined(__cplusplus)
}
#endif
#endif /* __FAST_FLOAT_STRTOD_H__ */
+1 -1
View File
@@ -296,7 +296,7 @@ static int isUnsupportedTerm(void) {
return 0;
}
/* Raw mode: 1960 magic shit. */
/* Raw mode: 1960's magic. */
static int enableRawMode(int fd) {
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
return 0;
+1
View File
@@ -132,6 +132,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; }
+90
View File
@@ -0,0 +1,90 @@
SUBDIRS = redisjson redistimeseries redisbloom redisearch
define submake
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $(1); done
endef
all: prepare_source
$(call submake,$@)
get_source:
$(call submake,$@)
prepare_source: get_source handle-werrors setup_environment
clean:
$(call submake,$@)
distclean: clean_environment
$(call submake,$@)
pristine:
$(call submake,$@)
install:
$(call submake,$@)
setup_environment: install-rust handle-werrors
clean_environment: uninstall-rust
# Keep all of the Rust stuff in one place
install-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@RUST_VERSION=1.80.1; \
ARCH="$$(uname -m)"; \
if ldd --version 2>&1 | grep -q musl; then LIBC_TYPE="musl"; else LIBC_TYPE="gnu"; fi; \
echo "Detected architecture: $${ARCH} and libc: $${LIBC_TYPE}"; \
case "$${ARCH}" in \
'x86_64') \
if [ "$${LIBC_TYPE}" = "musl" ]; then \
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-musl"; \
RUST_SHA256="37bbec6a7b9f55fef79c451260766d281a7a5b9d2e65c348bbc241127cf34c8d"; \
else \
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; \
RUST_SHA256="85e936d5d36970afb80756fa122edcc99bd72a88155f6bdd514f5d27e778e00a"; \
fi ;; \
'aarch64') \
if [ "$${LIBC_TYPE}" = "musl" ]; then \
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-musl"; \
RUST_SHA256="dd668c2d82f77c5458deb023932600fae633fff8d7f876330e01bc47e9976d17"; \
else \
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; \
RUST_SHA256="2e89bad7857711a1c11d017ea28fbfeec54076317763901194f8f5decbac1850"; \
fi ;; \
*) echo >&2 "Unsupported architecture: '$${ARCH}'"; exit 1 ;; \
esac; \
echo "Downloading and installing Rust standalone installer: $${RUST_INSTALLER}"; \
wget --quiet -O $${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/$${RUST_INSTALLER}.tar.xz; \
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --quiet || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
tar -xf $${RUST_INSTALLER}.tar.xz; \
(cd $${RUST_INSTALLER} && ./install.sh); \
rm -rf $${RUST_INSTALLER}
endif
uninstall-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@if [ -x "/usr/local/lib/rustlib/uninstall.sh" ]; then \
echo "Uninstalling Rust using uninstall.sh script"; \
rm -rf ~/.cargo; \
/usr/local/lib/rustlib/uninstall.sh; \
else \
echo "WARNING: Rust toolchain not found or uninstall script is missing."; \
fi
endif
handle-werrors: get_source
ifeq ($(DISABLE_WERRORS),yes)
@echo "Disabling -Werror for all modules"
@for dir in $(SUBDIRS); do \
echo "Processing $$dir"; \
find $$dir/src -type f \
\( -name "Makefile" \
-o -name "*.mk" \
-o -name "CMakeLists.txt" \) \
-exec sed -i 's/-Werror//g' {} +; \
done
endif
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
+49
View File
@@ -0,0 +1,49 @@
PREFIX ?= /usr/local
INSTALL_DIR ?= $(DESTDIR)$(PREFIX)/lib/redis/modules
INSTALL ?= install
# This logic *partially* follows the current module build system. It is a bit awkward and
# should be changed if/when the modules' build process is refactored.
ARCH_MAP_x86_64 := x64
ARCH_MAP_i386 := x86
ARCH_MAP_i686 := x86
ARCH_MAP_aarch64 := arm64v8
ARCH_MAP_arm64 := arm64v8
OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
ARCH := $(ARCH_MAP_$(shell uname -m))
ifeq ($(ARCH),)
$(error Unrecognized CPU architecture $(shell uname -m))
endif
FULL_VARIANT := $(OS)-$(ARCH)-release
# Common rules for all modules, based on per-module configuration
all: $(TARGET_MODULE)
$(TARGET_MODULE): get_source
$(MAKE) -C $(SRC_DIR)
get_source: $(SRC_DIR)/.prepared
$(SRC_DIR)/.prepared:
mkdir -p $(SRC_DIR)
git clone --recursive --depth 1 --branch $(MODULE_VERSION) $(MODULE_REPO) $(SRC_DIR)
touch $@
clean:
-$(MAKE) -C $(SRC_DIR) clean
distclean:
-$(MAKE) -C $(SRC_DIR) distclean
pristine:
-rm -rf $(SRC_DIR)
install: $(TARGET_MODULE)
mkdir -p $(INSTALL_DIR)
$(INSTALL) -m 0755 -D $(TARGET_MODULE) $(INSTALL_DIR)
.PHONY: all clean distclean pristine install
+6
View File
@@ -0,0 +1,6 @@
SRC_DIR = src
MODULE_VERSION = v7.99.1
MODULE_REPO = https://github.com/redisbloom/redisbloom
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
include ../common.mk
+7
View File
@@ -0,0 +1,7 @@
SRC_DIR = src
MODULE_VERSION = v7.99.1
MODULE_REPO = https://github.com/redisearch/redisearch
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
include ../common.mk
+11
View File
@@ -0,0 +1,11 @@
SRC_DIR = src
MODULE_VERSION = v7.99.1
MODULE_REPO = https://github.com/redisjson/redisjson
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
include ../common.mk
$(SRC_DIR)/.cargo_fetched:
cd $(SRC_DIR) && cargo fetch
get_source: $(SRC_DIR)/.cargo_fetched
+6
View File
@@ -0,0 +1,6 @@
SRC_DIR = src
MODULE_VERSION = v7.99.1
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
include ../common.mk
+10 -21
View File
@@ -1291,38 +1291,27 @@ lazyfree-lazy-user-flush no
# in different I/O threads. Since especially writing is so slow, normally
# Redis users use pipelining in order to speed up the Redis performances per
# core, and spawn multiple instances in order to scale more. Using I/O
# threads it is possible to easily speedup two times Redis without resorting
# threads it is possible to easily speedup several times Redis without resorting
# to pipelining nor sharding of the instance.
#
# By default threading is disabled, we suggest enabling it only in machines
# that have at least 4 or more cores, leaving at least one spare core.
# Using more than 8 threads is unlikely to help much. We also recommend using
# threaded I/O only if you actually have performance problems, with Redis
# instances being able to use a quite big percentage of CPU time, otherwise
# there is no point in using this feature.
# We also recommend using threaded I/O only if you actually have performance
# problems, with Redis instances being able to use a quite big percentage of
# CPU time, otherwise there is no point in using this feature.
#
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
# threads, if you have a 8 cores, try to use 6 threads. In order to
# So for instance if you have a four cores boxes, try to use 3 I/O
# threads, if you have a 8 cores, try to use 7 threads. In order to
# enable I/O threads use the following configuration directive:
#
# io-threads 4
#
# Setting io-threads to 1 will just use the main thread as usual.
# When I/O threads are enabled, we only use threads for writes, that is
# to thread the write(2) syscall and transfer the client buffers to the
# socket. However it is also possible to enable threading of reads and
# protocol parsing using the following configuration directive, by setting
# it to yes:
# When I/O threads are enabled, we not only use threads for writes, that
# is to thread the write(2) syscall and transfer the client buffers to the
# socket, but also use threads for reads and protocol parsing.
#
# io-threads-do-reads no
#
# Usually threading reads doesn't help much.
#
# NOTE 1: This configuration directive cannot be changed at runtime via
# CONFIG SET. Also, this feature currently does not work when SSL is
# enabled.
#
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
# NOTE: If you want to test the Redis speedup using redis-benchmark, make
# sure you also run the benchmark itself in threaded mode, using the
# --threads option to match the number of Redis threads, otherwise you'll not
# be able to notice the improvements.
+5 -5
View File
@@ -34,7 +34,7 @@ endif
ifneq ($(OPTIMIZATION),-O0)
OPTIMIZATION+=-fno-omit-frame-pointer
endif
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float
NODEPS:=clean distclean
# Default settings
@@ -127,7 +127,7 @@ endif
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm
FINAL_LIBS=-lm -lstdc++
DEBUG=-g -ggdb
# Linux ARM32 needs -latomic at linking time
@@ -235,7 +235,7 @@ ifdef OPENSSL_PREFIX
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float
# Determine systemd support and/or build preference (defaulting to auto-detection)
BUILD_WITH_SYSTEMD=no
@@ -354,7 +354,7 @@ endif
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o mstr.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
@@ -409,7 +409,7 @@ endif
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/fast_float/libfast_float.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
+1 -2
View File
@@ -1066,7 +1066,7 @@ int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) {
flags |= ACL_READ_PERMISSION;
} else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) {
flags |= ACL_WRITE_PERMISSION;
} else if (op[offset] == '~') {
} else if (op[offset] == '~' && flags) {
offset++;
break;
} else {
@@ -2762,7 +2762,6 @@ void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) {
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue;
if (cmd->acl_categories & cflag) {
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
(*arraylen)++;
+29 -12
View File
@@ -42,7 +42,7 @@
#endif
#endif
#define INITIAL_EVENT 1024
aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop;
int i;
@@ -50,8 +50,9 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
monotonicInit(); /* just in case the calling app didn't initialize */
if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
eventLoop->nevents = setsize < INITIAL_EVENT ? setsize : INITIAL_EVENT;
eventLoop->events = zmalloc(sizeof(aeFileEvent)*eventLoop->nevents);
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*eventLoop->nevents);
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
eventLoop->setsize = setsize;
eventLoop->timeEventHead = NULL;
@@ -61,10 +62,11 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
eventLoop->beforesleep = NULL;
eventLoop->aftersleep = NULL;
eventLoop->flags = 0;
memset(eventLoop->privdata, 0, sizeof(eventLoop->privdata));
if (aeApiCreate(eventLoop) == -1) goto err;
/* Events with mask == AE_NONE are not set. So let's initialize the
* vector with it. */
for (i = 0; i < setsize; i++)
for (i = 0; i < eventLoop->nevents; i++)
eventLoop->events[i].mask = AE_NONE;
return eventLoop;
@@ -102,20 +104,19 @@ void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
*
* Otherwise AE_OK is returned and the operation is successful. */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
int i;
if (setsize == eventLoop->setsize) return AE_OK;
if (eventLoop->maxfd >= setsize) return AE_ERR;
if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR;
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
eventLoop->setsize = setsize;
/* Make sure that if we created new slots, they are initialized with
* an AE_NONE mask. */
for (i = eventLoop->maxfd+1; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
/* If the current allocated space is larger than the requested size,
* we need to shrink it to the requested size. */
if (setsize < eventLoop->nevents) {
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
eventLoop->nevents = setsize;
}
return AE_OK;
}
@@ -147,6 +148,22 @@ int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
errno = ERANGE;
return AE_ERR;
}
/* Resize the events and fired arrays if the file
* descriptor exceeds the current number of events. */
if (unlikely(fd >= eventLoop->nevents)) {
int newnevents = eventLoop->nevents;
newnevents = (newnevents * 2 > fd + 1) ? newnevents * 2 : fd + 1;
newnevents = (newnevents > eventLoop->setsize) ? eventLoop->setsize : newnevents;
eventLoop->events = zrealloc(eventLoop->events, sizeof(aeFileEvent) * newnevents);
eventLoop->fired = zrealloc(eventLoop->fired, sizeof(aeFiredEvent) * newnevents);
/* Initialize new slots with an AE_NONE mask */
for (int i = eventLoop->nevents; i < newnevents; i++)
eventLoop->events[i].mask = AE_NONE;
eventLoop->nevents = newnevents;
}
aeFileEvent *fe = &eventLoop->events[fd];
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
+2
View File
@@ -79,6 +79,7 @@ typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
long long timeEventNextId;
int nevents; /* Size of Registered events */
aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
@@ -87,6 +88,7 @@ typedef struct aeEventLoop {
aeBeforeSleepProc *beforesleep;
aeBeforeSleepProc *aftersleep;
int flags;
void *privdata[2];
} aeEventLoop;
/* Prototypes */
+23
View File
@@ -997,6 +997,29 @@ int startAppendOnly(void) {
return C_OK;
}
void startAppendOnlyWithRetry(void) {
unsigned int tries, max_tries = 10;
for (tries = 0; tries < max_tries; ++tries) {
if (startAppendOnly() == C_OK)
break;
serverLog(LL_WARNING, "Failed to enable AOF! Trying it again in one second.");
sleep(1);
}
if (tries == max_tries) {
serverLog(LL_WARNING, "FATAL: AOF can't be turned on. Exiting now.");
exit(1);
}
}
/* Called after "appendonly" config is changed. */
void applyAppendOnlyConfig(void) {
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
stopAppendOnly();
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
startAppendOnlyWithRetry();
}
}
/* This is a wrapper to the write syscall in order to retry on short writes
* or if the syscall gets interrupted. It could look strange that we retry
* on short writes given that we are writing to a block device: normally if
+1 -1
View File
@@ -10,7 +10,7 @@ const char *ascii_logo =
" _._ \n"
" _.-``__ ''-._ \n"
" _.-`` `. `_. ''-._ Redis Community Edition \n"
" .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n"
" .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n"
" ( ' , .-` | `, ) Running in %s mode\n"
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
" | `-._ `._ / _.-' | PID: %ld\n"
+6 -2
View File
@@ -81,6 +81,7 @@ static int job_comp_pipe[2]; /* Pipe used to awake the event loop */
typedef struct bio_comp_item {
comp_fn *func; /* callback after completion job will be processed */
uint64_t arg; /* user data to be passed to the function */
void *ptr; /* user pointer to be passed to the function */
} bio_comp_item;
/* This structure represents a background Job. It is only used locally to this
@@ -110,6 +111,7 @@ typedef union bio_job {
int type; /* header */
comp_fn *fn; /* callback. Handover to main thread to cb as notify for job completion */
uint64_t arg; /* callback arguments */
void *ptr; /* callback pointer */
} comp_rq;
} bio_job;
@@ -200,7 +202,7 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
bioSubmitJob(BIO_LAZY_FREE, job);
}
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data) {
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr) {
int type;
switch (assigned_worker) {
case BIO_WORKER_CLOSE_FILE:
@@ -219,6 +221,7 @@ void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_
bio_job *job = zmalloc(sizeof(*job));
job->comp_rq.fn = func;
job->comp_rq.arg = user_data;
job->comp_rq.ptr = user_ptr;
bioSubmitJob(type, job);
}
@@ -339,6 +342,7 @@ void *bioProcessBackgroundJobs(void *arg) {
bio_comp_item *comp_rsp = zmalloc(sizeof(bio_comp_item));
comp_rsp->func = job->comp_rq.fn;
comp_rsp->arg = job->comp_rq.arg;
comp_rsp->ptr = job->comp_rq.ptr;
/* just write it to completion job responses */
pthread_mutex_lock(&bio_mutex_comp);
@@ -432,7 +436,7 @@ void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask) {
listNode *ln = listFirst(tmp_list);
bio_comp_item *rsp = ln->value;
listDelNode(tmp_list, ln);
rsp->func(rsp->arg);
rsp->func(rsp->arg, rsp->ptr);
zfree(rsp);
}
listRelease(tmp_list);
+2 -2
View File
@@ -10,7 +10,7 @@
#define __BIO_H
typedef void lazy_free_fn(void *args[]);
typedef void comp_fn(uint64_t user_data);
typedef void comp_fn(uint64_t user_data, void *user_ptr);
typedef enum bio_worker_t {
BIO_WORKER_CLOSE_FILE = 0,
@@ -40,7 +40,7 @@ void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache);
void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache);
void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache);
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data);
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr);
#endif
+72 -17
View File
@@ -16,18 +16,49 @@
/* 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) */
ATTRIBUTE_TARGET_POPCNT
long long redisPopcount(void *s, long count) {
long long bits = 0;
unsigned char *p = s;
uint32_t *p4;
#if defined(HAVE_POPCNT)
int use_popcnt = __builtin_cpu_supports("popcnt"); /* Check if CPU supports POPCNT instruction. */
#else
int use_popcnt = 0; /* Assume CPU does not support POPCNT if
* __builtin_cpu_supports() is not available. */
#endif
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};
/* Count initial bytes not aligned to 32 bit. */
while((unsigned long)p & 3 && count) {
/* Count initial bytes not aligned to 64-bit when using the POPCNT instruction,
* otherwise align to 32-bit. */
int align = use_popcnt ? 7 : 3;
while ((unsigned long)p & align && count) {
bits += bitsinbyte[*p++];
count--;
}
if (likely(use_popcnt)) {
/* Use separate counters to make the CPU think there are no
* dependencies between these popcnt operations. */
uint64_t cnt[4];
memset(cnt, 0, sizeof(cnt));
/* Count bits 32 bytes at a time by using popcnt.
* Unroll the loop to avoid the overhead of a single popcnt per iteration,
* allowing the CPU to extract more instruction-level parallelism.
* Reference: https://danluu.com/assembly-intrinsics/ */
while (count >= 32) {
cnt[0] += __builtin_popcountll(*(uint64_t*)(p));
cnt[1] += __builtin_popcountll(*(uint64_t*)(p + 8));
cnt[2] += __builtin_popcountll(*(uint64_t*)(p + 16));
cnt[3] += __builtin_popcountll(*(uint64_t*)(p + 24));
count -= 32;
p += 32;
}
bits += cnt[0] + cnt[1] + cnt[2] + cnt[3];
goto remain;
}
/* Count bits 28 bytes at a time */
p4 = (uint32_t*)p;
while(count>=28) {
@@ -64,8 +95,10 @@ long long redisPopcount(void *s, long count) {
((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
}
/* Count the remaining bytes. */
p = (unsigned char*)p4;
remain:
/* Count the remaining bytes. */
while(count--) bits += bitsinbyte[*p++];
return bits;
}
@@ -456,22 +489,27 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
* bits to a string object. The command creates or pad with zeroes the string
* 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, uint64_t maxbit, int *dirty) {
* an error is sent to the client.
*
* (Must provide all the arguments to the function)
*/
static robj *lookupStringForBitCommand(client *c, uint64_t maxbit,
size_t *strOldSize, size_t *strGrowSize)
{
size_t byte = maxbit >> 3;
robj *o = lookupKeyWrite(c->db,c->argv[1]);
if (checkType(c,o,OBJ_STRING)) return NULL;
if (dirty) *dirty = 0;
if (o == NULL) {
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
dbAdd(c->db,c->argv[1],o);
if (dirty) *dirty = 1;
*strGrowSize = byte + 1;
*strOldSize = 0;
} else {
o = dbUnshareStringValue(c->db,c->argv[1],o);
size_t oldlen = sdslen(o->ptr);
*strOldSize = sdslen(o->ptr);
o->ptr = sdsgrowzero(o->ptr,byte+1);
if (dirty && oldlen != sdslen(o->ptr)) *dirty = 1;
*strGrowSize = sdslen(o->ptr) - *strOldSize;
}
return o;
}
@@ -528,8 +566,9 @@ void setbitCommand(client *c) {
return;
}
int dirty;
if ((o = lookupStringForBitCommand(c,bitoffset,&dirty)) == NULL) return;
size_t strOldSize, strGrowSize;
if ((o = lookupStringForBitCommand(c,bitoffset,&strOldSize,&strGrowSize)) == NULL)
return;
/* Get current values */
byte = bitoffset >> 3;
@@ -540,7 +579,7 @@ void setbitCommand(client *c) {
/* Either it is newly created, changed length, or the bit changes before and after.
* Note that the bitval here is actually a decimal number.
* So we need to use `!!` to convert it to 0 or 1 for comparison. */
if (dirty || (!!bitval != on)) {
if (strGrowSize || (!!bitval != on)) {
/* Update byte with new bit value. */
byteval &= ~(1 << bit);
byteval |= ((on & 0x1) << bit);
@@ -548,6 +587,13 @@ void setbitCommand(client *c) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty++;
/* If this is not a new key (old size not 0) and size changed, then
* update the keysizes histogram. Otherwise, the histogram already
* updated in lookupStringForBitCommand() by calling dbAdd(). */
if ((strOldSize > 0) && (strGrowSize != 0))
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
strOldSize, strOldSize + strGrowSize);
}
/* Return original value. */
@@ -1032,7 +1078,8 @@ struct bitfieldOp {
void bitfieldGeneric(client *c, int flags) {
robj *o;
uint64_t bitoffset;
int j, numops = 0, changes = 0, dirty = 0;
int j, numops = 0, changes = 0;
size_t strOldSize, strGrowSize = 0;
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
int readonly = 1;
@@ -1126,7 +1173,7 @@ void bitfieldGeneric(client *c, int flags) {
/* Lookup by making room up to the farthest bit reached by
* this operation. */
if ((o = lookupStringForBitCommand(c,
highest_write_offset,&dirty)) == NULL) {
highest_write_offset,&strOldSize,&strGrowSize)) == NULL) {
zfree(ops);
return;
}
@@ -1176,7 +1223,7 @@ void bitfieldGeneric(client *c, int flags) {
setSignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
if (dirty || (oldval != newval))
if (strGrowSize || (oldval != newval))
changes++;
} else {
addReplyNull(c);
@@ -1210,7 +1257,7 @@ void bitfieldGeneric(client *c, int flags) {
setUnsignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
if (dirty || (oldval != newval))
if (strGrowSize || (oldval != newval))
changes++;
} else {
addReplyNull(c);
@@ -1253,6 +1300,14 @@ void bitfieldGeneric(client *c, int flags) {
}
if (changes) {
/* If this is not a new key (old size not 0) and size changed, then
* update the keysizes histogram. Otherwise, the histogram already
* updated in lookupStringForBitCommand() by calling dbAdd(). */
if ((strOldSize > 0) && (strGrowSize != 0))
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
strOldSize, strOldSize + strGrowSize);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty += changes;
+245 -1
View File
@@ -317,7 +317,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
}
/* Create the connection */
conn = connCreate(connTypeOfCluster());
conn = connCreate(server.el, connTypeOfCluster());
if (connBlockingConnect(conn, host->ptr, atoi(port->ptr), timeout)
!= C_OK) {
addReplyError(c,"-IOERR error or timeout connecting to the client");
@@ -783,6 +783,130 @@ unsigned int countKeysInSlot(unsigned int slot) {
return kvstoreDictSize(server.db->keys, slot);
}
/* Add detailed information of a node to the output buffer of the given client. */
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
int reply_count = 0;
char *hostname;
void *node_replylen = addReplyDeferredLen(c);
addReplyBulkCString(c, "id");
addReplyBulkCBuffer(c, clusterNodeGetName(node), CLUSTER_NAMELEN);
reply_count++;
if (clusterNodeTcpPort(node)) {
addReplyBulkCString(c, "port");
addReplyLongLong(c, clusterNodeTcpPort(node));
reply_count++;
}
if (clusterNodeTlsPort(node)) {
addReplyBulkCString(c, "tls-port");
addReplyLongLong(c, clusterNodeTlsPort(node));
reply_count++;
}
addReplyBulkCString(c, "ip");
addReplyBulkCString(c, clusterNodeIp(node));
reply_count++;
addReplyBulkCString(c, "endpoint");
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
reply_count++;
hostname = clusterNodeHostname(node);
if (hostname != NULL && *hostname != '\0') {
addReplyBulkCString(c, "hostname");
addReplyBulkCString(c, hostname);
reply_count++;
}
long long node_offset;
if (clusterNodeIsMyself(node)) {
node_offset = clusterNodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
} else {
node_offset = clusterNodeReplOffset(node);
}
addReplyBulkCString(c, "role");
addReplyBulkCString(c, clusterNodeIsSlave(node) ? "replica" : "master");
reply_count++;
addReplyBulkCString(c, "replication-offset");
addReplyLongLong(c, node_offset);
reply_count++;
addReplyBulkCString(c, "health");
const char *health_msg = NULL;
if (clusterNodeIsFailing(node)) {
health_msg = "fail";
} else if (clusterNodeIsSlave(node) && node_offset == 0) {
health_msg = "loading";
} else {
health_msg = "online";
}
addReplyBulkCString(c, health_msg);
reply_count++;
setDeferredMapLen(c, node_replylen, reply_count);
}
static clusterNode *clusterGetMasterFromShard(void *shard_handle) {
clusterNode *n = NULL;
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
while((n = clusterShardNodeIteratorNext(node_it)) != NULL) {
if (!clusterNodeIsFailing(n)) {
break;
}
}
clusterShardNodeIteratorFree(node_it);
if (!n) return NULL;
return clusterNodeGetMaster(n);
}
/* Add the shard reply of a single shard based off the given primary node. */
void addShardReplyForClusterShards(client *c, void *shard_handle) {
serverAssert(clusterGetShardNodeCount(shard_handle) > 0);
addReplyMapLen(c, 2);
addReplyBulkCString(c, "slots");
/* Use slot_info_pairs from the primary only */
clusterNode *master_node = clusterGetMasterFromShard(shard_handle);
if (master_node && clusterNodeHasSlotInfo(master_node)) {
serverAssert((clusterNodeSlotInfoCount(master_node) % 2) == 0);
addReplyArrayLen(c, clusterNodeSlotInfoCount(master_node));
for (int i = 0; i < clusterNodeSlotInfoCount(master_node); i++)
addReplyLongLong(c, (unsigned long)clusterNodeSlotInfoEntry(master_node, i));
} else {
/* If no slot info pair is provided, the node owns no slots */
addReplyArrayLen(c, 0);
}
addReplyBulkCString(c, "nodes");
addReplyArrayLen(c, clusterGetShardNodeCount(shard_handle));
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
for (clusterNode *n = clusterShardNodeIteratorNext(node_it); n != NULL; n = clusterShardNodeIteratorNext(node_it)) {
addNodeDetailsToShardReply(c, n);
clusterFreeNodesSlotsInfo(n);
}
clusterShardNodeIteratorFree(node_it);
}
/* Add to the output buffer of the given client, an array of slot (start, end)
* pair owned by the shard, also the primary and set of replica(s) along with
* information about each node. */
void clusterCommandShards(client *c) {
addReplyArrayLen(c, clusterGetShardCount());
/* This call will add slot_info_pairs to all nodes */
clusterGenNodesSlotsInfo(0);
dictIterator *shard_it = clusterGetShardIterator();
for(void *shard_handle = clusterNextShardHandle(shard_it); shard_handle != NULL; shard_handle = clusterNextShardHandle(shard_it)) {
addShardReplyForClusterShards(c, shard_handle);
}
clusterFreeShardIterator(shard_it);
}
void clusterCommandHelp(client *c) {
const char *help[] = {
"COUNTKEYSINSLOT <slot>",
@@ -1434,6 +1558,126 @@ void readonlyCommand(client *c) {
addReply(c,shared.ok);
}
void replySlotsFlushAndFree(client *c, SlotsFlush *sflush) {
addReplyArrayLen(c, sflush->numRanges);
for (int i = 0 ; i < sflush->numRanges ; i++) {
addReplyArrayLen(c, 2);
addReplyLongLong(c, sflush->ranges[i].first);
addReplyLongLong(c, sflush->ranges[i].last);
}
zfree(sflush);
}
/* Partially flush destination DB in a cluster node, based on the slot range.
*
* Usage: SFLUSH <start-slot> <end slot> [<start-slot> <end slot>]* [SYNC|ASYNC]
*
* This is an initial implementation of SFLUSH (slots flush) which is limited to
* flushing a single shard as a whole, but in the future the same command may be
* used to partially flush a shard based on hash slots. Currently only if provided
* slots cover entirely the slots of a node, the node will be flushed and the
* return value will be pairs of slot ranges. Otherwise, a single empty set will
* be returned. If possible, SFLUSH SYNC will be run as blocking ASYNC as an
* optimization.
*/
void sflushCommand(client *c) {
int flags = EMPTYDB_NO_FLAGS, argc = c->argc;
if (server.cluster_enabled == 0) {
addReplyError(c,"This instance has cluster support disabled");
return;
}
/* check if last argument is SYNC or ASYNC */
if (!strcasecmp(c->argv[c->argc-1]->ptr,"sync")) {
flags = EMPTYDB_NO_FLAGS;
argc--;
} else if (!strcasecmp(c->argv[c->argc-1]->ptr,"async")) {
flags = EMPTYDB_ASYNC;
argc--;
} else if (server.lazyfree_lazy_user_flush) {
flags = EMPTYDB_ASYNC;
}
/* parse the slot range */
if (argc % 2 == 0) {
addReplyErrorArity(c);
return;
}
/* Verify <first, last> slot pairs are valid and not overlapping */
long long j, first, last;
unsigned char slotsToFlushRq[CLUSTER_SLOTS] = {0};
for (j = 1; j < argc; j += 2) {
/* check if the first slot is valid */
if (getLongLongFromObject(c->argv[j], &first) != C_OK || first < 0 || first >= CLUSTER_SLOTS) {
addReplyError(c,"Invalid or out of range slot");
return;
}
/* check if the last slot is valid */
if (getLongLongFromObject(c->argv[j+1], &last) != C_OK || last < 0 || last >= CLUSTER_SLOTS) {
addReplyError(c,"Invalid or out of range slot");
return;
}
if (first > last) {
addReplyErrorFormat(c,"start slot number %lld is greater than end slot number %lld", first, last);
return;
}
/* Mark the slots in slotsToFlushRq[] */
for (int i = first; i <= last; i++) {
if (slotsToFlushRq[i]) {
addReplyErrorFormat(c, "Slot %d specified multiple times", i);
return;
}
slotsToFlushRq[i] = 1;
}
}
/* Verify slotsToFlushRq[] covers ALL slots of myNode. */
clusterNode *myNode = getMyClusterNode();
/* During iteration trace also the slot range pairs and save in SlotsFlush.
* It is allocated on heap since there is a chance that FLUSH SYNC will be
* running as blocking ASYNC and only later reply with slot ranges */
int capacity = 32; /* Initial capacity */
SlotsFlush *sflush = zmalloc(sizeof(SlotsFlush) + sizeof(SlotRange) * capacity);
sflush->numRanges = 0;
int inSlotRange = 0;
for (int i = 0; i < CLUSTER_SLOTS; i++) {
if (myNode == getNodeBySlot(i)) {
if (!slotsToFlushRq[i]) {
addReplySetLen(c, 0); /* Not all slots of mynode got covered. See sflushCommand() comment. */
zfree(sflush);
return;
}
if (!inSlotRange) { /* If start another slot range */
sflush->ranges[sflush->numRanges].first = i;
inSlotRange = 1;
}
} else {
if (inSlotRange) { /* If end another slot range */
sflush->ranges[sflush->numRanges++].last = i - 1;
inSlotRange = 0;
/* If reached 'sflush' capacity, double the capacity */
if (sflush->numRanges >= capacity) {
capacity *= 2;
sflush = zrealloc(sflush, sizeof(SlotsFlush) + sizeof(SlotRange) * capacity);
}
}
}
}
/* Update last pair if last cluster slot is also end of last range */
if (inSlotRange) sflush->ranges[sflush->numRanges++].last = CLUSTER_SLOTS - 1;
/* Flush selected slots. If not flush as blocking async, then reply immediately */
if (flushCommandCommon(c, FLUSH_TYPE_SLOTS, flags, sflush) == 0)
replySlotsFlushAndFree(c, sflush);
}
/* The READWRITE command just clears the READONLY command state. */
void readwriteCommand(client *c) {
if (server.cluster_enabled == 0) {
+20 -1
View File
@@ -97,7 +97,7 @@ int clusterManualFailoverTimeLimit(void);
void clusterCommandSlots(client * c);
void clusterCommandMyId(client *c);
void clusterCommandMyShardId(client *c);
void clusterCommandShards(client *c);
sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary);
int clusterNodeCoversSlot(clusterNode *n, int slot);
@@ -142,4 +142,23 @@ int isValidAuxString(char *s, unsigned int length);
void migrateCommand(client *c);
void clusterCommand(client *c);
ConnectionType *connTypeOfCluster(void);
void clusterGenNodesSlotsInfo(int filter);
void clusterFreeNodesSlotsInfo(clusterNode *n);
int clusterNodeSlotInfoCount(clusterNode *n);
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx);
int clusterNodeHasSlotInfo(clusterNode *n);
int clusterGetShardCount(void);
void *clusterGetShardIterator(void);
void *clusterNextShardHandle(void *shard_iterator);
void clusterFreeShardIterator(void *shard_iterator);
int clusterGetShardNodeCount(void *shard);
void *clusterShardHandleGetNodeIterator(void *shard);
clusterNode *clusterShardNodeIteratorNext(void *node_iterator);
void clusterShardNodeIteratorFree(void *node_iterator);
clusterNode *clusterShardNodeFirst(void *shard);
int clusterNodeTcpPort(clusterNode *node);
int clusterNodeTlsPort(clusterNode *node);
#endif /* __CLUSTER_H */
+119 -140
View File
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
/*
@@ -88,6 +93,7 @@ int auxTlsPortPresent(clusterNode *n);
static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
void freeClusterLink(clusterLink *link);
int verifyClusterNodeId(const char *name, int length);
static void updateShardId(clusterNode *node, const char *shard_id);
int getNodeDefaultClientPort(clusterNode *n) {
return server.tls_cluster ? n->tls_port : n->tcp_port;
@@ -198,12 +204,11 @@ int auxShardIdSetter(clusterNode *n, void *value, int length) {
return C_ERR;
}
memcpy(n->shard_id, value, CLUSTER_NAMELEN);
/* if n already has replicas, make sure they all agree
* on the shard id */
/* if n already has replicas, make sure they all use
* the primary shard id */
for (int i = 0; i < n->numslaves; i++) {
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) {
return C_ERR;
}
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
updateShardId(n->slaves[i], n->shard_id);
}
clusterAddNodeToShard(value, n);
return C_OK;
@@ -545,18 +550,12 @@ int clusterLoadConfig(char *filename) {
clusterAddNode(master);
}
/* shard_id can be absent if we are loading a nodes.conf generated
* by an older version of Redis; we should follow the primary's
* shard_id in this case */
if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
clusterAddNodeToShard(master->shard_id, n);
} else if (clusterGetNodesInMyShard(master) != NULL &&
memcmp(master->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
{
/* If the primary has been added to a shard, make sure this
* node has the same persisted shard id as the primary. */
goto fmterr;
}
* by an older version of Redis;
* ignore replica's shard_id in the file, only use the primary's.
* If replica precedes primary in file, it will be corrected
* later by the auxShardIdSetter */
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
clusterAddNodeToShard(master->shard_id, n);
n->slaveof = master;
clusterNodeAddSlave(master,n);
} else if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
@@ -634,6 +633,8 @@ int clusterLoadConfig(char *filename) {
}
/* Config sanity check */
if (server.cluster->myself == NULL) goto fmterr;
if (!(myself->flags & (CLUSTER_NODE_MASTER | CLUSTER_NODE_SLAVE))) goto fmterr;
if (nodeIsSlave(myself) && myself->slaveof == NULL) goto fmterr;
zfree(line);
fclose(fp);
@@ -901,22 +902,39 @@ static void updateAnnouncedHumanNodename(clusterNode *node, char *new) {
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
static void assignShardIdToNode(clusterNode *node, const char *shard_id, int flag) {
clusterRemoveNodeFromShard(node);
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
clusterAddNodeToShard(shard_id, node);
clusterDoBeforeSleep(flag);
}
static void updateShardId(clusterNode *node, const char *shard_id) {
if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
clusterRemoveNodeFromShard(node);
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
clusterAddNodeToShard(shard_id, node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
if (shard_id && myself != node && myself->slaveof == node) {
if (memcmp(myself->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
/* shard-id can diverge right after a rolling upgrade
* from pre-7.2 releases */
clusterRemoveNodeFromShard(myself);
memcpy(myself->shard_id, shard_id, CLUSTER_NAMELEN);
clusterAddNodeToShard(shard_id, myself);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG);
/* If the replica or master does not support shard-id (old version),
* we still need to make our best effort to keep their shard-id consistent.
*
* 1. Master supports but the replica does not.
* We might first update the replica's shard-id to the master's randomly
* generated shard-id. Then, when the master's shard-id arrives, we must
* also update all its replicas.
* 2. If the master does not support but the replica does.
* We also need to synchronize the master's shard-id with the replica.
* 3. If neither of master and replica supports it.
* The master will have a randomly generated shard-id and will update
* the replica to match the master's shard-id. */
if (node->slaveof == NULL) {
for (int i = 0; i < clusterNodeNumSlaves(node); i++) {
clusterNode *slavenode = clusterNodeGetSlave(node, i);
if (memcmp(slavenode->shard_id, shard_id, CLUSTER_NAMELEN) != 0)
assignShardIdToNode(slavenode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
}
} else {
clusterNode *masternode = node->slaveof;
if (memcmp(masternode->shard_id, shard_id, CLUSTER_NAMELEN) != 0)
assignShardIdToNode(masternode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
}
}
}
@@ -1244,7 +1262,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
connection *conn = connCreateAccepted(connTypeOfCluster(), cfd, &require_auth);
connection *conn = connCreateAccepted(server.el, connTypeOfCluster(), cfd, &require_auth);
/* Make sure connection is not in an error state */
if (connGetState(conn) != CONN_STATE_ACCEPTING) {
@@ -2577,9 +2595,6 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
extensions++;
if (hdr != NULL) {
if (extensions != 0) {
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA;
}
hdr->extensions = htons(extensions);
}
@@ -2769,6 +2784,9 @@ int clusterProcessPacket(clusterLink *link) {
}
sender = getNodeFromLinkAndMsg(link, hdr);
if (sender && (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA)) {
sender->flags |= CLUSTER_NODE_EXTENSIONS_SUPPORTED;
}
/* Update the last time we saw any data from this node. We
* use this in order to avoid detecting a timeout from a node that
@@ -3534,6 +3552,8 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) {
/* Set the message flags. */
if (clusterNodeIsMaster(myself) && server.cluster->mf_end)
hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED;
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; /* Always make other nodes know that
* this node supports extension data. */
hdr->totlen = htonl(msglen);
}
@@ -3612,7 +3632,9 @@ void clusterSendPing(clusterLink *link, int type) {
* to put inside the packet. */
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
estlen += writePingExt(NULL, 0);
if (link->node && nodeSupportsExtensions(link->node)) {
estlen += writePingExt(NULL, 0);
}
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
* sizeof(clusterMsg) or more. */
if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg);
@@ -3682,7 +3704,9 @@ void clusterSendPing(clusterLink *link, int type) {
/* Compute the actual total length and send! */
uint32_t totlen = 0;
totlen += writePingExt(hdr, gossipcount);
if (link->node && nodeSupportsExtensions(link->node)) {
totlen += writePingExt(hdr, gossipcount);
}
totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData);
totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
serverAssert(gossipcount < USHRT_MAX);
@@ -4559,7 +4583,7 @@ static int clusterNodeCronHandleReconnect(clusterNode *node, mstime_t handshake_
if (node->link == NULL) {
clusterLink *link = createClusterLink(node);
link->conn = connCreate(connTypeOfCluster());
link->conn = connCreate(server.el, connTypeOfCluster());
connSetPrivateData(link->conn, link);
if (connConnect(link->conn, node->ip, node->cport, server.bind_source_addr,
clusterLinkConnectHandler) == C_ERR) {
@@ -5582,113 +5606,68 @@ void clusterUpdateSlots(client *c, unsigned char *slots, int del) {
}
}
/* Add detailed information of a node to the output buffer of the given client. */
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
int reply_count = 0;
void *node_replylen = addReplyDeferredLen(c);
addReplyBulkCString(c, "id");
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
reply_count++;
if (node->tcp_port) {
addReplyBulkCString(c, "port");
addReplyLongLong(c, node->tcp_port);
reply_count++;
}
if (node->tls_port) {
addReplyBulkCString(c, "tls-port");
addReplyLongLong(c, node->tls_port);
reply_count++;
}
addReplyBulkCString(c, "ip");
addReplyBulkCString(c, node->ip);
reply_count++;
addReplyBulkCString(c, "endpoint");
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
reply_count++;
if (sdslen(node->hostname) != 0) {
addReplyBulkCString(c, "hostname");
addReplyBulkCBuffer(c, node->hostname, sdslen(node->hostname));
reply_count++;
}
long long node_offset;
if (node->flags & CLUSTER_NODE_MYSELF) {
node_offset = nodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
} else {
node_offset = node->repl_offset;
}
addReplyBulkCString(c, "role");
addReplyBulkCString(c, nodeIsSlave(node) ? "replica" : "master");
reply_count++;
addReplyBulkCString(c, "replication-offset");
addReplyLongLong(c, node_offset);
reply_count++;
addReplyBulkCString(c, "health");
const char *health_msg = NULL;
if (nodeFailed(node)) {
health_msg = "fail";
} else if (nodeIsSlave(node) && node_offset == 0) {
health_msg = "loading";
} else {
health_msg = "online";
}
addReplyBulkCString(c, health_msg);
reply_count++;
setDeferredMapLen(c, node_replylen, reply_count);
int clusterGetShardCount(void) {
return dictSize(server.cluster->shards);
}
/* Add the shard reply of a single shard based off the given primary node. */
void addShardReplyForClusterShards(client *c, list *nodes) {
serverAssert(listLength(nodes) > 0);
clusterNode *n = listNodeValue(listFirst(nodes));
addReplyMapLen(c, 2);
addReplyBulkCString(c, "slots");
/* Use slot_info_pairs from the primary only */
n = clusterNodeGetMaster(n);
if (n->slot_info_pairs != NULL) {
serverAssert((n->slot_info_pairs_count % 2) == 0);
addReplyArrayLen(c, n->slot_info_pairs_count);
for (int i = 0; i < n->slot_info_pairs_count; i++)
addReplyLongLong(c, (unsigned long)n->slot_info_pairs[i]);
} else {
/* If no slot info pair is provided, the node owns no slots */
addReplyArrayLen(c, 0);
}
addReplyBulkCString(c, "nodes");
addReplyArrayLen(c, listLength(nodes));
listIter li;
listRewind(nodes, &li);
for (listNode *ln = listNext(&li); ln != NULL; ln = listNext(&li)) {
clusterNode *n = listNodeValue(ln);
addNodeDetailsToShardReply(c, n);
clusterFreeNodesSlotsInfo(n);
}
void *clusterGetShardIterator(void) {
return dictGetSafeIterator(server.cluster->shards);
}
/* Add to the output buffer of the given client, an array of slot (start, end)
* pair owned by the shard, also the primary and set of replica(s) along with
* information about each node. */
void clusterCommandShards(client *c) {
addReplyArrayLen(c, dictSize(server.cluster->shards));
/* This call will add slot_info_pairs to all nodes */
clusterGenNodesSlotsInfo(0);
dictIterator *di = dictGetSafeIterator(server.cluster->shards);
for(dictEntry *de = dictNext(di); de != NULL; de = dictNext(di)) {
addShardReplyForClusterShards(c, dictGetVal(de));
}
dictReleaseIterator(di);
void *clusterNextShardHandle(void *shard_iterator) {
dictEntry *de = dictNext(shard_iterator);
if(de == NULL) return NULL;
return dictGetVal(de);
}
void clusterFreeShardIterator(void *shard_iterator) {
dictReleaseIterator(shard_iterator);
}
int clusterNodeHasSlotInfo(clusterNode *n) {
return n->slot_info_pairs != NULL;
}
int clusterNodeSlotInfoCount(clusterNode *n) {
return n->slot_info_pairs_count;
}
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx) {
return n->slot_info_pairs[idx];
}
int clusterGetShardNodeCount(void *shard) {
return listLength((list*)shard);
}
void *clusterShardHandleGetNodeIterator(void *shard) {
listIter *li = zmalloc(sizeof(listIter));
listRewind((list*)shard, li);
return li;
}
void clusterShardNodeIteratorFree(void *node_iterator) {
zfree(node_iterator);
}
clusterNode *clusterShardNodeIteratorNext(void *node_iterator) {
listNode *item = listNext((listIter*)node_iterator);
if (item == NULL) return NULL;
return listNodeValue(item);
}
clusterNode *clusterShardNodeFirst(void *shard) {
listNode *item = listFirst((list*)shard);
if (item == NULL) return NULL;
return listNodeValue(item);
}
int clusterNodeTcpPort(clusterNode *node) {
return node->tcp_port;
}
int clusterNodeTlsPort(clusterNode *node) {
return node->tls_port;
}
sds genClusterInfoString(void) {
+15
View File
@@ -1,3 +1,16 @@
/*
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#ifndef CLUSTER_LEGACY_H
#define CLUSTER_LEGACY_H
@@ -51,6 +64,7 @@ typedef struct clusterLink {
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
#define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
@@ -59,6 +73,7 @@ typedef struct clusterLink {
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
/* This structure represent elements of node->fail_reports. */
typedef struct clusterNodeFailReport {
+13 -6
View File
@@ -1239,6 +1239,9 @@ commandHistory CLIENT_LIST_History[] = {
{"6.2.0","Added `argv-mem`, `tot-mem`, `laddr` and `redir` fields and the optional `ID` filter."},
{"7.0.0","Added `resp`, `multi-mem`, `rbs` and `rbp` fields."},
{"7.0.3","Added `ssub` field."},
{"7.2.0","Added `lib-name` and `lib-ver` fields."},
{"7.4.0","Added `watch` field."},
{"8.0.0","Added `io-thread` field."},
};
#endif
@@ -1546,7 +1549,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = {
{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)},
{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)},
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,9,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args},
{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args},
{MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args},
@@ -3778,7 +3781,9 @@ struct COMMAND_ARG HPEXPIRETIME_Args[] = {
#ifndef SKIP_CMD_TIPS_TABLE
/* HPTTL tips */
#define HPTTL_Tips NULL
const char *HPTTL_Tips[] = {
"nondeterministic_output",
};
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
@@ -3956,7 +3961,9 @@ struct COMMAND_ARG HSTRLEN_Args[] = {
#ifndef SKIP_CMD_TIPS_TABLE
/* HTTL tips */
#define HTTL_Tips NULL
const char *HTTL_Tips[] = {
"nondeterministic_output",
};
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
@@ -11044,13 +11051,13 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,1,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,1,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
{MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args},
/* hyperloglog */
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
@@ -11141,7 +11148,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("sintercard","Returns the number of members of the intersect of multiple sets.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERCARD_History,0,SINTERCARD_Tips,0,sinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SET,SINTERCARD_Keyspecs,1,sintercardGetKeys,3),.args=SINTERCARD_Args},
{MAKE_CMD("sinterstore","Stores the intersect of multiple sets in a key.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERSTORE_History,0,SINTERSTORE_Tips,0,sinterstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SINTERSTORE_Keyspecs,2,NULL,2),.args=SINTERSTORE_Args},
{MAKE_CMD("sismember","Determines whether a member belongs to a set.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SISMEMBER_History,0,SISMEMBER_Tips,0,sismemberCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SISMEMBER_Keyspecs,1,NULL,2),.args=SISMEMBER_Args},
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,sinterCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,smembersCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
{MAKE_CMD("smismember","Determines whether multiple members belong to a set.","O(N) where N is the number of elements being checked for membership","6.2.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMISMEMBER_History,0,SMISMEMBER_Tips,0,smismemberCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SMISMEMBER_Keyspecs,1,NULL,2),.args=SMISMEMBER_Args},
{MAKE_CMD("smove","Moves a member from one set to another.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMOVE_History,0,SMOVE_Tips,0,smoveCommand,4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SMOVE_Keyspecs,2,NULL,3),.args=SMOVE_Args},
{MAKE_CMD("spop","Returns one or more random members from a set after removing them. Deletes the set if the last member was popped.","Without the count argument O(1), otherwise O(N) where N is the value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SPOP_History,1,SPOP_Tips,1,spopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SPOP_Keyspecs,1,NULL,2),.args=SPOP_Args},
+12
View File
@@ -31,6 +31,18 @@
[
"7.0.3",
"Added `ssub` field."
],
[
"7.2.0",
"Added `lib-name` and `lib-ver` fields."
],
[
"7.4.0",
"Added `watch` field."
],
[
"8.0.0",
"Added `io-thread` field."
]
],
"command_flags": [
+3
View File
@@ -14,6 +14,9 @@
"acl_categories": [
"HASH"
],
"command_tips": [
"NONDETERMINISTIC_OUTPUT"
],
"key_specs": [
{
"flags": [
+3
View File
@@ -14,6 +14,9 @@
"acl_categories": [
"HASH"
],
"command_tips": [
"NONDETERMINISTIC_OUTPUT"
],
"key_specs": [
{
"flags": [
+75
View File
@@ -0,0 +1,75 @@
{
"SFLUSH": {
"summary": "Remove all keys from selected range of slots.",
"complexity": "O(N)+O(k) where N is the number of keys and k is the number of slots.",
"group": "server",
"since": "8.0.0",
"arity": -3,
"function": "sflushCommand",
"command_flags": [
"WRITE",
"EXPERIMENTAL"
],
"acl_categories": [
"KEYSPACE",
"DANGEROUS"
],
"command_tips": [
],
"reply_schema": {
"description": "List of slot ranges",
"type": "array",
"minItems": 0,
"maxItems": 4294967295,
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"description": "start slot number",
"type": "integer"
},
{
"description": "end slot number",
"type": "integer"
}
]
}
},
"arguments": [
{
"name": "data",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "slot-start",
"type": "integer"
},
{
"name": "slot-last",
"type": "integer"
}
]
},
{
"name": "flush-type",
"type": "oneof",
"optional": true,
"arguments": [
{
"name": "async",
"type": "pure-token",
"token": "ASYNC"
},
{
"name": "sync",
"type": "pure-token",
"token": "SYNC"
}
]
}
]
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"group": "set",
"since": "1.0.0",
"arity": 2,
"function": "sinterCommand",
"function": "smembersCommand",
"command_flags": [
"READONLY"
],
+15 -6
View File
@@ -430,6 +430,7 @@ void loadServerConfigFromString(char *config) {
{"list-max-ziplist-entries", 2, 2},
{"list-max-ziplist-value", 2, 2},
{"lua-replicate-commands", 2, 2},
{"io-threads-do-reads", 2, 2},
{NULL, 0},
};
char buf[1024];
@@ -2502,6 +2503,13 @@ static int updateWatchdogPeriod(const char **err) {
}
static int updateAppendonly(const char **err) {
/* If loading flag is set, AOF might have been stopped temporarily, and it
* will be restarted depending on server.aof_enabled flag after loading is
* completed. So, we just need to update 'server.aof_enabled' which has been
* updated already before calling this function. */
if (server.loading)
return 1;
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
stopAppendOnly();
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
@@ -2540,11 +2548,10 @@ static int updateMaxclients(const char **err) {
*err = msg;
return 0;
}
if ((unsigned int) aeGetSetSize(server.el) <
server.maxclients + CONFIG_FDSET_INCR)
{
if (aeResizeSetSize(server.el,
server.maxclients + CONFIG_FDSET_INCR) == AE_ERR)
size_t newsize = server.maxclients + CONFIG_FDSET_INCR;
if ((unsigned int) aeGetSetSize(server.el) < newsize) {
if (aeResizeSetSize(server.el, newsize) == AE_ERR ||
resizeAllIOThreadsEventLoops(newsize) == AE_ERR)
{
*err = "The event loop API used by Redis is not able to handle the specified number of clients";
return 0;
@@ -3025,6 +3032,7 @@ static int applyClientMaxMemoryUsage(const char **err) {
if (server.maxmemory_clients != 0)
initServerClientMemUsageBuckets();
pauseAllIOThreads();
/* When client eviction is enabled update memory buckets for all clients.
* When disabled, clear that data structure. */
listRewind(server.clients, &li);
@@ -3038,6 +3046,7 @@ static int applyClientMaxMemoryUsage(const char **err) {
updateClientMemUsageAndBucket(c);
}
}
resumeAllIOThreads();
if (server.maxmemory_clients == 0)
freeServerClientMemUsageBuckets();
@@ -3080,7 +3089,7 @@ standardConfig static_configs[] = {
createBoolConfig("activedefrag", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL),
createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL),
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL),
createBoolConfig("cluster-allow-pubsubshard-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_pubsubshard_when_down, 1, NULL, NULL),
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled),
+20
View File
@@ -32,6 +32,14 @@
#define redis_stat stat
#endif
#ifndef CACHE_LINE_SIZE
#if defined(__aarch64__) && defined(__APPLE__)
#define CACHE_LINE_SIZE 128
#else
#define CACHE_LINE_SIZE 64
#endif
#endif
/* Test for proc filesystem */
#ifdef __linux__
#define HAVE_PROC_STAT 1
@@ -39,6 +47,7 @@
#define HAVE_PROC_SMAPS 1
#define HAVE_PROC_SOMAXCONN 1
#define HAVE_PROC_OOM_SCORE_ADJ 1
#define HAVE_EVENT_FD 1
#endif
/* Test for task_info() */
@@ -299,4 +308,15 @@ void setcpuaffinity(const char *cpulist);
#define HAVE_FADVISE
#endif
#if defined(__x86_64__) && ((defined(__GNUC__) && __GNUC__ > 5) || (defined(__clang__)))
#if defined(__has_attribute) && __has_attribute(target)
#define HAVE_POPCNT
#define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt")))
#else
#define ATTRIBUTE_TARGET_POPCNT
#endif
#else
#define ATTRIBUTE_TARGET_POPCNT
#endif
#endif
+4 -4
View File
@@ -156,14 +156,14 @@ void connTypeCleanupAll(void) {
}
/* walk all the connection types until has pending data */
int connTypeHasPendingData(void) {
int connTypeHasPendingData(struct aeEventLoop *el) {
ConnectionType *ct;
int type;
int ret = 0;
for (type = 0; type < CONN_TYPE_MAX; type++) {
ct = connTypes[type];
if (ct && ct->has_pending_data && (ret = ct->has_pending_data())) {
if (ct && ct->has_pending_data && (ret = ct->has_pending_data(el))) {
return ret;
}
}
@@ -172,7 +172,7 @@ int connTypeHasPendingData(void) {
}
/* walk all the connection types and process pending data for each connection type */
int connTypeProcessPendingData(void) {
int connTypeProcessPendingData(struct aeEventLoop *el) {
ConnectionType *ct;
int type;
int ret = 0;
@@ -180,7 +180,7 @@ int connTypeProcessPendingData(void) {
for (type = 0; type < CONN_TYPE_MAX; type++) {
ct = connTypes[type];
if (ct && ct->process_pending_data) {
ret += ct->process_pending_data();
ret += ct->process_pending_data(el);
}
}
+37 -10
View File
@@ -60,8 +60,8 @@ typedef struct ConnectionType {
int (*listen)(connListener *listener);
/* create/shutdown/close connection */
connection* (*conn_create)(void);
connection* (*conn_create_accepted)(int fd, void *priv);
connection* (*conn_create)(struct aeEventLoop *el);
connection* (*conn_create_accepted)(struct aeEventLoop *el, int fd, void *priv);
void (*shutdown)(struct connection *conn);
void (*close)(struct connection *conn);
@@ -81,9 +81,13 @@ typedef struct ConnectionType {
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
/* event loop */
void (*unbind_event_loop)(struct connection *conn);
int (*rebind_event_loop)(struct connection *conn, aeEventLoop *el);
/* pending data */
int (*has_pending_data)(void);
int (*process_pending_data)(void);
int (*has_pending_data)(struct aeEventLoop *el);
int (*process_pending_data)(struct aeEventLoop *el);
/* TLS specified methods */
sds (*get_peer_cert)(struct connection *conn);
@@ -98,6 +102,7 @@ struct connection {
short int refs;
unsigned short int iovcnt;
void *private_data;
struct aeEventLoop *el;
ConnectionCallbackFunc conn_handler;
ConnectionCallbackFunc write_handler;
ConnectionCallbackFunc read_handler;
@@ -319,6 +324,28 @@ static inline int connHasReadHandler(connection *conn) {
return conn->read_handler != NULL;
}
/* Returns true if the connection is bound to an event loop */
static inline int connHasEventLoop(connection *conn) {
return conn->el != NULL;
}
/* Unbind the current event loop from the connection, so that it can be
* rebind to a different event loop in the future. */
static inline void connUnbindEventLoop(connection *conn) {
if (conn->el == NULL) return;
connSetReadHandler(conn, NULL);
connSetWriteHandler(conn, NULL);
if (conn->type->unbind_event_loop)
conn->type->unbind_event_loop(conn);
conn->el = NULL;
}
/* Rebind the connection to another event loop, read/write handlers must not
* be installed in the current event loop */
static inline int connRebindEventLoop(connection *conn, aeEventLoop *el) {
return conn->type->rebind_event_loop(conn, el);
}
/* Associate a private data pointer with the connection */
static inline void connSetPrivateData(connection *conn, void *data) {
conn->private_data = data;
@@ -379,14 +406,14 @@ ConnectionType *connectionTypeUnix(void);
int connectionIndexByType(const char *typename);
/* Create a connection of specified type */
static inline connection *connCreate(ConnectionType *ct) {
return ct->conn_create();
static inline connection *connCreate(struct aeEventLoop *el, ConnectionType *ct) {
return ct->conn_create(el);
}
/* Create an accepted connection of specified type.
* priv is connection type specified argument */
static inline connection *connCreateAccepted(ConnectionType *ct, int fd, void *priv) {
return ct->conn_create_accepted(fd, priv);
static inline connection *connCreateAccepted(struct aeEventLoop *el, ConnectionType *ct, int fd, void *priv) {
return ct->conn_create_accepted(el, fd, priv);
}
/* Configure a connection type. A typical case is to configure TLS.
@@ -400,10 +427,10 @@ static inline int connTypeConfigure(ConnectionType *ct, void *priv, int reconfig
void connTypeCleanupAll(void);
/* Test all the connection type has pending data or not. */
int connTypeHasPendingData(void);
int connTypeHasPendingData(struct aeEventLoop *el);
/* walk all the connection types and process pending data for each connection type */
int connTypeProcessPendingData(void);
int connTypeProcessPendingData(struct aeEventLoop *el);
/* Listen on an initialized listener */
static inline int connListen(connListener *listener) {
+307 -89
View File
@@ -21,9 +21,12 @@
* C-level DB API
*----------------------------------------------------------------------------*/
static_assert(MAX_KEYSIZES_TYPES == OBJ_TYPE_BASIC_MAX, "Must be equal");
/* Flags for expireIfNeeded */
#define EXPIRE_FORCE_DELETE_EXPIRED 1
#define EXPIRE_AVOID_DELETE_EXPIRED 2
#define EXPIRE_ALLOW_ACCESS_EXPIRED 4
/* Return values for expireIfNeeded */
typedef enum {
@@ -45,10 +48,55 @@ void updateLFU(robj *val) {
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}
/*
* Update histogram of keys-sizes
*
* It is used to track the distribution of key sizes in the dataset. It is updated
* every time key's length is modified. Available to user via INFO command.
*
* The histogram is a base-2 logarithmic histogram, with 64 bins. The i'th bin
* represents the number of keys with a size in the range 2^i and 2^(i+1)
* exclusive. oldLen/newLen must be smaller than 2^48, and if their value
* equals 0, it means that the key is being created/deleted, respectively. Each
* data type has its own histogram and it is per database (In addition, there is
* histogram per slot for future cluster use).
*
* Examples to LEN values and corresponding bins in histogram:
* [1,2)->0 [2,4)->1 [4,8)->2 [8,16)->3
*/
void updateKeysizesHist(redisDb *db, int didx, uint32_t type, uint64_t oldLen, uint64_t newLen) {
if(unlikely(type >= OBJ_TYPE_BASIC_MAX))
return;
kvstoreDictMetadata *dictMeta = kvstoreGetDictMetadata(db->keys, didx);
kvstoreMetadata *kvstoreMeta = kvstoreGetMetadata(db->keys);
if (oldLen != 0) {
int old_bin = log2ceil(oldLen);
debugServerAssertWithInfo(server.current_client, NULL, old_bin < MAX_KEYSIZES_BINS);
/* If following a key deletion it is last one in slot's dict, then
* slot's dict might get released as well. Verify if metadata is not NULL. */
if(dictMeta) dictMeta->keysizes_hist[type][old_bin]--;
kvstoreMeta->keysizes_hist[type][old_bin]--;
}
if (newLen != 0) {
int new_bin = log2ceil(newLen);
debugServerAssertWithInfo(server.current_client, NULL, new_bin < MAX_KEYSIZES_BINS);
/* If following a key deletion it is last one in slot's dict, then
* slot's dict might get released as well. Verify if metadata is not NULL. */
if(dictMeta) dictMeta->keysizes_hist[type][new_bin]++;
kvstoreMeta->keysizes_hist[type][new_bin]++;
}
}
/* Lookup a key for read or write operations, or return NULL if the key is not
* found in the specified DB. This function implements the functionality of
* lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants.
*
* 'deref' is an optional output dictEntry reference argument, to get the
* associated dictEntry* of the key in case the key is found.
*
* Side-effects of calling this function:
*
* 1. A key gets expired if it reached it's TTL.
@@ -72,7 +120,7 @@ void updateLFU(robj *val) {
* Even if the key expiry is master-driven, we can correctly report a key is
* expired on replicas even if the master is lagging expiring our key via DELs
* in the replication link. */
robj *lookupKey(redisDb *db, robj *key, int flags) {
robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
dictEntry *de = dbFind(db, key->ptr);
robj *val = NULL;
if (de) {
@@ -91,6 +139,8 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
expire_flags |= EXPIRE_FORCE_DELETE_EXPIRED;
if (flags & LOOKUP_NOEXPIRE)
expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED;
if (flags & LOOKUP_ACCESS_EXPIRED)
expire_flags |= EXPIRE_ALLOW_ACCESS_EXPIRED;
if (expireIfNeeded(db, key, expire_flags) != KEY_VALID) {
/* The key is no longer valid. */
val = NULL;
@@ -102,7 +152,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (server.current_client && server.current_client->flags & CLIENT_NO_TOUCH &&
server.current_client->cmd->proc != touchCommand)
server.executing_client->cmd->proc != touchCommand)
flags |= LOOKUP_NOTOUCH;
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
@@ -123,6 +173,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* TODO: Use separate misses stats and notify event for WRITE */
}
if (val && deref) *deref = de;
return val;
}
@@ -137,7 +188,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* the key. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
serverAssert(!(flags & LOOKUP_WRITE));
return lookupKey(db, key, flags);
return lookupKey(db, key, flags, NULL);
}
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
@@ -153,13 +204,20 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
return lookupKey(db, key, flags | LOOKUP_WRITE);
return lookupKey(db, key, flags | LOOKUP_WRITE, NULL);
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
/* Like lookupKeyWrite(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dbFind call.
*/
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) {
return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref);
}
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReplyOrErrorObject(c, reply);
@@ -191,6 +249,7 @@ static dictEntry *dbAddInternal(redisDb *db, robj *key, robj *val, int update_if
kvstoreDictSetVal(db->keys, slot, de, val);
signalKeyAsReady(db, key, val->type);
notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id);
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val)); /* add hist */
return de;
}
@@ -236,6 +295,7 @@ int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
int slot = getKeySlot(key);
dictEntry *de = kvstoreDictAddRaw(db->keys, slot, key, NULL);
if (de == NULL) return 0;
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val)); /* add hist */
initObjectLRUOrLFU(val);
kvstoreDictSetVal(db->keys, slot, de, val);
return 1;
@@ -259,6 +319,9 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEnt
serverAssertWithInfo(NULL,key,de != NULL);
robj *old = dictGetVal(de);
/* Remove old key from keysizes histogram */
updateKeysizesHist(db, slot, old->type, getObjectLength(old), 0); /* remove hist */
val->lru = old->lru;
if (overwrite) {
@@ -277,6 +340,9 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEnt
}
kvstoreDictSetVal(db->keys, slot, de, val);
/* Add new key to keysizes histogram */
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val));
/* if hash with HFEs, take care to remove from global HFE DS */
if (old->type == OBJ_HASH)
hashTypeRemoveFromExpires(&db->hexpires, old);
@@ -294,6 +360,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
dbSetValue(db, key, val, 0, NULL);
}
/* Replace an existing key with a new value, we just replace value and don't
* emit any events.
* The dictEntry input is optional, can be used if we already have one.
*/
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) {
dbSetValue(db, key, val, 0, de);
}
/* High level Set operation. This function can be used in order to set
* a key, whatever it was existing or not, to a new object.
*
@@ -308,6 +382,12 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
* The client 'c' argument may be set to NULL if the operation is performed
* in a context where there is no clear client performing the operation. */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
setKeyWithDictEntry(c,db,key,val,flags,NULL);
}
/* Like setKey(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dictFind call. */
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) {
int keyfound = 0;
if (flags & SETKEY_ALREADY_EXIST)
@@ -322,7 +402,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
} else if (keyfound<0) {
dbAddInternal(db,key,val,1);
} else {
dbSetValue(db,key,val,1,NULL);
dbSetValue(db,key,val,1,de);
}
incrRefCount(val);
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);
@@ -347,23 +427,22 @@ robj *dbRandomKey(redisDb *db) {
key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key));
if (dbFindExpires(db, key)) {
if (allvolatile && server.masterhost && --maxtries == 0) {
/* If the DB is composed only of keys with an expire set,
* it could happen that all the keys are already logically
* expired in the slave, so the function cannot stop because
* expireIfNeeded() is false, nor it can stop because
* dictGetFairRandomKey() returns NULL (there are keys to return).
* To prevent the infinite loop we do some tries, but if there
* are the conditions for an infinite loop, eventually we
* return a key name that may be already expired. */
return keyobj;
}
if (expireIfNeeded(db,keyobj,0) != KEY_VALID) {
decrRefCount(keyobj);
continue; /* search for another key. This expired. */
}
if (allvolatile && server.masterhost && --maxtries == 0) {
/* If the DB is composed only of keys with an expire set,
* it could happen that all the keys are already logically
* expired in the slave, so the function cannot stop because
* expireIfNeeded() is false, nor it can stop because
* dictGetFairRandomKey() returns NULL (there are keys to return).
* To prevent the infinite loop we do some tries, but if there
* are the conditions for an infinite loop, eventually we
* return a key name that may be already expired. */
return keyobj;
}
if (expireIfNeeded(db,keyobj,0) != KEY_VALID) {
decrRefCount(keyobj);
continue; /* search for another key. This expired. */
}
return keyobj;
}
}
@@ -377,6 +456,9 @@ int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {
if (de) {
robj *val = dictGetVal(de);
/* remove key from histogram */
updateKeysizesHist(db, slot, val->type, getObjectLength(val), 0);
/* If hash object with expiry on fields, remove it from HFE DS of DB */
if (val->type == OBJ_HASH)
hashTypeRemoveFromExpires(&db->hexpires, val);
@@ -452,12 +534,18 @@ int dbDelete(redisDb *db, robj *key) {
* using an sdscat() call to append some data, or anything else.
*/
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
return dbUnshareStringValueWithDictEntry(db,key,o,NULL);
}
/* Like dbUnshareStringValue(), but accepts a optional dictEntry,
* which can be used if we already have one, thus saving the dbFind call. */
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) {
serverAssert(o->type == OBJ_STRING);
if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplaceValue(db,key,o);
dbReplaceValueWithDictEntry(db,key,o,de);
}
return o;
}
@@ -566,7 +654,8 @@ redisDb *initTempDb(void) {
redisDb *tempDb = zcalloc(sizeof(redisDb)*server.dbnum);
for (int i=0; i<server.dbnum; i++) {
tempDb[i].id = i;
tempDb[i].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
tempDb[i].keys = kvstoreCreate(&dbDictType, slot_count_bits,
flags | KVSTORE_ALLOC_META_KEYS_HIST);
tempDb[i].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
tempDb[i].hexpires = ebCreate();
}
@@ -575,11 +664,11 @@ redisDb *initTempDb(void) {
}
/* Discard tempDb, this can be slow (similar to FLUSHALL), but it's always async. */
void discardTempDb(redisDb *tempDb, void(callback)(dict*)) {
void discardTempDb(redisDb *tempDb) {
int async = 1;
/* Release temp DBs. */
emptyDbStructure(tempDb, -1, async, callback);
emptyDbStructure(tempDb, -1, async, NULL);
for (int i=0; i<server.dbnum; i++) {
/* Destroy global HFE DS before deleting the hashes since ebuckets DS is
* embedded in the stored objects. */
@@ -696,9 +785,12 @@ void flushAllDataAndResetRDB(int flags) {
#endif
}
/* Optimized FLUSHALL\FLUSHDB SYNC command finished to run by lazyfree thread */
void flushallSyncBgDone(uint64_t client_id) {
/* CB function on blocking ASYNC FLUSH completion
*
* Utilized by commands SFLUSH, FLUSHALL and FLUSHDB.
*/
void flushallSyncBgDone(uint64_t client_id, void *sflush) {
SlotsFlush *slotsFlush = sflush;
client *c = lookupClientByID(client_id);
/* Verify that client still exists */
@@ -711,8 +803,11 @@ void flushallSyncBgDone(uint64_t client_id) {
/* Don't update blocked_us since command was processed in bg by lazy_free thread */
updateStatsOnUnblock(c, 0 /*blocked_us*/, elapsedUs(c->bstate.lazyfreeStartTime), 0);
/* lazyfree bg job always succeed */
addReply(c, shared.ok);
/* Only SFLUSH command pass pointer to `SlotsFlush` */
if (slotsFlush)
replySlotsFlushAndFree(c, slotsFlush);
else
addReply(c, shared.ok);
/* mark client as unblocked */
unblockClient(c, 1);
@@ -727,10 +822,17 @@ void flushallSyncBgDone(uint64_t client_id) {
server.current_client = old_client;
}
void flushCommandCommon(client *c, int isFlushAll) {
int blocking_async = 0; /* FLUSHALL\FLUSHDB SYNC opt to run as blocking ASYNC */
int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
/* Common flush command implementation for FLUSHALL and FLUSHDB.
*
* Return 1 indicates that flush SYNC is actually running in bg as blocking ASYNC
* Return 0 otherwise
*
* sflush - provided only by SFLUSH command, otherwise NULL. Will be used on
* completion to reply with the slots flush result. Ownership is passed
* to the completion job in case of `blocking_async`.
*/
int flushCommandCommon(client *c, int type, int flags, SlotsFlush *sflush) {
int blocking_async = 0; /* Flush SYNC option to run as blocking ASYNC */
/* in case of SYNC, check if we can optimize and run it in bg as blocking ASYNC */
if ((!(flags & EMPTYDB_ASYNC)) && (!(c->flags & CLIENT_AVOID_BLOCKING_ASYNC_FLUSH))) {
@@ -739,7 +841,7 @@ void flushCommandCommon(client *c, int isFlushAll) {
blocking_async = 1;
}
if (isFlushAll)
if (type == FLUSH_TYPE_ALL)
flushAllDataAndResetRDB(flags | EMPTYDB_NOFUNCTIONS);
else
server.dirty += emptyData(c->db->id,flags | EMPTYDB_NOFUNCTIONS,NULL);
@@ -757,10 +859,9 @@ void flushCommandCommon(client *c, int isFlushAll) {
c->bstate.timeout = 0;
blockClient(c,BLOCKED_LAZYFREE);
bioCreateCompRq(BIO_WORKER_LAZY_FREE, flushallSyncBgDone, c->id);
} else {
addReply(c, shared.ok);
bioCreateCompRq(BIO_WORKER_LAZY_FREE, flushallSyncBgDone, c->id, sflush);
}
#if defined(USE_JEMALLOC)
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
* for large databases, flushdb blocks for long anyway, so a bit more won't
@@ -768,7 +869,7 @@ void flushCommandCommon(client *c, int isFlushAll) {
*
* Take care purge only FLUSHDB for sync flow. FLUSHALL sync flow already
* applied at flushAllDataAndResetRDB. Async flow will apply only later on */
if ((!isFlushAll) && (!(flags & EMPTYDB_ASYNC))) {
if ((type != FLUSH_TYPE_ALL) && (!(flags & EMPTYDB_ASYNC))) {
/* Only clear the current thread cache.
* Ignore the return call since this will fail if the tcache is disabled. */
je_mallctl("thread.tcache.flush", NULL, NULL, NULL, 0);
@@ -776,20 +877,32 @@ void flushCommandCommon(client *c, int isFlushAll) {
jemalloc_purge();
}
#endif
return blocking_async;
}
/* FLUSHALL [SYNC|ASYNC]
*
* Flushes the whole server data set. */
void flushallCommand(client *c) {
flushCommandCommon(c, 1);
int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
/* If FLUSH SYNC isn't running as blocking async, then reply */
if (flushCommandCommon(c, FLUSH_TYPE_ALL, flags, NULL) == 0)
addReply(c, shared.ok);
}
/* FLUSHDB [SYNC|ASYNC]
*
* Flushes the currently SELECTed Redis DB. */
void flushdbCommand(client *c) {
flushCommandCommon(c, 0);
int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
/* If FLUSH SYNC isn't running as blocking async, then reply */
if (flushCommandCommon(c, FLUSH_TYPE_DB,flags, NULL) == 0)
addReply(c, shared.ok);
}
/* This command implements DEL and UNLINK. */
@@ -944,11 +1057,10 @@ void scanCallback(void *privdata, const dictEntry *de) {
serverAssert(!((data->type != LLONG_MAX) && o));
/* Filter an element if it isn't the type we want. */
/* TODO: uncomment in redis 8.0
if (!o && data->type != LLONG_MAX) {
robj *rval = dictGetVal(de);
if (!objectTypeCompare(rval, data->type)) return;
}*/
}
/* Filter element if it does not match the pattern. */
void *keyStr = dictGetKey(de);
@@ -1095,9 +1207,8 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
typename = c->argv[i+1]->ptr;
type = getObjectTypeByName(typename);
if (type == LLONG_MAX) {
/* TODO: uncomment in redis 8.0
addReplyErrorFormat(c, "unknown type name '%s'", typename);
return; */
return;
}
i+= 2;
} else if (!strcasecmp(c->argv[i]->ptr, "novalues")) {
@@ -1194,50 +1305,95 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
}
} while (cursor && maxiterations-- && data.sampled < count);
} else if (o->type == OBJ_SET) {
unsigned long array_reply_len = 0;
void *replylen = NULL;
listRelease(keys);
char *str;
char buf[LONG_STR_SIZE];
size_t len;
int64_t llele;
/* Reply to the client. */
addReplyArrayLen(c, 2);
/* Cursor is always 0 given we iterate over all set */
addReplyBulkLongLong(c,0);
/* If there is no pattern the length is the entire set size, otherwise we defer the reply size */
if (use_pattern)
replylen = addReplyDeferredLen(c);
else {
array_reply_len = setTypeSize(o);
addReplyArrayLen(c, array_reply_len);
}
setTypeIterator *si = setTypeInitIterator(o);
unsigned long cur_length = 0;
while (setTypeNext(si, &str, &len, &llele) != -1) {
if (str == NULL) {
len = ll2string(buf, sizeof(buf), llele);
}
char *key = str ? str : buf;
if (use_pattern && !stringmatchlen(pat, sdslen(pat), key, len, 0)) {
if (use_pattern && !stringmatchlen(pat, patlen, key, len, 0)) {
continue;
}
listAddNodeTail(keys, sdsnewlen(key, len));
addReplyBulkCBuffer(c, key, len);
cur_length++;
}
setTypeReleaseIterator(si);
cursor = 0;
if (use_pattern)
setDeferredArrayLen(c,replylen,cur_length);
else
serverAssert(cur_length == array_reply_len); /* fail on corrupt data */
return;
} else if ((o->type == OBJ_HASH || o->type == OBJ_ZSET) &&
o->encoding == OBJ_ENCODING_LISTPACK)
{
unsigned char *p = lpFirst(o->ptr);
unsigned char *str;
int64_t len;
unsigned long array_reply_len = 0;
unsigned char intbuf[LP_INTBUF_SIZE];
void *replylen = NULL;
listRelease(keys);
/* Reply to the client. */
addReplyArrayLen(c, 2);
/* Cursor is always 0 given we iterate over all set */
addReplyBulkLongLong(c,0);
/* If there is no pattern the length is the entire set size, otherwise we defer the reply size */
if (use_pattern)
replylen = addReplyDeferredLen(c);
else {
array_reply_len = o->type == OBJ_HASH ? hashTypeLength(o, 0) : zsetLength(o);
if (!no_values) {
array_reply_len *= 2;
}
addReplyArrayLen(c, array_reply_len);
}
unsigned long cur_length = 0;
while(p) {
str = lpGet(p, &len, intbuf);
/* point to the value */
p = lpNext(o->ptr, p);
if (use_pattern && !stringmatchlen(pat, sdslen(pat), (char *)str, len, 0)) {
if (use_pattern && !stringmatchlen(pat, patlen, (char *)str, len, 0)) {
/* jump to the next key/val pair */
p = lpNext(o->ptr, p);
continue;
}
/* add key object */
listAddNodeTail(keys, sdsnewlen(str, len));
addReplyBulkCBuffer(c, str, len);
cur_length++;
/* add value object */
if (!no_values) {
str = lpGet(p, &len, intbuf);
listAddNodeTail(keys, sdsnewlen(str, len));
addReplyBulkCBuffer(c, str, len);
cur_length++;
}
p = lpNext(o->ptr, p);
}
cursor = 0;
if (use_pattern)
setDeferredArrayLen(c,replylen,cur_length);
else
serverAssert(cur_length == array_reply_len); /* fail on corrupt data */
return;
} else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_LISTPACK_EX) {
int64_t len;
long long expire_at;
@@ -1245,6 +1401,16 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
unsigned char *p = lpFirst(lp);
unsigned char *str, *val;
unsigned char intbuf[LP_INTBUF_SIZE];
void *replylen = NULL;
listRelease(keys);
/* Reply to the client. */
addReplyArrayLen(c, 2);
/* Cursor is always 0 given we iterate over all set */
addReplyBulkLongLong(c,0);
/* In the case of OBJ_ENCODING_LISTPACK_EX we always defer the reply size given some fields might be expired */
replylen = addReplyDeferredLen(c);
unsigned long cur_length = 0;
while (p) {
str = lpGet(p, &len, intbuf);
@@ -1255,7 +1421,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
serverAssert(p && lpGetIntegerValue(p, &expire_at));
if (hashTypeIsExpired(o, expire_at) ||
(use_pattern && !stringmatchlen(pat, sdslen(pat), (char *)str, len, 0)))
(use_pattern && !stringmatchlen(pat, patlen, (char *)str, len, 0)))
{
/* jump to the next key/val pair */
p = lpNext(lp, p);
@@ -1263,15 +1429,18 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
}
/* add key object */
listAddNodeTail(keys, sdsnewlen(str, len));
addReplyBulkCBuffer(c, str, len);
cur_length++;
/* add value object */
if (!no_values) {
str = lpGet(val, &len, intbuf);
listAddNodeTail(keys, sdsnewlen(str, len));
addReplyBulkCBuffer(c, str, len);
cur_length++;
}
p = lpNext(lp, p);
}
cursor = 0;
setDeferredArrayLen(c,replylen,cur_length);
return;
} else {
serverPanic("Not handled encoding in SCAN.");
}
@@ -1285,16 +1454,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
while ((ln = listNext(&li))) {
sds key = listNodeValue(ln);
initStaticStringObject(kobj, key);
/* Filter an element if it isn't the type we want. */
/* TODO: remove this in redis 8.0 */
if (typename) {
robj* typecheck = lookupKeyReadWithFlags(c->db, &kobj, LOOKUP_NOTOUCH|LOOKUP_NONOTIFY);
if (!typecheck || !objectTypeCompare(typecheck, type)) {
listDelNode(keys, ln);
}
continue;
}
if (expireIfNeeded(c->db, &kobj, 0) != KEY_VALID) {
if (expireIfNeeded(c->db, &kobj, 0)) {
listDelNode(keys, ln);
}
}
@@ -1839,16 +1999,23 @@ int removeExpire(redisDb *db, robj *key) {
return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), key->ptr) == DICT_OK;
}
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de, *existing;
setExpireWithDictEntry(c,db,key,when,NULL);
}
/* Like setExpire(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the kvstoreDictFind call. */
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) {
dictEntry *de, *existing;
/* Reuse the sds from the main dict in the expire dict */
int slot = getKeySlot(key->ptr);
kde = kvstoreDictFind(db->keys, slot, key->ptr);
if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing);
if (existing) {
@@ -1873,17 +2040,72 @@ 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);
dbGenericDelete(db,keyobj,server.lazyfree_lazy_expire,DB_FLAG_KEY_EXPIRED);
latencyEndMonitor(expire_latency);
latencyAddSampleIfNeeded("expire-del",expire_latency);
notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
/* Delete the specified expired or evicted key and propagate to replicas.
* Currently notify_type can only be NOTIFY_EXPIRED or NOTIFY_EVICTED,
* and it affects other aspects like the latency monitor event name and,
* which config to look for lazy free, stats var to increment, and so on.
*
* key_mem_freed is an out parameter which contains the estimated
* amount of memory freed due to the trimming (may be NULL) */
static void deleteKeyAndPropagate(redisDb *db, robj *keyobj, int notify_type, long long *key_mem_freed) {
mstime_t latency;
int del_flag = notify_type == NOTIFY_EXPIRED ? DB_FLAG_KEY_EXPIRED : DB_FLAG_KEY_EVICTED;
int lazy_flag = notify_type == NOTIFY_EXPIRED ? server.lazyfree_lazy_expire : server.lazyfree_lazy_eviction;
char *latency_name = notify_type == NOTIFY_EXPIRED ? "expire-del" : "evict-del";
char *notify_name = notify_type == NOTIFY_EXPIRED ? "expired" : "evicted";
/* The key needs to be converted from static to heap before deleted */
int static_key = keyobj->refcount == OBJ_STATIC_REFCOUNT;
if (static_key) {
keyobj = createStringObject(keyobj->ptr, sdslen(keyobj->ptr));
}
serverLog(LL_DEBUG,"key %s %s: deleting it", (char*)keyobj->ptr, notify_type == NOTIFY_EXPIRED ? "expired" : "evicted");
/* We compute the amount of memory freed by db*Delete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* Same for CSC invalidation messages generated by signalModifiedKey.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space.
*
* The code here used to first propagate and then record delta
* using only zmalloc_used_memory but in CRDT we can't do that
* so we use freeMemoryGetNotCountedMemory to avoid counting
* AOF and slave buffers */
if (key_mem_freed) *key_mem_freed = (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
latencyStartMonitor(latency);
dbGenericDelete(db, keyobj, lazy_flag, del_flag);
latencyEndMonitor(latency);
latencyAddSampleIfNeeded(latency_name, latency);
if (key_mem_freed) *key_mem_freed -= (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
notifyKeyspaceEvent(notify_type, notify_name,keyobj, db->id);
signalModifiedKey(NULL, db, keyobj);
propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
server.stat_expiredkeys++;
propagateDeletion(db, keyobj, lazy_flag);
if (notify_type == NOTIFY_EXPIRED)
server.stat_expiredkeys++;
else
server.stat_evictedkeys++;
if (static_key)
decrRefCount(keyobj);
}
/* Delete the specified expired key and propagate. */
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
deleteKeyAndPropagate(db, keyobj, NOTIFY_EXPIRED, NULL);
}
/* Delete the specified evicted key and propagate. */
void deleteEvictedKeyAndPropagate(redisDb *db, robj *keyobj, long long *key_mem_freed) {
deleteKeyAndPropagate(db, keyobj, NOTIFY_EVICTED, key_mem_freed);
}
/* Propagate an implicit key deletion into replicas and the AOF file.
@@ -1966,14 +2188,17 @@ int keyIsExpired(redisDb *db, robj *key) {
*
* On the other hand, if you just want expiration check, but need to avoid
* the actual key deletion and propagation of the deletion, use the
* EXPIRE_AVOID_DELETE_EXPIRED flag.
* EXPIRE_AVOID_DELETE_EXPIRED flag. If also needed to read expired key (that
* hasn't being deleted yet) then use EXPIRE_ALLOW_ACCESS_EXPIRED.
*
* The return value of the function is KEY_VALID if the key is still valid.
* The function returns KEY_EXPIRED if the key is expired BUT not deleted,
* or returns KEY_DELETED if the key is expired and deleted. */
keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) {
if (server.lazy_expire_disabled) return KEY_VALID;
if (!keyIsExpired(db,key)) return KEY_VALID;
if ((server.allow_access_expired) ||
(flags & EXPIRE_ALLOW_ACCESS_EXPIRED) ||
(!keyIsExpired(db,key)))
return KEY_VALID;
/* If we are running in the context of a replica, instead of
* evicting the expired key from the database, we return ASAP:
@@ -2003,16 +2228,9 @@ keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) {
* will have failed over and the new primary will send us the expire. */
if (isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)) return KEY_EXPIRED;
/* The key needs to be converted from static to heap before deleted */
int static_key = key->refcount == OBJ_STATIC_REFCOUNT;
if (static_key) {
key = createStringObject(key->ptr, sdslen(key->ptr));
}
/* Delete the key */
deleteExpiredKeyAndPropagate(db,key);
if (static_key) {
decrRefCount(key);
}
return KEY_DELETED;
}
+6 -1
View File
@@ -15,6 +15,7 @@
#include "bio.h"
#include "quicklist.h"
#include "fpconv_dtoa.h"
#include "fast_float_strtod.h"
#include "cluster.h"
#include "threads_mngr.h"
#include "script.h"
@@ -567,6 +568,7 @@ NULL
addReplyError(c,"Error trying to load the RDB dump, check server logs.");
return;
}
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
serverLog(LL_NOTICE,"DB reloaded by DEBUG RELOAD");
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
@@ -582,6 +584,7 @@ NULL
addReplyError(c, "Error trying to load the AOF files, check server logs.");
return;
}
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
server.dirty = 0; /* Prevent AOF / replication */
serverLog(LL_NOTICE,"Append Only File loaded by DEBUG LOADAOF");
addReply(c,shared.ok);
@@ -831,7 +834,7 @@ NULL
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
}
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
double dtime = strtod(c->argv[2]->ptr,NULL);
double dtime = fast_float_strtod(c->argv[2]->ptr,NULL);
long long utime = dtime*1000000;
struct timespec tv;
@@ -2390,6 +2393,8 @@ void removeSigSegvHandlers(void) {
}
void printCrashReport(void) {
server.crashing = 1;
/* Log INFO and CLIENT LIST */
logServerInfo();
+34 -3
View File
@@ -54,6 +54,17 @@ void* activeDefragAlloc(void *ptr) {
return newptr;
}
/* Raw memory allocation for defrag, avoid using tcache. */
void *activeDefragAllocRaw(size_t size) {
return zmalloc_no_tcache(size);
}
/* Raw memory free for defrag, avoid using tcache. */
void activeDefragFreeRaw(void *ptr) {
zfree_no_tcache(ptr);
server.stat_active_defrag_hits++;
}
/*Defrag helper for sds strings
*
* returns NULL in case the allocation wasn't moved.
@@ -718,8 +729,9 @@ void defragStream(redisDb *db, dictEntry *kde) {
void defragModule(redisDb *db, dictEntry *kde) {
robj *obj = dictGetVal(kde);
serverAssert(obj->type == OBJ_MODULE);
if (!moduleDefragValue(dictGetKey(kde), obj, db->id))
robj keyobj;
initStaticStringObject(keyobj, dictGetKey(kde));
if (!moduleDefragValue(&keyobj, obj, db->id))
defragLater(db, kde);
}
@@ -929,7 +941,9 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime, int
} else if (ob->type == OBJ_STREAM) {
return scanLaterStreamListpacks(ob, cursor, endtime);
} else if (ob->type == OBJ_MODULE) {
return moduleLateDefrag(dictGetKey(de), ob, cursor, endtime, dbid);
robj keyobj;
initStaticStringObject(keyobj, dictGetKey(de));
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
} else {
*cursor = 0; /* object type may have changed since we schedule it for later */
}
@@ -1078,6 +1092,7 @@ void activeDefragCycle(void) {
slot = -1;
defrag_later_item_in_progress = 0;
db = NULL;
moduleDefragEnd();
goto update_metrics;
}
return;
@@ -1119,6 +1134,10 @@ void activeDefragCycle(void) {
break; /* this will exit the function and we'll continue on the next cycle */
}
if (current_db == -1) {
moduleDefragStart();
}
/* Move on to next database, and stop if we reached the last one. */
if (++current_db >= server.dbnum) {
/* defrag other items not part of the db / keys */
@@ -1140,6 +1159,8 @@ void activeDefragCycle(void) {
db = NULL;
server.active_defrag_running = 0;
moduleDefragEnd();
computeDefragCycles(); /* if another scan is needed, start it right away */
if (server.active_defrag_running != 0 && ustime() < endtime)
continue;
@@ -1259,6 +1280,16 @@ void *activeDefragAlloc(void *ptr) {
return NULL;
}
void *activeDefragAllocRaw(size_t size) {
/* fallback to regular allocation */
return zmalloc(size);
}
void activeDefragFreeRaw(void *ptr) {
/* fallback to regular free */
zfree(ptr);
}
robj *activeDefragStringOb(robj *ob) {
UNUSED(ob);
return NULL;
+12
View File
@@ -257,6 +257,18 @@ dictStats* dictGetStatsHt(dict *d, int htidx, int full);
void dictCombineStats(dictStats *from, dictStats *into);
void dictFreeStats(dictStats *stats);
#define dictForEach(d, ty, m, ...) do { \
dictIterator *di = dictGetIterator(d); \
dictEntry *de; \
while ((de = dictNext(di)) != NULL) { \
ty *m = dictGetVal(de); \
do { \
__VA_ARGS__ \
} while(0); \
} \
dictReleaseIterator(di); \
} while(0);
#ifdef REDIS_TEST
int dictTest(int argc, char *argv[], int flags);
#endif
+144 -2
View File
@@ -1876,6 +1876,105 @@ uint64_t ebGetExpireTime(EbucketsType *type, eItem item) {
return ebGetMetaExpTime(meta);
}
/* Init ebuckets iterator
*
* This is a non-safe iterator. Any modification to ebuckets will invalidate the
* iterator. Calling this function takes care to reference the first item
* in ebuckets with minimal expiration time. If no items to iterate, then
* iter->currItem will be NULL and iter->itemsCurrBucket will be set to 0.
*/
void ebStart(EbucketsIterator *iter, ebuckets eb, EbucketsType *type) {
iter->eb = eb;
iter->type = type;
iter->isRax = 0;
if (ebIsEmpty(eb)) {
iter->currItem = NULL;
iter->itemsCurrBucket = 0;
} else if (ebIsList(eb)) {
iter->currItem = ebGetListPtr(type, eb);
iter->itemsCurrBucket = type->getExpireMeta(iter->currItem)->numItems;
} else {
rax *rax = ebGetRaxPtr(eb);
raxStart(&iter->raxIter, rax);
raxSeek(&iter->raxIter, "^", NULL, 0);
raxNext(&iter->raxIter);
FirstSegHdr *firstSegHdr = iter->raxIter.data;
iter->itemsCurrBucket = firstSegHdr->totalItems;
iter->currItem = firstSegHdr->head;
iter->isRax = 1;
}
}
/* Advance iterator to the next item
*
* Returns:
* - 0 if the end of ebuckets has been reached, setting `iter->currItem`
* to NULL.
* - 1 otherwise, updating `iter->currItem` to the next item.
*/
int ebNext(EbucketsIterator *iter) {
if (iter->currItem == NULL)
return 0;
eItem item = iter->currItem;
ExpireMeta *meta = iter->type->getExpireMeta(item);
if (iter->isRax) {
if (meta->lastItemBucket) {
if (raxNext(&iter->raxIter)) {
FirstSegHdr *firstSegHdr = iter->raxIter.data;
iter->currItem = firstSegHdr->head;
iter->itemsCurrBucket = firstSegHdr->totalItems;
} else {
iter->currItem = NULL;
}
} else if (meta->lastInSegment) {
NextSegHdr *nextSegHdr = meta->next;
iter->currItem = nextSegHdr->head;
} else {
iter->currItem = meta->next;
}
} else {
iter->currItem = meta->next;
}
if (iter->currItem == NULL) {
iter->itemsCurrBucket = 0;
return 0;
}
return 1;
}
/* Advance the iterator to the next bucket
*
* Returns:
* - 0 if no more ebuckets are available, setting `iter->currItem` to NULL
* and `iter->itemsCurrBucket` to 0.
* - 1 otherwise, updating `iter->currItem` and `iter->itemsCurrBucket` for the
* next ebucket.
*/
int ebNextBucket(EbucketsIterator *iter) {
if (iter->currItem == NULL)
return 0;
if ((iter->isRax) && (raxNext(&iter->raxIter))) {
FirstSegHdr *currSegHdr = iter->raxIter.data;
iter->currItem = currSegHdr->head;
iter->itemsCurrBucket = currSegHdr->totalItems;
} else {
iter->currItem = NULL;
iter->itemsCurrBucket = 0;
}
return 1;
}
/* Stop and cleanup the ebuckets iterator */
void ebStop(EbucketsIterator *iter) {
if (iter->isRax)
raxStop(&iter->raxIter);
}
/*** Unit tests ***/
#ifdef REDIS_TEST
@@ -2117,6 +2216,50 @@ int ebucketsTest(int argc, char **argv, int flags) {
}
#endif
TEST("basic iterator test") {
MyItem *items[100];
for (uint32_t numItems = 0 ; numItems < ARRAY_SIZE(items) ; ++numItems) {
ebuckets eb = NULL;
EbucketsIterator iter;
/* Create and add items to ebuckets */
for (uint32_t i = 0; i < numItems; i++) {
items[i] = zmalloc(sizeof(MyItem));
ebAdd(&eb, &myEbucketsType, items[i], i);
}
/* iterate items */
ebStart(&iter, eb, &myEbucketsType);
for (uint32_t i = 0; i < numItems; i++) {
assert(iter.currItem == items[i]);
int res = ebNext(&iter);
if (i+1<numItems) {
assert(res == 1);
assert(iter.currItem != NULL);
} else {
assert(res == 0);
assert(iter.currItem == NULL);
}
}
ebStop(&iter);
/* iterate buckets */
ebStart(&iter, eb, &myEbucketsType);
uint32_t countItems = 0;
uint32_t countBuckets = 0;
while (1) {
countItems += iter.itemsCurrBucket;
if (!ebNextBucket(&iter)) break;
countBuckets++;
}
ebStop(&iter);
assert(countItems == numItems);
if (numItems>=8) assert(numItems/8 >= countBuckets);
ebDestroy(&eb, &myEbucketsType, NULL);
}
}
TEST("list - Create a single item, get TTL, and remove") {
MyItem *singleItem = zmalloc(sizeof(MyItem));
ebuckets eb = NULL;
@@ -2146,9 +2289,8 @@ int ebucketsTest(int argc, char **argv, int flags) {
assert(ebRemove(&eb, &myEbucketsType, items[i]));
}
for (int i = 0 ; i < EB_LIST_MAX_ITEMS ; i++) {
for (int i = 0 ; i < EB_LIST_MAX_ITEMS ; i++)
zfree(items[i]);
}
ebDestroy(&eb, &myEbucketsType, NULL);
}
+22
View File
@@ -255,6 +255,20 @@ typedef struct ExpireInfo {
EB_EXPIRE_TIME_INVALID if none left. */
} ExpireInfo;
/* Iterator to traverse ebuckets items */
typedef struct EbucketsIterator {
/* private data of iterator */
ebuckets eb;
EbucketsType *type;
raxIterator raxIter;
int isRax;
/* public read only */
eItem currItem; /* Current item ref. Use ebGetMetaExpTime()
on `currItem` to get expiration time.*/
uint64_t itemsCurrBucket; /* Number of items in current bucket. */
} EbucketsIterator;
/* ebuckets API */
static inline ebuckets ebCreate(void) { return NULL; } /* Empty ebuckets */
@@ -281,6 +295,14 @@ int ebAdd(ebuckets *eb, EbucketsType *type, eItem item, uint64_t expireTime);
uint64_t ebGetExpireTime(EbucketsType *type, eItem item);
void ebStart(EbucketsIterator *iter, ebuckets eb, EbucketsType *type);
void ebStop(EbucketsIterator *iter);
int ebNext(EbucketsIterator *iter);
int ebNextBucket(EbucketsIterator *iter);
typedef eItem (ebDefragFunction)(const eItem item);
eItem ebDefragItem(ebuckets *eb, EbucketsType *type, eItem item, ebDefragFunction *fn);
+97
View File
@@ -0,0 +1,97 @@
/* eventnotifier.c -- An event notifier based on eventfd or pipe.
*
* Copyright (c) 2024-Present, Redis Ltd.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
#include "eventnotifier.h"
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HAVE_EVENT_FD
#include <sys/eventfd.h>
#endif
#include "anet.h"
#include "zmalloc.h"
eventNotifier* createEventNotifier(void) {
eventNotifier *en = zmalloc(sizeof(eventNotifier));
if (!en) return NULL;
#ifdef HAVE_EVENT_FD
if ((en->efd = eventfd(0, EFD_NONBLOCK| EFD_CLOEXEC)) != -1) {
return en;
}
#else
if (anetPipe(en->pipefd, O_CLOEXEC|O_NONBLOCK, O_CLOEXEC|O_NONBLOCK) != -1) {
return en;
}
#endif
/* Clean up if error. */
zfree(en);
return NULL;
}
int getReadEventFd(struct eventNotifier *en) {
#ifdef HAVE_EVENT_FD
return en->efd;
#else
return en->pipefd[0];
#endif
}
int getWriteEventFd(struct eventNotifier *en) {
#ifdef HAVE_EVENT_FD
return en->efd;
#else
return en->pipefd[1];
#endif
}
int triggerEventNotifier(struct eventNotifier *en) {
#ifdef HAVE_EVENT_FD
uint64_t u = 1;
if (write(en->efd, &u, sizeof(uint64_t)) == -1) {
return EN_ERR;
}
#else
char buf[1] = {'R'};
if (write(en->pipefd[1], buf, 1) == -1) {
return EN_ERR;
}
#endif
return EN_OK;
}
int handleEventNotifier(struct eventNotifier *en) {
#ifdef HAVE_EVENT_FD
uint64_t u;
if (read(en->efd, &u, sizeof(uint64_t)) == -1) {
return EN_ERR;
}
#else
char buf[1];
if (read(en->pipefd[0], buf, 1) == -1) {
return EN_ERR;
}
#endif
return EN_OK;
}
void freeEventNotifier(struct eventNotifier *en) {
#ifdef HAVE_EVENT_FD
close(en->efd);
#else
close(en->pipefd[0]);
close(en->pipefd[1]);
#endif
/* Free memory */
zfree(en);
}
+33
View File
@@ -0,0 +1,33 @@
/* eventnotifier.h -- An event notifier based on eventfd or pipe.
*
* Copyright (c) 2024-Present, Redis Ltd.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
#ifndef EVENTNOTIFIER_H
#define EVENTNOTIFIER_H
#include "config.h"
#define EN_OK 0
#define EN_ERR -1
typedef struct eventNotifier {
#ifdef HAVE_EVENT_FD
int efd;
#else
int pipefd[2];
#endif
} eventNotifier;
eventNotifier* createEventNotifier(void);
int getReadEventFd(struct eventNotifier *en);
int getWriteEventFd(struct eventNotifier *en);
int triggerEventNotifier(struct eventNotifier *en);
int handleEventNotifier(struct eventNotifier *en);
void freeEventNotifier(struct eventNotifier *en);
#endif
+10 -27
View File
@@ -525,8 +525,7 @@ int performEvictions(void) {
int keys_freed = 0;
size_t mem_reported, mem_tofree;
long long mem_freed; /* May be negative */
mstime_t latency, eviction_latency;
long long delta;
mstime_t latency;
int slaves = listLength(server.slaves);
int result = EVICT_FAIL;
@@ -659,34 +658,18 @@ int performEvictions(void) {
/* Finally remove the selected key. */
if (bestkey) {
long long key_mem_freed;
db = server.db+bestdbid;
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
/* We compute the amount of memory freed by db*Delete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* Same for CSC invalidation messages generated by signalModifiedKey.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
enterExecutionUnit(1, 0);
delta = (long long) zmalloc_used_memory();
latencyStartMonitor(eviction_latency);
dbGenericDelete(db,keyobj,server.lazyfree_lazy_eviction,DB_FLAG_KEY_EVICTED);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
server.stat_evictedkeys++;
signalModifiedKey(NULL,db,keyobj);
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
keyobj, db->id);
propagateDeletion(db,keyobj,server.lazyfree_lazy_eviction);
exitExecutionUnit();
postExecutionUnitOperations();
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
deleteEvictedKeyAndPropagate(db, keyobj, &key_mem_freed);
decrRefCount(keyobj);
exitExecutionUnit();
/* Propagate the DEL command */
postExecutionUnitOperations();
mem_freed += key_mem_freed;
keys_freed++;
if (keys_freed % 16 == 0) {
+12 -22
View File
@@ -36,17 +36,18 @@ static double avg_ttl_factor[16] = {0.98, 0.9604, 0.941192, 0.922368, 0.903921,
* to the function to avoid too many gettimeofday() syscalls. */
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
long long t = dictGetSignedIntegerVal(de);
if (now > t) {
enterExecutionUnit(1, 0);
sds key = dictGetKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
deleteExpiredKeyAndPropagate(db,keyobj);
decrRefCount(keyobj);
exitExecutionUnit();
return 1;
} else {
if (now < t)
return 0;
}
enterExecutionUnit(1, 0);
sds key = dictGetKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
deleteExpiredKeyAndPropagate(db,keyobj);
decrRefCount(keyobj);
exitExecutionUnit();
/* Propagate the DEL command */
postExecutionUnitOperations();
return 1;
}
/* Try to expire a few timed out keys. The algorithm used is adaptive and
@@ -113,8 +114,6 @@ void expireScanCallback(void *privdata, const dictEntry *const_de) {
long long ttl = dictGetSignedIntegerVal(de) - data->now;
if (activeExpireCycleTryExpire(data->db, de, data->now)) {
data->expired++;
/* Propagate the DEL command */
postExecutionUnitOperations();
}
if (ttl > 0) {
/* We want the average TTL of keys yet not expired. */
@@ -465,16 +464,7 @@ void expireSlaveKeys(void) {
if ((dbids & 1) != 0) {
redisDb *db = server.db+dbid;
dictEntry *expire = dbFindExpires(db, keyname);
int expired = 0;
if (expire &&
activeExpireCycleTryExpire(server.db+dbid,expire,start))
{
expired = 1;
/* Propagate the DEL (writable replicas do not propagate anything to other replicas,
* but they might propagate to AOF) and trigger module hooks. */
postExecutionUnitOperations();
}
int expired = expire && activeExpireCycleTryExpire(server.db+dbid,expire,start);
/* If the key was not expired in this DB, we need to set the
* corresponding bit in the new bitmap we set as value.
+1 -1
View File
@@ -171,7 +171,7 @@ void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
stats->n_lib = 0;
}
dictReleaseIterator(iter);
curr_functions_lib_ctx->cache_memory = 0;
lib_ctx->cache_memory = 0;
}
void functionsLibCtxClearCurrent(int async) {
+4 -4
View File
@@ -796,8 +796,8 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
if (withcoords) {
addReplyArrayLen(c, 2);
addReplyHumanLongDouble(c, gp->longitude);
addReplyHumanLongDouble(c, gp->latitude);
addReplyDouble(c,gp->longitude);
addReplyDouble(c,gp->latitude);
}
}
} else {
@@ -959,8 +959,8 @@ void geoposCommand(client *c) {
continue;
}
addReplyArrayLen(c,2);
addReplyHumanLongDouble(c,xy[0]);
addReplyHumanLongDouble(c,xy[1]);
addReplyDouble(c,xy[0]);
addReplyDouble(c,xy[1]);
}
}
}
+5 -12
View File
@@ -429,7 +429,7 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
* of the pattern 000..1 of the element hash. As a side effect 'regp' is
* set to the register index this element hashes to. */
int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
uint64_t hash, bit, index;
uint64_t hash, index;
int count;
/* Count the number of zeroes starting from bit HLL_REGISTERS
@@ -439,21 +439,14 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
* Note that the final "1" ending the sequence of zeroes must be
* included in the count, so if we find "001" the count is 3, and
* the smallest count possible is no zeroes at all, just a 1 bit
* at the first position, that is a count of 1.
*
* This may sound like inefficient, but actually in the average case
* there are high probabilities to find a 1 after a few iterations. */
* at the first position, that is a count of 1. */
hash = MurmurHash64A(ele,elesize,0xadc83b19ULL);
index = hash & HLL_P_MASK; /* Register index. */
hash >>= HLL_P; /* Remove bits used to address the register. */
hash |= ((uint64_t)1<<HLL_Q); /* Make sure the loop terminates
and count will be <= Q+1. */
bit = 1;
count = 1; /* Initialized to 1 since we count the "00000...1" pattern. */
while((hash & bit) == 0) {
count++;
bit <<= 1;
}
count = __builtin_ctzll(hash) + 1;
*regp = (int) index;
return count;
}
@@ -648,7 +641,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
* for future reallocates on incremental growth. But we do not allocate more than
* 'server.hll_sparse_max_bytes' bytes for the sparse representation.
* If the available size of hyperloglog sds string is not enough for the increment
* we need, we promote the hypreloglog to dense representation in 'step 3'.
* we need, we promote the hyperloglog to dense representation in 'step 3'.
*/
if (sdsalloc(o->ptr) < server.hll_sparse_max_bytes && sdsavail(o->ptr) < 3) {
size_t newlen = sdslen(o->ptr) + 3;
+631
View File
@@ -0,0 +1,631 @@
/* iothread.c -- The threaded io implementation.
*
* Copyright (c) 2024-Present, Redis Ltd.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
#include "server.h"
/* IO threads. */
static IOThread IOThreads[IO_THREADS_MAX_NUM];
/* For main thread */
static list *mainThreadPendingClientsToIOThreads[IO_THREADS_MAX_NUM]; /* Clients to IO threads */
static list *mainThreadProcessingClients[IO_THREADS_MAX_NUM]; /* Clients in processing */
static list *mainThreadPendingClients[IO_THREADS_MAX_NUM]; /* Pending clients from IO threads */
static pthread_mutex_t mainThreadPendingClientsMutexes[IO_THREADS_MAX_NUM]; /* Mutex for pending clients */
static eventNotifier* mainThreadPendingClientsNotifiers[IO_THREADS_MAX_NUM]; /* Notifier for pending clients */
/* When IO threads read a complete query of clients or want to free clients, it
* should remove it from its clients list and put the client in the list to main
* thread, we will send these clients to main thread in IOThreadBeforeSleep. */
void enqueuePendingClientsToMainThread(client *c, int unbind) {
/* If the IO thread may no longer manage it, such as closing client, we should
* unbind client from event loop, so main thread doesn't need to do it costly. */
if (unbind) connUnbindEventLoop(c->conn);
/* Just skip if it already is transferred. */
if (c->io_thread_client_list_node) {
listDelNode(IOThreads[c->tid].clients, c->io_thread_client_list_node);
c->io_thread_client_list_node = NULL;
/* Disable read and write to avoid race when main thread processes. */
c->io_flags &= ~(CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED);
listAddNodeTail(IOThreads[c->tid].pending_clients_to_main_thread, c);
}
}
/* Unbind connection of client from io thread event loop, write and read handlers
* also be removed, ensures that we can operate the client safely. */
void unbindClientFromIOThreadEventLoop(client *c) {
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
c->running_tid == IOTHREAD_MAIN_THREAD_ID);
if (!connHasEventLoop(c->conn)) return;
/* As calling in main thread, we should pause the io thread to make it safe. */
pauseIOThread(c->tid);
connUnbindEventLoop(c->conn);
resumeIOThread(c->tid);
}
/* When main thread is processing a client from IO thread, and wants to keep it,
* we should unbind connection of client from io thread event loop first,
* and then bind the client connection into server's event loop. */
void keepClientInMainThread(client *c) {
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
c->running_tid == IOTHREAD_MAIN_THREAD_ID);
/* IO thread no longer manage it. */
server.io_threads_clients_num[c->tid]--;
/* Unbind connection of client from io thread event loop. */
unbindClientFromIOThreadEventLoop(c);
/* Let main thread to run it, rebind event loop and read handler */
connRebindEventLoop(c->conn, server.el);
connSetReadHandler(c->conn, readQueryFromClient);
c->io_flags |= CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED;
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
c->tid = IOTHREAD_MAIN_THREAD_ID;
/* Main thread starts to manage it. */
server.io_threads_clients_num[c->tid]++;
}
/* If the client is managed by IO thread, we should fetch it from IO thread
* and then main thread will can process it. Just like IO Thread transfers
* the client to the main thread for processing. */
void fetchClientFromIOThread(client *c) {
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
c->running_tid != IOTHREAD_MAIN_THREAD_ID);
pauseIOThread(c->tid);
/* Remove the client from clients list of IO thread or main thread. */
if (c->io_thread_client_list_node) {
listDelNode(IOThreads[c->tid].clients, c->io_thread_client_list_node);
c->io_thread_client_list_node = NULL;
} else {
list *clients[5] = {
IOThreads[c->tid].pending_clients,
IOThreads[c->tid].pending_clients_to_main_thread,
mainThreadPendingClients[c->tid],
mainThreadProcessingClients[c->tid],
mainThreadPendingClientsToIOThreads[c->tid]
};
for (int i = 0; i < 5; i++) {
listNode *ln = listSearchKey(clients[i], c);
if (ln) {
listDelNode(clients[i], ln);
/* Client only can be in one client list. */
break;
}
}
}
/* Unbind connection of client from io thread event loop. */
connUnbindEventLoop(c->conn);
/* Now main thread can process it. */
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
resumeIOThread(c->tid);
}
/* For some clients, we must handle them in the main thread, since there is
* data race to be processed in IO threads.
*
* - Close ASAP, we must free the client in main thread.
* - Replica, pubsub, monitor, blocked, tracking clients, main thread may
* directly write them a reply when conditions are met.
* - Script command with debug may operate connection directly. */
int isClientMustHandledByMainThread(client *c) {
if (c->flags & (CLIENT_CLOSE_ASAP | CLIENT_MASTER | CLIENT_SLAVE |
CLIENT_PUBSUB | CLIENT_MONITOR | CLIENT_BLOCKED |
CLIENT_UNBLOCKED | CLIENT_TRACKING | CLIENT_LUA_DEBUG |
CLIENT_LUA_DEBUG_SYNC))
{
return 1;
}
return 0;
}
/* When the main thread accepts a new client or transfers clients to IO threads,
* it assigns the client to the IO thread with the fewest clients. */
void assignClientToIOThread(client *c) {
serverAssert(c->tid == IOTHREAD_MAIN_THREAD_ID);
/* Find the IO thread with the fewest clients. */
int min_id = 0;
int min = INT_MAX;
for (int i = 1; i < server.io_threads_num; i++) {
if (server.io_threads_clients_num[i] < min) {
min = server.io_threads_clients_num[i];
min_id = i;
}
}
/* Assign the client to the IO thread. */
server.io_threads_clients_num[c->tid]--;
c->tid = min_id;
c->running_tid = min_id;
server.io_threads_clients_num[min_id]++;
/* Unbind connection of client from main thread event loop, disable read and
* write, and then put it in the list, main thread will send these clients
* to IO thread in beforeSleep. */
connUnbindEventLoop(c->conn);
c->io_flags &= ~(CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED);
listAddNodeTail(mainThreadPendingClientsToIOThreads[c->tid], c);
}
/* If updating maxclients config, we not only resize the event loop of main thread
* but also resize the event loop of all io threads, and if one thread is failed,
* it is failed totally, since a fd can be distributed into any IO thread. */
int resizeAllIOThreadsEventLoops(size_t newsize) {
int result = AE_OK;
if (server.io_threads_num <= 1) return result;
/* To make context safe. */
pauseAllIOThreads();
for (int i = 1; i < server.io_threads_num; i++) {
IOThread *t = &IOThreads[i];
if (aeResizeSetSize(t->el, newsize) == AE_ERR)
result = AE_ERR;
}
resumeAllIOThreads();
return result;
}
/* In the main thread, we may want to operate data of io threads, maybe uninstall
* event handler, access query/output buffer or resize event loop, we need a clean
* and safe context to do that. We pause io thread in IOThreadBeforeSleep, do some
* jobs and then resume it. To avoid thread suspended, we use busy waiting to confirm
* the target status. Besides we use atomic variable to make sure memory visibility
* and ordering.
*
* Make sure that only the main thread can call these function,
* - pauseIOThread, resumeIOThread
* - pauseAllIOThreads, resumeAllIOThreads
* - pauseIOThreadsRange, resumeIOThreadsRange
*
* The main thread will pause the io thread, and then wait for the io thread to
* be paused. The io thread will check the paused status in IOThreadBeforeSleep,
* and then pause itself.
*
* The main thread will resume the io thread, and then wait for the io thread to
* be resumed. The io thread will check the paused status in IOThreadBeforeSleep,
* and then resume itself.
*/
/* We may pause the same io thread nestedly, so we need to record the times of
* pausing, and only when the times of pausing is 0, we can pause the io thread,
* and only when the times of pausing is 1, we can resume the io thread. */
static int PausedIOThreads[IO_THREADS_MAX_NUM] = {0};
/* Pause the specific range of io threads, and wait for them to be paused. */
void pauseIOThreadsRange(int start, int end) {
if (server.io_threads_num <= 1) return;
serverAssert(start >= 1 && end < server.io_threads_num && start <= end);
serverAssert(pthread_equal(pthread_self(), server.main_thread_id));
/* Try to make all io threads paused in parallel */
for (int i = start; i <= end; i++) {
PausedIOThreads[i]++;
/* Skip if already paused */
if (PausedIOThreads[i] > 1) continue;
int paused;
atomicGetWithSync(IOThreads[i].paused, paused);
/* Don't support to call reentrant */
serverAssert(paused == IO_THREAD_UNPAUSED);
atomicSetWithSync(IOThreads[i].paused, IO_THREAD_PAUSING);
/* Just notify io thread, no actual job, since io threads check paused
* status in IOThreadBeforeSleep, so just wake it up if polling wait. */
triggerEventNotifier(IOThreads[i].pending_clients_notifier);
}
/* Wait for all io threads paused */
for (int i = start; i <= end; i++) {
if (PausedIOThreads[i] > 1) continue;
int paused = IO_THREAD_PAUSING;
while (paused != IO_THREAD_PAUSED) {
atomicGetWithSync(IOThreads[i].paused, paused);
}
}
}
/* Resume the specific range of io threads, and wait for them to be resumed. */
void resumeIOThreadsRange(int start, int end) {
if (server.io_threads_num <= 1) return;
serverAssert(start >= 1 && end < server.io_threads_num && start <= end);
serverAssert(pthread_equal(pthread_self(), server.main_thread_id));
for (int i = start; i <= end; i++) {
serverAssert(PausedIOThreads[i] > 0);
PausedIOThreads[i]--;
if (PausedIOThreads[i] > 0) continue;
int paused;
/* Check if it is paused, since we must call 'pause' and
* 'resume' in pairs */
atomicGetWithSync(IOThreads[i].paused, paused);
serverAssert(paused == IO_THREAD_PAUSED);
/* Resume */
atomicSetWithSync(IOThreads[i].paused, IO_THREAD_RESUMING);
while (paused != IO_THREAD_UNPAUSED) {
atomicGetWithSync(IOThreads[i].paused, paused);
}
}
}
/* The IO thread checks whether it is being paused, and if so, it pauses itself
* and waits for resuming, corresponding to the pause/resumeIOThread* functions.
* Currently, this is only called in IOThreadBeforeSleep, as there are no pending
* I/O events at this point, with a clean context. */
void handlePauseAndResume(IOThread *t) {
int paused;
/* Check if i am being paused. */
atomicGetWithSync(t->paused, paused);
if (paused == IO_THREAD_PAUSING) {
atomicSetWithSync(t->paused, IO_THREAD_PAUSED);
/* Wait for resuming */
while (paused != IO_THREAD_RESUMING) {
atomicGetWithSync(t->paused, paused);
}
atomicSetWithSync(t->paused, IO_THREAD_UNPAUSED);
}
}
/* Pause the specific io thread, and wait for it to be paused. */
void pauseIOThread(int id) {
pauseIOThreadsRange(id, id);
}
/* Resume the specific io thread, and wait for it to be resumed. */
void resumeIOThread(int id) {
resumeIOThreadsRange(id, id);
}
/* Pause all io threads, and wait for them to be paused. */
void pauseAllIOThreads(void) {
pauseIOThreadsRange(1, server.io_threads_num-1);
}
/* Resume all io threads, and wait for them to be resumed. */
void resumeAllIOThreads(void) {
resumeIOThreadsRange(1, server.io_threads_num-1);
}
/* Add the pending clients to the list of IO threads, and trigger an event to
* notify io threads to handle. */
int sendPendingClientsToIOThreads(void) {
int processed = 0;
for (int i = 1; i < server.io_threads_num; i++) {
int len = listLength(mainThreadPendingClientsToIOThreads[i]);
if (len > 0) {
IOThread *t = &IOThreads[i];
pthread_mutex_lock(&t->pending_clients_mutex);
listJoin(t->pending_clients, mainThreadPendingClientsToIOThreads[i]);
pthread_mutex_unlock(&t->pending_clients_mutex);
/* Trigger an event, maybe an error is returned when buffer is full
* if using pipe, but no worry, io thread will handle all clients
* in list when receiving a notification. */
triggerEventNotifier(t->pending_clients_notifier);
}
processed += len;
}
return processed;
}
extern int ProcessingEventsWhileBlocked;
/* The main thread processes the clients from IO threads, these clients may have
* a complete command to execute or need to be freed. Note that IO threads never
* free client since this operation access much server data.
*
* Please notice that this function may be called reentrantly, i,e, the same goes
* for handleClientsFromIOThread and processClientsOfAllIOThreads. For example,
* when processing script command, it may call processEventsWhileBlocked to
* process new events, if the clients with fired events from the same io thread,
* it may call this function reentrantly. */
void processClientsFromIOThread(IOThread *t) {
listNode *node = NULL;
while (listLength(mainThreadProcessingClients[t->id])) {
/* Each time we pop up only the first client to process to guarantee
* reentrancy safety. */
if (node) zfree(node);
node = listFirst(mainThreadProcessingClients[t->id]);
listUnlinkNode(mainThreadProcessingClients[t->id], node);
client *c = listNodeValue(node);
/* Make sure the client is readable or writable in io thread to
* avoid data race. */
serverAssert(!(c->io_flags & (CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED)));
serverAssert(!(c->flags & CLIENT_CLOSE_ASAP));
/* Let main thread to run it, set running thread id first. */
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
/* If a read error occurs, handle it in the main thread first, since we
* want to print logs about client information before freeing. */
if (c->read_error) handleClientReadError(c);
/* The client is asked to close in IO thread. */
if (c->io_flags & CLIENT_IO_CLOSE_ASAP) {
freeClient(c);
continue;
}
/* Update the client in the mem usage */
updateClientMemUsageAndBucket(c);
/* Process the pending command and input buffer. */
if (!c->read_error && c->io_flags & CLIENT_IO_PENDING_COMMAND) {
c->flags |= CLIENT_PENDING_COMMAND;
if (processPendingCommandAndInputBuffer(c) == C_ERR) {
/* If the client is no longer valid, it must be freed safely. */
continue;
}
}
/* We may have pending replies if io thread may not finish writing
* reply to client, so we did not put the client in pending write
* queue. And we should do that first since we may keep the client
* in main thread instead of returning to io threads. */
if (!(c->flags & CLIENT_PENDING_WRITE) && clientHasPendingReplies(c))
putClientInPendingWriteQueue(c);
/* The client only can be processed in the main thread, otherwise data
* race will happen, since we may touch client's data in main thread. */
if (isClientMustHandledByMainThread(c)) {
keepClientInMainThread(c);
continue;
}
/* Remove this client from pending write clients queue of main thread,
* And some clients may do not have reply if CLIENT REPLY OFF/SKIP. */
if (c->flags & CLIENT_PENDING_WRITE) {
c->flags &= ~CLIENT_PENDING_WRITE;
listUnlinkNode(server.clients_pending_write, &c->clients_pending_write_node);
}
c->running_tid = c->tid;
listLinkNodeHead(mainThreadPendingClientsToIOThreads[c->tid], node);
node = NULL;
}
if (node) zfree(node);
/* Trigger the io thread to handle these clients ASAP to make them processed
* in parallel.
*
* If AOF fsync policy is always, we should not let io thread handle these
* clients now since we don't flush AOF buffer to file and sync yet.
* So these clients will be delayed to send io threads in beforeSleep after
* flushAppendOnlyFile.
*
* If we are in processEventsWhileBlocked, we don't send clients to io threads
* now, we want to update server.events_processed_while_blocked accurately. */
if (listLength(mainThreadPendingClientsToIOThreads[t->id]) &&
server.aof_fsync != AOF_FSYNC_ALWAYS &&
!ProcessingEventsWhileBlocked)
{
pthread_mutex_lock(&(t->pending_clients_mutex));
listJoin(t->pending_clients, mainThreadPendingClientsToIOThreads[t->id]);
pthread_mutex_unlock(&(t->pending_clients_mutex));
triggerEventNotifier(t->pending_clients_notifier);
}
}
/* When the io thread finishes processing the client with the read event, it will
* notify the main thread through event triggering in IOThreadBeforeSleep. The main
* thread handles the event through this function. */
void handleClientsFromIOThread(struct aeEventLoop *el, int fd, void *ptr, int mask) {
UNUSED(el);
UNUSED(mask);
IOThread *t = ptr;
/* Handle fd event first. */
serverAssert(fd == getReadEventFd(mainThreadPendingClientsNotifiers[t->id]));
handleEventNotifier(mainThreadPendingClientsNotifiers[t->id]);
/* Get the list of clients to process. */
pthread_mutex_lock(&mainThreadPendingClientsMutexes[t->id]);
listJoin(mainThreadProcessingClients[t->id], mainThreadPendingClients[t->id]);
pthread_mutex_unlock(&mainThreadPendingClientsMutexes[t->id]);
if (listLength(mainThreadProcessingClients[t->id]) == 0) return;
/* Process the clients from IO threads. */
processClientsFromIOThread(t);
}
/* In the new threaded io design, one thread may process multiple clients, so when
* an io thread notifies the main thread of an event, there may be multiple clients
* with commands that need to be processed. But in the event handler function
* handleClientsFromIOThread may be blocked when processing the specific command,
* the previous clients can not get a reply, and the subsequent clients can not be
* processed, so we need to handle this scenario in beforeSleep. The function is to
* process the commands of subsequent clients from io threads. And another function
* sendPendingClientsToIOThreads make sure clients from io thread can get replies.
* See also beforeSleep. */
void processClientsOfAllIOThreads(void) {
for (int i = 1; i < server.io_threads_num; i++) {
processClientsFromIOThread(&IOThreads[i]);
}
}
/* After the main thread processes the clients, it will send the clients back to
* io threads to handle, and fire an event, the io thread handles the event by
* this function. If the client is not binded to the event loop, we should bind
* it first and install read handler, and we don't uninstall client read handler
* unless freeing client. If the client has pending reply, we just reply to client
* first, and then install write handler if needed. */
void handleClientsFromMainThread(struct aeEventLoop *ae, int fd, void *ptr, int mask) {
UNUSED(ae);
UNUSED(mask);
IOThread *t = ptr;
/* Handle fd event first. */
serverAssert(fd == getReadEventFd(t->pending_clients_notifier));
handleEventNotifier(t->pending_clients_notifier);
pthread_mutex_lock(&t->pending_clients_mutex);
listJoin(t->processing_clients, t->pending_clients);
pthread_mutex_unlock(&t->pending_clients_mutex);
if (listLength(t->processing_clients) == 0) return;
listIter li;
listNode *ln;
listRewind(t->processing_clients, &li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
serverAssert(!(c->io_flags & (CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED)));
/* Main thread must handle clients with CLIENT_CLOSE_ASAP flag, since
* we only set io_flags when clients in io thread are freed ASAP. */
serverAssert(!(c->flags & CLIENT_CLOSE_ASAP));
/* Link client in IO thread clients list first. */
serverAssert(c->io_thread_client_list_node == NULL);
listAddNodeTail(t->clients, c);
c->io_thread_client_list_node = listLast(t->clients);
/* The client is asked to close, we just let main thread free it. */
if (c->io_flags & CLIENT_IO_CLOSE_ASAP) {
enqueuePendingClientsToMainThread(c, 1);
continue;
}
/* Enable read and write and reset some flags. */
c->io_flags |= CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED;
c->io_flags &= ~CLIENT_IO_PENDING_COMMAND;
/* Only bind once, we never remove read handler unless freeing client. */
if (!connHasEventLoop(c->conn)) {
connRebindEventLoop(c->conn, t->el);
serverAssert(!connHasReadHandler(c->conn));
connSetReadHandler(c->conn, readQueryFromClient);
}
/* If the client has pending replies, write replies to client. */
if (clientHasPendingReplies(c)) {
writeToClient(c, 0);
if (!(c->io_flags & CLIENT_IO_CLOSE_ASAP) && clientHasPendingReplies(c)) {
connSetWriteHandler(c->conn, sendReplyToClient);
}
}
}
listEmpty(t->processing_clients);
}
void IOThreadBeforeSleep(struct aeEventLoop *el) {
IOThread *t = el->privdata[0];
/* Handle pending data(typical TLS). */
connTypeProcessPendingData(el);
/* If any connection type(typical TLS) still has pending unread data don't sleep at all. */
aeSetDontWait(el, connTypeHasPendingData(el));
/* Check if i am being paused, pause myself and resume. */
handlePauseAndResume(t);
/* Check if there are clients to be processed in main thread, and then join
* them to the list of main thread. */
if (listLength(t->pending_clients_to_main_thread) > 0) {
pthread_mutex_lock(&mainThreadPendingClientsMutexes[t->id]);
listJoin(mainThreadPendingClients[t->id], t->pending_clients_to_main_thread);
pthread_mutex_unlock(&mainThreadPendingClientsMutexes[t->id]);
/* Trigger an event, maybe an error is returned when buffer is full
* if using pipe, but no worry, main thread will handle all clients
* in list when receiving a notification. */
triggerEventNotifier(mainThreadPendingClientsNotifiers[t->id]);
}
}
/* The main function of IO thread, it will run an event loop. The mian thread
* and IO thread will communicate through event notifier. */
void *IOThreadMain(void *ptr) {
IOThread *t = ptr;
char thdname[16];
snprintf(thdname, sizeof(thdname), "io_thd_%d", t->id);
redis_set_thread_title(thdname);
redisSetCpuAffinity(server.server_cpulist);
makeThreadKillable();
aeSetBeforeSleepProc(t->el, IOThreadBeforeSleep);
aeMain(t->el);
return NULL;
}
/* Initialize the data structures needed for threaded I/O. */
void initThreadedIO(void) {
if (server.io_threads_num <= 1) return;
server.io_threads_active = 1;
if (server.io_threads_num > IO_THREADS_MAX_NUM) {
serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
"The maximum number is %d.", IO_THREADS_MAX_NUM);
exit(1);
}
/* Spawn and initialize the I/O threads. */
for (int i = 1; i < server.io_threads_num; i++) {
IOThread *t = &IOThreads[i];
t->id = i;
t->el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
t->el->privdata[0] = t;
t->pending_clients = listCreate();
t->processing_clients = listCreate();
t->pending_clients_to_main_thread = listCreate();
t->clients = listCreate();
atomicSetWithSync(t->paused, IO_THREAD_UNPAUSED);
pthread_mutexattr_t *attr = NULL;
#if defined(__linux__) && defined(__GLIBC__)
attr = zmalloc(sizeof(pthread_mutexattr_t));
pthread_mutexattr_init(attr);
pthread_mutexattr_settype(attr, PTHREAD_MUTEX_ADAPTIVE_NP);
#endif
pthread_mutex_init(&t->pending_clients_mutex, attr);
t->pending_clients_notifier = createEventNotifier();
if (aeCreateFileEvent(t->el, getReadEventFd(t->pending_clients_notifier),
AE_READABLE, handleClientsFromMainThread, t) != AE_OK)
{
serverLog(LL_WARNING, "Fatal: Can't register file event for IO thread notifications.");
exit(1);
}
/* Create IO thread */
if (pthread_create(&t->tid, NULL, IOThreadMain, (void*)t) != 0) {
serverLog(LL_WARNING, "Fatal: Can't initialize IO thread.");
exit(1);
}
/* For main thread */
mainThreadPendingClientsToIOThreads[i] = listCreate();
mainThreadPendingClients[i] = listCreate();
mainThreadProcessingClients[i] = listCreate();
pthread_mutex_init(&mainThreadPendingClientsMutexes[i], attr);
mainThreadPendingClientsNotifiers[i] = createEventNotifier();
if (aeCreateFileEvent(server.el, getReadEventFd(mainThreadPendingClientsNotifiers[i]),
AE_READABLE, handleClientsFromIOThread, t) != AE_OK)
{
serverLog(LL_WARNING, "Fatal: Can't register file event for main thread notifications.");
exit(1);
}
if (attr) zfree(attr);
}
}
/* Kill the IO threads, TODO: release the applied resources. */
void killIOThreads(void) {
if (server.io_threads_num <= 1) return;
int err, j;
for (j = 1; j < server.io_threads_num; j++) {
if (IOThreads[j].tid == pthread_self()) continue;
if (IOThreads[j].tid && pthread_cancel(IOThreads[j].tid) == 0) {
if ((err = pthread_join(IOThreads[j].tid,NULL)) != 0) {
serverLog(LL_WARNING,
"IO thread(tid:%lu) can not be joined: %s",
(unsigned long)IOThreads[j].tid, strerror(err));
} else {
serverLog(LL_WARNING,
"IO thread(tid:%lu) terminated",(unsigned long)IOThreads[j].tid);
}
}
}
}
+85 -15
View File
@@ -42,6 +42,7 @@ struct _kvstore {
unsigned long long *dict_size_index; /* Binary indexed tree (BIT) that describes cumulative key frequencies up until given dict-index. */
size_t overhead_hashtable_lut; /* The overhead of all dictionaries. */
size_t overhead_hashtable_rehashing; /* The overhead of dictionaries rehashing. */
void *metadata[]; /* conditionally allocated based on "flags" */
};
/* Structure for kvstore iterator that allows iterating across multiple dicts. */
@@ -59,10 +60,17 @@ struct _kvstoreDictIterator {
dictIterator di;
};
/* Dict metadata for database, used for record the position in rehashing list. */
/* Basic metadata allocated per dict */
typedef struct {
listNode *rehashing_node; /* list node in rehashing list */
} kvstoreDictMetadata;
} kvstoreDictMetaBase;
/* Conditionally metadata allocated per dict (specifically for keysizes histogram) */
typedef struct {
kvstoreDictMetaBase base; /* must be first in struct ! */
/* External metadata */
kvstoreDictMetadata meta;
} kvstoreDictMetaEx;
/**********************************/
/*** Helpers **********************/
@@ -124,7 +132,9 @@ static void cumulativeKeyCountAdd(kvstore *kvs, int didx, long delta) {
dict *d = kvstoreGetDict(kvs, didx);
size_t dsize = dictSize(d);
int non_empty_dicts_delta = dsize == 1? 1 : dsize == 0? -1 : 0;
/* Increment if dsize is 1 and delta is positive (first element inserted, dict becomes non-empty).
* Decrement if dsize is 0 (dict becomes empty). */
int non_empty_dicts_delta = (dsize == 1 && delta > 0) ? 1 : (dsize == 0) ? -1 : 0;
kvs->non_empty_dicts += non_empty_dicts_delta;
/* BIT does not need to be calculated when there's only one dict. */
@@ -182,7 +192,7 @@ static void freeDictIfNeeded(kvstore *kvs, int didx) {
* If there's one dict, bucket count can be retrieved directly from single dict bucket. */
static void kvstoreDictRehashingStarted(dict *d) {
kvstore *kvs = d->type->userdata;
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
listAddNodeTail(kvs->rehashing, d);
metadata->rehashing_node = listLast(kvs->rehashing);
@@ -199,7 +209,7 @@ static void kvstoreDictRehashingStarted(dict *d) {
* the old ht size of the dictionary from the total sum of buckets for a DB. */
static void kvstoreDictRehashingCompleted(dict *d) {
kvstore *kvs = d->type->userdata;
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
if (metadata->rehashing_node) {
listDelNode(kvs->rehashing, metadata->rehashing_node);
metadata->rehashing_node = NULL;
@@ -212,10 +222,15 @@ static void kvstoreDictRehashingCompleted(dict *d) {
kvs->overhead_hashtable_rehashing -= from;
}
/* Returns the size of the DB dict metadata in bytes. */
static size_t kvstoreDictMetadataSize(dict *d) {
/* Returns the size of the DB dict base metadata in bytes. */
static size_t kvstoreDictMetaBaseSize(dict *d) {
UNUSED(d);
return sizeof(kvstoreDictMetadata);
return sizeof(kvstoreDictMetaBase);
}
/* Returns the size of the DB dict extended metadata in bytes. */
static size_t kvstoreDictMetadataExtendSize(dict *d) {
UNUSED(d);
return sizeof(kvstoreDictMetaEx);
}
/**********************************/
@@ -230,7 +245,13 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
* for the dict cursor, see kvstoreScan */
assert(num_dicts_bits <= 16);
kvstore *kvs = zcalloc(sizeof(*kvs));
/* Calc kvstore size */
size_t kvsize = sizeof(kvstore);
/* Conditionally calc also histogram size */
if (flags & KVSTORE_ALLOC_META_KEYS_HIST)
kvsize += sizeof(kvstoreMetadata);
kvstore *kvs = zcalloc(kvsize);
memcpy(&kvs->dtype, type, sizeof(kvs->dtype));
kvs->flags = flags;
@@ -241,7 +262,10 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
assert(!type->rehashingStarted);
assert(!type->rehashingCompleted);
kvs->dtype.userdata = kvs;
kvs->dtype.dictMetadataBytes = kvstoreDictMetadataSize;
if (flags & KVSTORE_ALLOC_META_KEYS_HIST)
kvs->dtype.dictMetadataBytes = kvstoreDictMetadataExtendSize;
else
kvs->dtype.dictMetadataBytes = kvstoreDictMetaBaseSize;
kvs->dtype.rehashingStarted = kvstoreDictRehashingStarted;
kvs->dtype.rehashingCompleted = kvstoreDictRehashingCompleted;
@@ -261,7 +285,6 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
kvs->bucket_count = 0;
kvs->overhead_hashtable_lut = 0;
kvs->overhead_hashtable_rehashing = 0;
return kvs;
}
@@ -270,9 +293,13 @@ void kvstoreEmpty(kvstore *kvs, void(callback)(dict*)) {
dict *d = kvstoreGetDict(kvs, didx);
if (!d)
continue;
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
if (metadata->rehashing_node)
metadata->rehashing_node = NULL;
if (kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST) {
kvstoreDictMetaEx *metaExt = (kvstoreDictMetaEx *) metadata;
memset(&metaExt->meta.keysizes_hist, 0, sizeof(metaExt->meta.keysizes_hist));
}
dictEmpty(d, callback);
freeDictIfNeeded(kvs, didx);
}
@@ -294,7 +321,7 @@ void kvstoreRelease(kvstore *kvs) {
dict *d = kvstoreGetDict(kvs, didx);
if (!d)
continue;
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
if (metadata->rehashing_node)
metadata->rehashing_node = NULL;
dictRelease(d);
@@ -328,11 +355,15 @@ unsigned long kvstoreBuckets(kvstore *kvs) {
size_t kvstoreMemUsage(kvstore *kvs) {
size_t mem = sizeof(*kvs);
size_t metaSize = sizeof(kvstoreDictMetaBase);
if (kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST)
metaSize = sizeof(kvstoreDictMetaEx);
unsigned long long keys_count = kvstoreSize(kvs);
mem += keys_count * dictEntryMemUsage() +
kvstoreBuckets(kvs) * sizeof(dictEntry*) +
kvs->allocated_dicts * (sizeof(dict) + kvstoreDictMetadataSize(NULL));
kvs->allocated_dicts * (sizeof(dict) + metaSize);
/* Values are dict* shared with kvs->dicts */
mem += listLength(kvs->rehashing) * sizeof(listNode);
@@ -783,7 +814,7 @@ void kvstoreDictLUTDefrag(kvstore *kvs, kvstoreDictLUTDefragFunction *defragfn)
/* After defragmenting the dict, update its corresponding
* rehashing node in the kvstore's rehashing list. */
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(*d);
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(*d);
if (metadata->rehashing_node)
metadata->rehashing_node->value = *d;
}
@@ -854,6 +885,19 @@ int kvstoreDictDelete(kvstore *kvs, int didx, const void *key) {
return ret;
}
kvstoreDictMetadata *kvstoreGetDictMetadata(kvstore *kvs, int didx) {
dict *d = kvstoreGetDict(kvs, didx);
if ((!d) || (!(kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST)))
return NULL;
kvstoreDictMetaEx *metadata = (kvstoreDictMetaEx *)dictMetadata(d);
return &(metadata->meta);
}
kvstoreMetadata *kvstoreGetMetadata(kvstore *kvs) {
return (kvstoreMetadata *) &kvs->metadata;
}
#ifdef REDIS_TEST
#include <stdio.h>
#include "testhelp.h"
@@ -1026,6 +1070,32 @@ int kvstoreTest(int argc, char **argv, int flags) {
kvstoreRelease(kvs);
}
TEST("Verify non-empty dict count is correctly updated") {
kvstore *kvs = kvstoreCreate(&KvstoreDictTestType, 2,
KVSTORE_ALLOCATE_DICTS_ON_DEMAND | KVSTORE_ALLOC_META_KEYS_HIST);
for (int idx = 0; idx < 4; idx++) {
for (i = 0; i < 16; i++) {
de = kvstoreDictAddRaw(kvs, idx, stringFromInt(i), NULL);
assert(de != NULL);
/* When the first element is inserted, the number of non-empty dictionaries is increased by 1. */
if (i == 0) assert(kvstoreNumNonEmptyDicts(kvs) == idx + 1);
}
}
/* Step by step, clear all dictionaries and ensure non-empty dict count is updated */
for (int idx = 0; idx < 4; idx++) {
kvs_di = kvstoreGetDictSafeIterator(kvs, idx);
while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) {
key = dictGetKey(de);
assert(kvstoreDictDelete(kvs, idx, key) == DICT_OK);
/* When the dictionary is emptied, the number of non-empty dictionaries is reduced by 1. */
if (kvstoreDictSize(kvs, idx) == 0) assert(kvstoreNumNonEmptyDicts(kvs) == 3 - idx);
}
kvstoreReleaseDictIterator(kvs_di);
}
kvstoreRelease(kvs);
}
kvstoreRelease(kvs1);
kvstoreRelease(kvs2);
return 0;
+18
View File
@@ -4,6 +4,21 @@
#include "dict.h"
#include "adlist.h"
/* maximum number of bins of keysizes histogram */
#define MAX_KEYSIZES_BINS 48
#define MAX_KEYSIZES_TYPES 5 /* static_assert at db.c verifies == OBJ_TYPE_BASIC_MAX */
/* When creating kvstore with flag `KVSTORE_ALLOC_META_KEYS_HIST`, then kvstore
* alloc and memset struct kvstoreMetadata on init, yet, managed outside kvstore */
typedef struct {
uint64_t keysizes_hist[MAX_KEYSIZES_TYPES][MAX_KEYSIZES_BINS];
} kvstoreMetadata;
/* Like kvstoreMetadata, this one per dict */
typedef struct {
uint64_t keysizes_hist[MAX_KEYSIZES_TYPES][MAX_KEYSIZES_BINS];
} kvstoreDictMetadata;
typedef struct _kvstore kvstore;
typedef struct _kvstoreIterator kvstoreIterator;
typedef struct _kvstoreDictIterator kvstoreDictIterator;
@@ -13,6 +28,7 @@ typedef int (kvstoreExpandShouldSkipDictIndex)(int didx);
#define KVSTORE_ALLOCATE_DICTS_ON_DEMAND (1<<0)
#define KVSTORE_FREE_EMPTY_DICTS (1<<1)
#define KVSTORE_ALLOC_META_KEYS_HIST (1<<2) /* Alloc keysizes histogram */
kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags);
void kvstoreEmpty(kvstore *kvs, void(callback)(dict*));
void kvstoreRelease(kvstore *kvs);
@@ -71,6 +87,8 @@ void kvstoreDictSetVal(kvstore *kvs, int didx, dictEntry *de, void *val);
dictEntry *kvstoreDictTwoPhaseUnlinkFind(kvstore *kvs, int didx, const void *key, dictEntry ***plink, int *table_index);
void kvstoreDictTwoPhaseUnlinkFree(kvstore *kvs, int didx, dictEntry *he, dictEntry **plink, int table_index);
int kvstoreDictDelete(kvstore *kvs, int didx, const void *key);
kvstoreDictMetadata *kvstoreGetDictMetadata(kvstore *kvs, int didx);
kvstoreMetadata *kvstoreGetMetadata(kvstore *kvs);
#ifdef REDIS_TEST
int kvstoreTest(int argc, char *argv[], int flags);
+1 -1
View File
@@ -207,7 +207,7 @@ void emptyDbAsync(redisDb *db) {
}
kvstore *oldkeys = db->keys, *oldexpires = db->expires;
ebuckets oldHfe = db->hexpires;
db->keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
db->keys = kvstoreCreate(&dbDictType, slot_count_bits, flags | KVSTORE_ALLOC_META_KEYS_HIST);
db->expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
db->hexpires = ebCreate();
atomicIncr(lazyfree_objects, kvstoreSize(oldkeys));
+10 -4
View File
@@ -687,8 +687,8 @@ int lpGetIntegerValue(unsigned char *p, long long *lval) {
* will be returned. 'user' is passed to this callback.
* Skip 'skip' entries between every comparison.
* Returns NULL when the field could not be found. */
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
static inline unsigned char *lpFindCbInternal(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
{
int skipcnt = 0;
unsigned char *value;
@@ -707,7 +707,7 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
assert(p >= lp + LP_HDR_SIZE && p + entry_size < lp + lp_bytes);
}
if (cmp(lp, p, user, value, ll) == 0)
if (unlikely(cmp(lp, p, user, value, ll) == 0))
return p;
/* Reset skip count */
@@ -734,6 +734,12 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
return NULL;
}
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
{
return lpFindCbInternal(lp, p, user, cmp, skip);
}
struct lpFindArg {
unsigned char *s; /* Item to search */
uint32_t slen; /* Item len */
@@ -787,7 +793,7 @@ unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s,
.s = s,
.slen = slen
};
return lpFindCb(lp, p, &arg, lpFindCmp, skip);
return lpFindCbInternal(lp, p, &arg, lpFindCmp, skip);
}
/* Insert, delete or replace the specified string element 'elestr' of length
+94 -30
View File
@@ -2287,6 +2287,8 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->options = 0;
module->info_cb = 0;
module->defrag_cb = 0;
module->defrag_start_cb = 0;
module->defrag_end_cb = 0;
module->loadmod = NULL;
module->num_commands_with_acl_categories = 0;
module->onload = 1;
@@ -4068,7 +4070,8 @@ static void moduleInitKeyTypeSpecific(RedisModuleKey *key) {
* * REDISMODULE_OPEN_KEY_NONOTIFY - Don't trigger keyspace event on key misses.
* * REDISMODULE_OPEN_KEY_NOSTATS - Don't update keyspace hits/misses counters.
* * REDISMODULE_OPEN_KEY_NOEXPIRE - Avoid deleting lazy expired keys.
* * REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key. */
* * REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key.
* * REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - Access expired keys that have not yet been deleted */
RedisModuleKey *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
RedisModuleKey *kp;
robj *value;
@@ -4078,6 +4081,7 @@ RedisModuleKey *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
flags |= (mode & REDISMODULE_OPEN_KEY_NOSTATS? LOOKUP_NOSTATS: 0);
flags |= (mode & REDISMODULE_OPEN_KEY_NOEXPIRE? LOOKUP_NOEXPIRE: 0);
flags |= (mode & REDISMODULE_OPEN_KEY_NOEFFECTS? LOOKUP_NOEFFECTS: 0);
flags |= (mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED ? (LOOKUP_ACCESS_EXPIRED) : 0);
if (mode & REDISMODULE_WRITE) {
value = lookupKeyWriteWithFlags(ctx->client->db,keyname, flags);
@@ -4167,15 +4171,7 @@ int RM_KeyType(RedisModuleKey *key) {
* If the key pointer is NULL or the key is empty, zero is returned. */
size_t RM_ValueLength(RedisModuleKey *key) {
if (key == NULL || key->value == NULL) return 0;
switch(key->value->type) {
case OBJ_STRING: return stringObjectLen(key->value);
case OBJ_LIST: return listTypeLength(key->value);
case OBJ_SET: return setTypeSize(key->value);
case OBJ_ZSET: return zsetLength(key->value);
case OBJ_HASH: return hashTypeLength(key->value, 0); /* OPEN: To subtract expired fields? */
case OBJ_STREAM: return streamLength(key->value);
default: return 0;
}
return getObjectLength(key->value);
}
/* If the key is open for writing, remove it, and setup the key to
@@ -4274,7 +4270,7 @@ int RM_SetAbsExpire(RedisModuleKey *key, mstime_t expire) {
void RM_ResetDataset(int restart_aof, int async) {
if (restart_aof && server.aof_state != AOF_OFF) stopAppendOnly();
flushAllDataAndResetRDB((async? EMPTYDB_ASYNC: EMPTYDB_NO_FLAGS) | EMPTYDB_NOFUNCTIONS);
if (server.aof_enabled && restart_aof) restartAOFAfterSYNC();
if (server.aof_enabled && restart_aof) startAppendOnlyWithRetry();
}
/* Returns the number of keys in the current db. */
@@ -5376,6 +5372,9 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
va_list ap;
if (key->value && key->value->type != OBJ_HASH) return REDISMODULE_ERR;
if (key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)
hfeFlags = HFE_LAZY_ACCESS_EXPIRED; /* allow read also expired fields */
va_start(ap, flags);
while(1) {
RedisModuleString *field, **valueptr;
@@ -8842,9 +8841,9 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
* it will not be notified about it. */
int prev_active = sub->active;
sub->active = 1;
server.lazy_expire_disabled++;
server.allow_access_expired++;
sub->notify_callback(&ctx, type, event, key);
server.lazy_expire_disabled--;
server.allow_access_expired--;
sub->active = prev_active;
moduleFreeContext(&ctx);
}
@@ -9671,6 +9670,12 @@ RedisModuleString *RM_GetModuleUserACLString(RedisModuleUser *user) {
* The returned string must be released with RedisModule_FreeString() or by
* enabling automatic memory management. */
RedisModuleString *RM_GetCurrentUserName(RedisModuleCtx *ctx) {
/* Sometimes, the user isn't passed along the call stack or isn't
* even set, so we need to check for the members to avoid crashes. */
if (ctx->client == NULL || ctx->client->user == NULL || ctx->client->user->name == NULL) {
return NULL;
}
return RM_CreateString(ctx,ctx->client->user->name,sdslen(ctx->client->user->name));
}
@@ -11085,8 +11090,9 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
} else if (o->type == OBJ_HASH) {
sds val = dictGetVal(de);
/* If field is expired, then ignore */
if (hfieldIsExpired(key))
/* If field is expired and not indicated to access expired, then ignore */
if ((!(data->key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)) &&
(hfieldIsExpired(key)))
return;
field = createStringObject(key, hfieldlen(key));
@@ -11222,7 +11228,8 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
p = lpNext(lp, p);
/* Skip expired fields */
if (hashTypeIsExpired(o, vllExpire))
if ((!(key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)) &&
(hashTypeIsExpired(o, vllExpire)))
continue;
}
@@ -11881,7 +11888,7 @@ void processModuleLoadingProgressEvent(int is_aof) {
/* When a key is deleted (in dbAsyncDelete/dbSyncDelete/setKey), it
* will be called to tell the module which key is about to be released. */
void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags) {
server.lazy_expire_disabled++;
server.allow_access_expired++;
int subevent = REDISMODULE_SUBEVENT_KEY_DELETED;
if (flags & DB_FLAG_KEY_EXPIRED) {
subevent = REDISMODULE_SUBEVENT_KEY_EXPIRED;
@@ -11904,7 +11911,7 @@ void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags) {
mt->unlink(key,mv->value);
}
}
server.lazy_expire_disabled--;
server.allow_access_expired--;
}
/* Return the free_effort of the module, it will automatically choose to call
@@ -12463,10 +12470,15 @@ void modulePipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Helper function for the MODULE and HELLO command: send the list of the
* loaded modules to the client. */
void addReplyLoadedModules(client *c) {
const long ln = dictSize(modules);
/* In case no module is load we avoid iterator creation */
addReplyArrayLen(c,ln);
if (ln == 0) {
return;
}
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
addReplyArrayLen(c,dictSize(modules));
while ((de = dictNext(di)) != NULL) {
sds name = dictGetKey(de);
struct RedisModule *module = dictGetVal(de);
@@ -13070,7 +13082,7 @@ int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) {
int ret = rdbLoad(stream->data.filename,NULL,RDBFLAGS_NONE);
if (server.current_client) unprotectClient(server.current_client);
if (server.aof_state != AOF_OFF) startAppendOnly();
if (server.aof_enabled) startAppendOnlyWithRetry();
if (ret != RDB_OK) {
errno = (ret == RDB_NOT_EXIST) ? ENOENT : EIO;
@@ -13451,6 +13463,16 @@ int RM_RegisterDefragFunc(RedisModuleCtx *ctx, RedisModuleDefragFunc cb) {
return REDISMODULE_OK;
}
/* Register a defrag callbacks that will be called when defrag operation starts and ends.
*
* The callbacks are the same as `RM_RegisterDefragFunc` but the user
* can also assume the callbacks are called when the defrag operation starts and ends. */
int RM_RegisterDefragCallbacks(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) {
ctx->module->defrag_start_cb = start;
ctx->module->defrag_end_cb = end;
return REDISMODULE_OK;
}
/* When the data type defrag callback iterates complex structures, this
* function should be called periodically. A zero (false) return
* indicates the callback may continue its work. A non-zero value (true)
@@ -13529,6 +13551,30 @@ void *RM_DefragAlloc(RedisModuleDefragCtx *ctx, void *ptr) {
return activeDefragAlloc(ptr);
}
/* Allocate memory for defrag purposes
*
* On the common cases user simply want to reallocate a pointer with a single
* owner. For such usecase RM_DefragAlloc is enough. But on some usecases the user
* might want to replace a pointer with multiple owners in different keys.
* In such case, an in place replacement can not work because the other key still
* keep a pointer to the old value.
*
* RM_DefragAllocRaw and RM_DefragFreeRaw allows to control when the memory
* for defrag purposes will be allocated and when it will be freed,
* allow to support more complex defrag usecases. */
void *RM_DefragAllocRaw(RedisModuleDefragCtx *ctx, size_t size) {
UNUSED(ctx);
return activeDefragAllocRaw(size);
}
/* Free memory for defrag purposes
*
* See RM_DefragAllocRaw for more information. */
void RM_DefragFreeRaw(RedisModuleDefragCtx *ctx, void *ptr) {
UNUSED(ctx);
activeDefragFreeRaw(ptr);
}
/* Defrag a RedisModuleString previously allocated by RM_Alloc, RM_Calloc, etc.
* See RM_DefragAlloc() for more information on how the defragmentation process
* works.
@@ -13610,17 +13656,32 @@ int moduleDefragValue(robj *key, robj *value, int dbid) {
/* Call registered module API defrag functions */
void moduleDefragGlobals(void) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
dictForEach(modules, struct RedisModule, module,
if (module->defrag_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_cb(&defrag_ctx);
}
);
}
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
if (!module->defrag_cb)
continue;
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_cb(&defrag_ctx);
}
dictReleaseIterator(di);
/* Call registered module API defrag start functions */
void moduleDefragStart(void) {
dictForEach(modules, struct RedisModule, module,
if (module->defrag_start_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_start_cb(&defrag_ctx);
}
);
}
/* Call registered module API defrag end functions */
void moduleDefragEnd(void) {
dictForEach(modules, struct RedisModule, module,
if (module->defrag_end_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_end_cb(&defrag_ctx);
}
);
}
/* Returns the name of the key currently being processed.
@@ -13980,7 +14041,10 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(GetCurrentCommandName);
REGISTER_API(GetTypeMethodVersion);
REGISTER_API(RegisterDefragFunc);
REGISTER_API(RegisterDefragCallbacks);
REGISTER_API(DefragAlloc);
REGISTER_API(DefragAllocRaw);
REGISTER_API(DefragFreeRaw);
REGISTER_API(DefragRedisModuleString);
REGISTER_API(DefragShouldStop);
REGISTER_API(DefragCursorSet);
+7 -1
View File
@@ -355,7 +355,12 @@ int isWatchedKeyExpired(client *c) {
}
/* "Touch" a key, so that if this key is being WATCHed by some client the
* next EXEC will fail. */
* next EXEC will fail.
*
* Sanitizer suppression: IO threads also read c->flags, but never modify
* it or read the CLIENT_DIRTY_CAS bit, main thread just only modifies
* this bit, so there is actually no real data race. */
REDIS_NO_SANITIZE("thread")
void touchWatchedKey(redisDb *db, robj *key) {
list *clients;
listIter li;
@@ -404,6 +409,7 @@ void touchWatchedKey(redisDb *db, robj *key) {
* replaced_with: for SWAPDB, the WATCH should be invalidated if
* the key exists in either of them, and skipped only if it
* doesn't exist in both. */
REDIS_NO_SANITIZE("thread")
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
listIter li;
listNode *ln;
+352 -507
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -680,6 +680,18 @@ robj *tryObjectEncoding(robj *o) {
return tryObjectEncodingEx(o, 1);
}
size_t getObjectLength(robj *o) {
switch(o->type) {
case OBJ_STRING: return stringObjectLen(o);
case OBJ_LIST: return listTypeLength(o);
case OBJ_SET: return setTypeSize(o);
case OBJ_ZSET: return zsetLength(o);
case OBJ_HASH: return hashTypeLength(o, 0);
case OBJ_STREAM: return streamLength(o);
default: return 0;
}
}
/* Get a decoded version of an encoded object (returned as a new object).
* If the object is already raw-encoded just increment the ref count. */
robj *getDecodedObject(robj *o) {
+9 -2
View File
@@ -1244,10 +1244,17 @@ int quicklistDelRange(quicklist *quicklist, const long start,
/* compare between a two entries */
int quicklistCompare(quicklistEntry* entry, unsigned char *p2, const size_t p2_len) {
if (unlikely(QL_NODE_IS_PLAIN(entry->node))) {
if (entry->value) {
return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0));
} else {
/* We use string2ll() to get an integer representation of the
* string 'p2' and compare it to 'entry->longval', it's much
* faster than convert integer to string and comparing. */
long long sval;
if (string2ll((const char*)p2, p2_len, &sval))
return entry->longval == sval;
}
return lpCompare(entry->zi, p2, p2_len);
return 0;
}
/* Returns a quicklist iterator 'iter'. After the initialization every
+30 -6
View File
@@ -2332,6 +2332,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
rdbReportCorruptRDB("invalid expireAt time: %llu",
(unsigned long long) expireAt);
decrRefCount(o);
if (dupSearchDict != NULL) dictRelease(dupSearchDict);
return NULL;
}
@@ -3161,7 +3162,13 @@ emptykey:
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */
void startLoading(size_t size, int rdbflags, int async) {
/* Load the DB */
loadingSetFlags(NULL, size, async);
loadingFireEvent(rdbflags);
}
/* Initialize stats, set loading flags and filename if provided. */
void loadingSetFlags(char *filename, size_t size, int async) {
rdbFileBeingLoaded = filename;
server.loading = 1;
if (async == 1) server.async_loading = 1;
server.loading_start_time = time(NULL);
@@ -3171,7 +3178,9 @@ void startLoading(size_t size, int rdbflags, int async) {
server.rdb_last_load_keys_expired = 0;
server.rdb_last_load_keys_loaded = 0;
blockingOperationStarts();
}
void loadingFireEvent(int rdbflags) {
/* Fire the loading modules start event. */
int subevent;
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
@@ -3187,8 +3196,8 @@ void startLoading(size_t size, int rdbflags, int async) {
* needed to provide loading stats.
* 'filename' is optional and used for rdb-check on error */
void startLoadingFile(size_t size, char* filename, int rdbflags) {
rdbFileBeingLoaded = filename;
startLoading(size, rdbflags, 0);
loadingSetFlags(filename, size, 0);
loadingFireEvent(rdbflags);
}
/* Refresh the absolute loading progress info */
@@ -3702,14 +3711,21 @@ eoferr:
return C_ERR;
}
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
return rdbLoadWithEmptyFunc(filename, rsi, rdbflags, NULL);
}
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The
* filename is open for reading and a rio stream object created in order
* to do the actual loading. Moreover the ETA displayed in the INFO
* output is initialized and finalized.
*
* If you pass an 'rsi' structure initialized with RDB_SAVE_INFO_INIT, the
* loading code will fill the information fields in the structure. */
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
* loading code will fill the information fields in the structure.
*
* If emptyDbFunc is not NULL, it will be called to flush old db or to
* discard partial db on error. */
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void)) {
FILE *fp;
rio rdb;
int retval;
@@ -3727,12 +3743,20 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
startLoadingFile(sb.st_size, filename, rdbflags);
loadingSetFlags(filename, sb.st_size, 0);
/* Note that inside loadingSetFlags(), server.loading is set.
* emptyDbCallback() may yield back to event-loop to reply -LOADING. */
if (emptyDbFunc)
emptyDbFunc(); /* Flush existing db. */
loadingFireEvent(rdbflags);
rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb,rdbflags,rsi);
fclose(fp);
if (retval != C_OK && emptyDbFunc)
emptyDbFunc(); /* Clean up partial db. */
stopLoading(retval==C_OK);
/* Reclaim the cache backed by rdb */
if (retval == C_OK && !(rdbflags & RDBFLAGS_KEEP_CACHE)) {
+1
View File
@@ -136,6 +136,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
int rdbSaveObjectType(rio *rdb, robj *o);
int rdbLoadObjectType(rio *rdb);
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void));
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
int rdbSaveToSlavesSockets(int req, rdbSaveInfo *rsi);
+6
View File
@@ -3264,6 +3264,9 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
config.cluster_reissue_command = 0;
return REDIS_ERR;
}
/* Reset dbnum after reconnecting so we can re-select the previous db in cliSelect(). */
config.dbnum = 0;
cliSelect();
}
config.cluster_reissue_command = 0;
if (config.cluster_send_asking) {
@@ -3691,6 +3694,8 @@ static int evalMode(int argc, char **argv) {
/* Call it */
int eval_ldb = config.eval_ldb; /* Save it, may be reverted. */
retval = issueCommand(argc+3-got_comma, argv2);
for (j = 0; j < argc+3-got_comma; j++) sdsfree(argv2[j]);
zfree(argv2);
if (eval_ldb) {
if (!config.eval_ldb) {
/* If the debugging session ended immediately, there was an
@@ -6076,6 +6081,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
fixed = -1;
if (reply) freeReplyObject(reply);
if (slot_nodes) listRelease(slot_nodes);
goto cleanup;
}
assert(reply->type == REDIS_REPLY_ARRAY);
+9 -1
View File
@@ -35,6 +35,7 @@
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
@@ -46,8 +47,15 @@ void _serverAssert(const char *estr, const char *file, int line) {
}
void _serverPanic(const char *file, int line, const char *msg, ...) {
va_list ap;
char fmtmsg[256];
va_start(ap,msg);
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
va_end(ap);
fprintf(stderr, "------------------------------------------------");
fprintf(stderr, "!!! Software Failure. Press left mouse button to continue");
fprintf(stderr, "Guru Meditation: %s #%s:%d",msg,file,line);
fprintf(stderr, "Guru Meditation: %s #%s:%d",fmtmsg,file,line);
abort();
}
+10 -1
View File
@@ -60,10 +60,13 @@ typedef long long ustime_t;
#define REDISMODULE_OPEN_KEY_NOEXPIRE (1<<19)
/* Avoid any effects from fetching the key */
#define REDISMODULE_OPEN_KEY_NOEFFECTS (1<<20)
/* Allow access expired key that haven't deleted yet */
#define REDISMODULE_OPEN_KEY_ACCESS_EXPIRED (1<<21)
/* Mask of all REDISMODULE_OPEN_KEY_* values. Any new mode should be added to this list.
* Should not be used directly by the module, use RM_GetOpenKeyModesAll instead.
* Located here so when we will add new modes we will not forget to update it. */
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS | REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
/* List push and pop */
#define REDISMODULE_LIST_HEAD 0
@@ -1296,7 +1299,10 @@ REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisMod
REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) 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 int (*RedisModule_RegisterDefragCallbacks)(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAllocRaw)(RedisModuleDefragCtx *ctx, size_t size) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DefragFreeRaw)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DefragShouldStop)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, unsigned long cursor) REDISMODULE_ATTR;
@@ -1662,7 +1668,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(GetCommandKeysWithFlags);
REDISMODULE_GET_API(GetCurrentCommandName);
REDISMODULE_GET_API(RegisterDefragFunc);
REDISMODULE_GET_API(RegisterDefragCallbacks);
REDISMODULE_GET_API(DefragAlloc);
REDISMODULE_GET_API(DefragAllocRaw);
REDISMODULE_GET_API(DefragFreeRaw);
REDISMODULE_GET_API(DefragRedisModuleString);
REDISMODULE_GET_API(DefragShouldStop);
REDISMODULE_GET_API(DefragCursorSet);
+46 -37
View File
@@ -3,13 +3,15 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
#include "cluster.h"
#include "bio.h"
@@ -1736,12 +1738,24 @@ void replicationSendNewlineToMaster(void) {
}
/* Callback used by emptyData() while flushing away old data to load
* the new dataset received by the master and by discardTempDb()
* after loading succeeded or failed. */
* the new dataset received by the master or to clear partial db if loading
* fails. */
void replicationEmptyDbCallback(dict *d) {
UNUSED(d);
if (server.repl_state == REPL_STATE_TRANSFER)
replicationSendNewlineToMaster();
processEventsWhileBlocked();
}
/* Function to flush old db or the partial db on error. */
static void rdbLoadEmptyDbFunc(void) {
serverAssert(server.loading);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS;
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
}
/* Once we have a link with the master and the synchronization was
@@ -1765,6 +1779,9 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
* connection. */
server.master->flags |= CLIENT_MASTER;
/* Allocate a private query buffer for the master client instead of using the reusable query buffer.
* This is done because the master's query buffer data needs to be preserved for my sub-replicas to use. */
server.master->querybuf = sdsempty();
server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset;
server.master->read_reploff = server.master->reploff;
@@ -1778,27 +1795,6 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
if (dbid != -1) selectDb(server.master,dbid);
}
/* This function will try to re-enable the AOF file after the
* master-replica synchronization: if it fails after multiple attempts
* the replica cannot be considered reliable and exists with an
* error. */
void restartAOFAfterSYNC(void) {
unsigned int tries, max_tries = 10;
for (tries = 0; tries < max_tries; ++tries) {
if (startAppendOnly() == C_OK) break;
serverLog(LL_WARNING,
"Failed enabling the AOF after successful master synchronization! "
"Trying it again in one second.");
sleep(1);
}
if (tries == max_tries) {
serverLog(LL_WARNING,
"FATAL: this replica instance finished the synchronization with "
"its master, but the AOF can't be turned on. Exiting now.");
exit(1);
}
}
static int useDisklessLoad(void) {
/* compute boolean decision to use diskless load */
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
@@ -1831,7 +1827,7 @@ redisDb *disklessLoadInitTempDb(void) {
/* Helper function for readSyncBulkPayload() to discard our tempDb
* when the loading succeeded or failed. */
void disklessLoadDiscardTempDb(redisDb *tempDb) {
discardTempDb(tempDb, replicationEmptyDbCallback);
discardTempDb(tempDb);
}
/* If we know we got an entirely different data set from our master
@@ -2057,9 +2053,6 @@ void readSyncBulkPayload(connection *conn) {
NULL);
} else {
replicationAttachToNewMaster();
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Before loading the DB into memory we need to delete the readable
@@ -2093,13 +2086,22 @@ void readSyncBulkPayload(connection *conn) {
functionsLibCtxClear(functions_lib_ctx);
}
loadingSetFlags(NULL, server.repl_transfer_size, asyncLoading);
if (server.repl_diskless_load != REPL_DISKLESS_LOAD_SWAPDB) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
/* Note that inside loadingSetFlags(), server.loading is set.
* replicationEmptyDbCallback() may yield back to event-loop to
* reply -LOADING. */
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
}
loadingFireEvent(RDBFLAGS_REPLICATION);
rioInitWithConn(&rdb,conn,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */
connBlock(conn);
connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION, asyncLoading);
int loadingFailed = 0;
rdbLoadingCtx loadingCtx = { .dbarray = dbarray, .functions_lib_ctx = functions_lib_ctx };
@@ -2120,7 +2122,6 @@ void readSyncBulkPayload(connection *conn) {
}
if (loadingFailed) {
stopLoading(0);
cancelReplicationHandshake(1);
rioFreeConn(&rdb, NULL);
@@ -2138,6 +2139,11 @@ void readSyncBulkPayload(connection *conn) {
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Note that replicationEmptyDbCallback() may yield back to event
* loop to reply -LOADING if flushing the db takes a long time. So,
* stopLoading() must be called after emptyData() above. */
stopLoading(0);
/* Note that there's no point in restarting the AOF on SYNC
* failure, it'll be restarted when sync succeeds or the replica
* gets promoted. */
@@ -2213,7 +2219,7 @@ void readSyncBulkPayload(connection *conn) {
return;
}
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != RDB_OK) {
if (rdbLoadWithEmptyFunc(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION,rdbLoadEmptyDbFunc) != RDB_OK) {
serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization "
"DB from disk, check server logs.");
@@ -2225,9 +2231,6 @@ void readSyncBulkPayload(connection *conn) {
bg_unlink(server.rdb_filename);
}
/* If disk-based RDB loading fails, remove the half-loaded dataset. */
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
/* Note that there's no point in restarting the AOF on sync failure,
it'll be restarted when sync succeeds or replica promoted. */
return;
@@ -2281,7 +2284,10 @@ void readSyncBulkPayload(connection *conn) {
/* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending
* to the new file. */
if (server.aof_enabled) restartAOFAfterSYNC();
if (server.aof_enabled) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Starting AOF after a successful sync");
startAppendOnlyWithRetry();
}
return;
error:
@@ -2919,7 +2925,7 @@ write_error: /* Handle sendCommand() errors. */
}
int connectWithMaster(void) {
server.repl_transfer_s = connCreate(connTypeOfReplication());
server.repl_transfer_s = connCreate(server.el, connTypeOfReplication());
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
server.bind_source_addr, syncWithMaster) == C_ERR) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
@@ -3098,7 +3104,10 @@ void replicationUnsetMaster(void) {
/* Restart the AOF subsystem in case we shut it down during a sync when
* we were still a slave. */
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
if (server.aof_enabled && server.aof_state == AOF_OFF) {
serverLog(LL_NOTICE, "Restarting AOF after becoming master");
startAppendOnlyWithRetry();
}
}
/* This function is called when the slave lose the connection with the
+2 -1
View File
@@ -34,6 +34,7 @@
* ----------------------------------------------------------------------------------------
*/
#include "fast_float_strtod.h"
#include "resp_parser.h"
#include "server.h"
@@ -132,7 +133,7 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
if (len <= MAX_LONG_DOUBLE_CHARS) {
memcpy(buf,proto+1,len);
buf[len] = '\0';
d = strtod(buf,NULL); /* We expect a valid representation. */
d = fast_float_strtod(buf,NULL); /* We expect a valid representation. */
} else {
d = 0;
}
+150 -27
View File
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
@@ -734,6 +739,8 @@ long long getInstantaneousMetric(int metric) {
*
* The function always returns 0 as it never terminates the client. */
int clientsCronResizeQueryBuffer(client *c) {
/* If the client query buffer is NULL, it is using the reusable query buffer and there is nothing to do. */
if (c->querybuf == NULL) return 0;
size_t querybuf_size = sdsalloc(c->querybuf);
time_t idletime = server.unixtime - c->lastinteraction;
@@ -743,7 +750,18 @@ int clientsCronResizeQueryBuffer(client *c) {
/* There are two conditions to resize the query buffer: */
if (idletime > 2) {
/* 1) Query is idle for a long time. */
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
size_t remaining = sdslen(c->querybuf) - c->qb_pos;
if (!(c->flags & CLIENT_MASTER) && !remaining) {
/* If the client is not a master and no data is pending,
* The client can safely use the reusable query buffer in the next read - free the client's querybuf. */
sdsfree(c->querybuf);
/* By setting the querybuf to NULL, the client will use the reusable query buffer in the next read.
* We don't move the client to the reusable query buffer immediately, because if we allocated a private
* query buffer for the client, it's likely that the client will use it again soon. */
c->querybuf = NULL;
} else {
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
}
} else if (querybuf_size > PROTO_RESIZE_THRESHOLD && querybuf_size/2 > c->querybuf_peak) {
/* 2) Query buffer is too big for latest peak and is larger than
* resize threshold. Trim excess space but only up to a limit,
@@ -759,7 +777,7 @@ int clientsCronResizeQueryBuffer(client *c) {
/* Reset the peak again to capture the peak memory usage in the next
* cycle. */
c->querybuf_peak = sdslen(c->querybuf);
c->querybuf_peak = c->querybuf ? sdslen(c->querybuf) : 0;
/* We reset to either the current used, or currently processed bulk size,
* which ever is bigger. */
if (c->bulklen != -1 && (size_t)c->bulklen + 2 > c->querybuf_peak) c->querybuf_peak = c->bulklen + 2;
@@ -834,8 +852,9 @@ size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
int clientsCronTrackExpansiveClients(client *c, int time_idx) {
size_t in_usage = sdsZmallocSize(c->querybuf) + c->argv_len_sum +
(c->argv ? zmalloc_size(c->argv) : 0);
size_t qb_size = c->querybuf ? sdsZmallocSize(c->querybuf) : 0;
size_t argv_size = c->argv ? zmalloc_size(c->argv) : 0;
size_t in_usage = qb_size + c->argv_len_sum + argv_size;
size_t out_usage = getClientOutputBufferMemoryUsage(c);
/* Track the biggest values observed so far in this slot. */
@@ -930,7 +949,7 @@ void removeClientFromMemUsageBucket(client *c, int allow_eviction) {
* returns 1 if client eviction for this client is allowed, 0 otherwise.
*/
int updateClientMemUsageAndBucket(client *c) {
serverAssert(io_threads_op == IO_THREADS_OP_IDLE && c->conn);
serverAssert(pthread_equal(pthread_self(), server.main_thread_id) && c->conn);
int allow_eviction = clientEvictionAllowed(c);
removeClientFromMemUsageBucket(c, allow_eviction);
@@ -982,6 +1001,7 @@ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
* default server.hz value is 10, so sometimes here we need to process thousands
* of clients per second, turning this function into a source of latency.
*/
#define CLIENTS_CRON_PAUSE_IOTHREAD 8
#define CLIENTS_CRON_MIN_ITERATIONS 5
void clientsCron(void) {
/* Try to process at least numclients/server.hz of clients
@@ -1016,6 +1036,15 @@ void clientsCron(void) {
ClientsPeakMemInput[zeroidx] = 0;
ClientsPeakMemOutput[zeroidx] = 0;
/* Pause the IO threads that are processing clients, to let us access clients
* safely. In order to avoid increasing CPU usage by pausing all threads when
* there are too many io threads, we pause io threads in multiple batches. */
static int start = 1, end = 0;
if (server.io_threads_num >= 1 && listLength(server.clients) > 0) {
end = start + CLIENTS_CRON_PAUSE_IOTHREAD - 1;
if (end >= server.io_threads_num) end = server.io_threads_num - 1;
pauseIOThreadsRange(start, end);
}
while(listLength(server.clients) && iterations--) {
client *c;
@@ -1026,6 +1055,15 @@ void clientsCron(void) {
head = listFirst(server.clients);
c = listNodeValue(head);
listRotateHeadToTail(server.clients);
if (c->running_tid != IOTHREAD_MAIN_THREAD_ID &&
!(c->running_tid >= start && c->running_tid <= end))
{
/* Skip clients that are being processed by the IO threads that
* are not paused. */
continue;
}
/* The following functions do different service checks on the client.
* The protocol is that they return non-zero if the client was
* terminated. */
@@ -1046,6 +1084,14 @@ void clientsCron(void) {
if (closeClientOnOutputBufferLimitReached(c, 0)) continue;
}
/* Resume the IO threads that were paused */
if (end) {
resumeIOThreadsRange(start, end);
start = end + 1;
if (start >= server.io_threads_num) start = 1;
end = 0;
}
}
/* This function handles 'background' operations we are required to do
@@ -1494,9 +1540,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
migrateCloseTimedoutSockets();
}
/* Stop the I/O threads if we don't have enough pending work. */
stopThreadedIOIfNeeded();
/* Resize tracking keys table if needed. This is also done at every
* command execution, but we want to be sure that if the last command
* executed changes the value via CONFIG SET, the server will perform
@@ -1648,24 +1691,28 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
* events to handle. */
if (ProcessingEventsWhileBlocked) {
uint64_t processed = 0;
processed += handleClientsWithPendingReadsUsingThreads();
processed += connTypeProcessPendingData();
processed += connTypeProcessPendingData(server.el);
if (server.aof_state == AOF_ON || server.aof_state == AOF_WAIT_REWRITE)
flushAppendOnlyFile(0);
processed += handleClientsWithPendingWrites();
processed += freeClientsInAsyncFreeQueue();
/* Let the clients after the blocking call be processed. */
processClientsOfAllIOThreads();
/* New connections may have been established while blocked, clients from
* IO thread may have replies to write, ensure they are promptly sent to
* IO threads. */
processed += sendPendingClientsToIOThreads();
server.events_processed_while_blocked += processed;
return;
}
/* We should handle pending reads clients ASAP after event loop. */
handleClientsWithPendingReadsUsingThreads();
/* Handle pending data(typical TLS). (must be done before flushAppendOnlyFile) */
connTypeProcessPendingData();
connTypeProcessPendingData(server.el);
/* If any connection type(typical TLS) still has pending unread data don't sleep at all. */
int dont_sleep = connTypeHasPendingData();
int dont_sleep = connTypeHasPendingData(server.el);
/* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa),
@@ -1731,8 +1778,8 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
long long prev_fsynced_reploff = server.fsynced_reploff;
/* Write the AOF buffer on disk,
* must be done before handleClientsWithPendingWritesUsingThreads,
* in case of appendfsync=always. */
* must be done before handleClientsWithPendingWrites and
* sendPendingClientsToIOThreads, in case of appendfsync=always. */
if (server.aof_state == AOF_ON || server.aof_state == AOF_WAIT_REWRITE)
flushAppendOnlyFile(0);
@@ -1754,7 +1801,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
}
/* Handle writes with pending output buffers. */
handleClientsWithPendingWritesUsingThreads();
handleClientsWithPendingWrites();
/* Let io thread to handle its pending clients. */
sendPendingClientsToIOThreads();
/* Record cron time in beforeSleep. This does not include the time consumed by AOF writing and IO writing above. */
monotime cron_start_time_after_write = getMonotonicUs();
@@ -2055,7 +2105,7 @@ void initServerConfig(void) {
server.bindaddr[j] = zstrdup(default_bindaddr[j]);
memset(server.listeners, 0x00, sizeof(server.listeners));
server.active_expire_enabled = 1;
server.lazy_expire_disabled = 0;
server.allow_access_expired = 0;
server.skip_checksum_validation = 0;
server.loading = 0;
server.async_loading = 0;
@@ -2083,6 +2133,7 @@ void initServerConfig(void) {
memset(server.blocked_clients_by_type,0,
sizeof(server.blocked_clients_by_type));
server.shutdown_asap = 0;
server.crashing = 0;
server.shutdown_flags = 0;
server.shutdown_mstime = 0;
server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
@@ -2549,9 +2600,9 @@ void resetServerStats(void) {
server.stat_sync_full = 0;
server.stat_sync_partial_ok = 0;
server.stat_sync_partial_err = 0;
server.stat_io_reads_processed = 0;
atomicSet(server.stat_io_reads_processed, 0);
atomicSet(server.stat_total_reads_processed, 0);
server.stat_io_writes_processed = 0;
atomicSet(server.stat_io_writes_processed, 0);
atomicSet(server.stat_total_writes_processed, 0);
atomicSet(server.stat_client_qbuf_limit_disconnections, 0);
server.stat_client_outbuf_limit_disconnections = 0;
@@ -2671,7 +2722,7 @@ void initServer(void) {
flags |= KVSTORE_FREE_EMPTY_DICTS;
}
for (j = 0; j < server.dbnum; j++) {
server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags | KVSTORE_ALLOC_META_KEYS_HIST);
server.db[j].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
server.db[j].hexpires = ebCreate();
server.db[j].expires_cursor = 0;
@@ -2744,6 +2795,7 @@ void initServer(void) {
server.aof_last_write_errno = 0;
server.repl_good_slaves_count = 0;
server.last_sig_received = 0;
memset(server.io_threads_clients_num, 0, sizeof(server.io_threads_clients_num));
/* Initiate acl info struct */
server.acl_info.invalid_cmd_accesses = 0;
@@ -5501,8 +5553,8 @@ void releaseInfoSectionDict(dict *sec) {
* The resulting dictionary should be released with releaseInfoSectionDict. */
dict *genInfoSectionDict(robj **argv, int argc, char **defaults, int *out_all, int *out_everything) {
char *default_sections[] = {
"server", "clients", "memory", "persistence", "stats", "replication",
"cpu", "module_list", "errorstats", "cluster", "keyspace", NULL};
"server", "clients", "memory", "persistence", "stats", "replication", "threads",
"cpu", "module_list", "errorstats", "cluster", "keyspace", "keysizes", NULL};
if (!defaults)
defaults = default_sections;
@@ -5852,6 +5904,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
long long current_active_defrag_time = server.stat_last_active_defrag_time ?
(long long) elapsedUs(server.stat_last_active_defrag_time): 0;
long long stat_client_qbuf_limit_disconnections;
long long stat_io_reads_processed, stat_io_writes_processed;
atomicGet(server.stat_total_reads_processed, stat_total_reads_processed);
atomicGet(server.stat_total_writes_processed, stat_total_writes_processed);
atomicGet(server.stat_net_input_bytes, stat_net_input_bytes);
@@ -5859,6 +5912,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
atomicGet(server.stat_net_repl_input_bytes, stat_net_repl_input_bytes);
atomicGet(server.stat_net_repl_output_bytes, stat_net_repl_output_bytes);
atomicGet(server.stat_client_qbuf_limit_disconnections, stat_client_qbuf_limit_disconnections);
atomicGet(server.stat_io_reads_processed, stat_io_reads_processed);
atomicGet(server.stat_io_writes_processed, stat_io_writes_processed);
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info, "# Stats\r\n" FMTARGS(
@@ -5910,8 +5965,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
"dump_payload_sanitizations:%lld\r\n", server.stat_dump_payload_sanitizations,
"total_reads_processed:%lld\r\n", stat_total_reads_processed,
"total_writes_processed:%lld\r\n", stat_total_writes_processed,
"io_threaded_reads_processed:%lld\r\n", server.stat_io_reads_processed,
"io_threaded_writes_processed:%lld\r\n", server.stat_io_writes_processed,
"io_threaded_reads_processed:%lld\r\n", stat_io_reads_processed,
"io_threaded_writes_processed:%lld\r\n", stat_io_writes_processed,
"client_query_buffer_limit_disconnections:%lld\r\n", stat_client_qbuf_limit_disconnections,
"client_output_buffer_limit_disconnections:%lld\r\n", server.stat_client_outbuf_limit_disconnections,
"reply_buffer_shrinks:%lld\r\n", server.stat_reply_buffer_shrinks,
@@ -6060,6 +6115,15 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
#endif /* RUSAGE_THREAD */
}
/* Threads */
if (all_sections || (dictFind(section_dict,"threads") != NULL)) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info, "# Threads\r\n");
for (j = 0; j < server.io_threads_num; j++) {
info = sdscatprintf(info, "io_thread_%d:clients=%d\r\n", j, server.io_threads_clients_num[j]);
}
}
/* Modules */
if (all_sections || (dictFind(section_dict,"module_list") != NULL) || (dictFind(section_dict,"modules") != NULL)) {
if (sections++) info = sdscat(info,"\r\n");
@@ -6130,6 +6194,60 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
}
}
/* keysizes */
if (all_sections || (dictFind(section_dict,"keysizes") != NULL)) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info, "# Keysizes\r\n");
char *typestr[] = {
[OBJ_STRING] = "distrib_strings_sizes",
[OBJ_LIST] = "distrib_lists_items",
[OBJ_SET] = "distrib_sets_items",
[OBJ_ZSET] = "distrib_zsets_items",
[OBJ_HASH] = "distrib_hashes_items"
};
serverAssert(sizeof(typestr)/sizeof(typestr[0]) == OBJ_TYPE_BASIC_MAX);
for (int dbnum = 0; dbnum < server.dbnum; dbnum++) {
char *expSizeLabels[] = {
"1", "2", "4", "8", "16", "32", "64", "128", "256", "512", /* Byte */
"1K", "2K", "4K", "8K", "16K", "32K", "64K", "128K", "256K", "512K", /* Kilo */
"1M", "2M", "4M", "8M", "16M", "32M", "64M", "128M", "256M", "512M", /* Mega */
"1G", "2G", "4G", "8G", "16G", "32G", "64G", "128G", "256G", "512G", /* Giga */
"1T", "2T", "4T", "8T", "16T", "32T", "64T", "128T", "256T", "512T", /* Tera */
"1P", "2P", "4P", "8P", "16P", "32P", "64P", "128P", "256P", "512P", /* Peta */
"1E", "2E", "4E", "8E" /* Exa */
};
if (kvstoreSize(server.db[dbnum].keys) == 0)
continue;
for (int type = 0; type < OBJ_TYPE_BASIC_MAX; type++) {
uint64_t *kvstoreHist = kvstoreGetMetadata(server.db[dbnum].keys)->keysizes_hist[type];
char buf[10000];
int cnt = 0, buflen = 0;
/* Print histogram to temp buf[]. First bin is garbage */
buflen += snprintf(buf + buflen, sizeof(buf) - buflen, "db%d_%s:", dbnum, typestr[type]);
for (int i = 0; i < MAX_KEYSIZES_BINS; i++) {
if (kvstoreHist[i] == 0)
continue;
int res = snprintf(buf + buflen, sizeof(buf) - buflen,
(cnt == 0) ? "%s=%llu" : ",%s=%llu",
expSizeLabels[i], (unsigned long long) kvstoreHist[i]);
if (res < 0) break;
buflen += res;
cnt += kvstoreHist[i];
}
/* Print the temp buf[] to the info string */
if (cnt) info = sdscatprintf(info, "%s\r\n", buf);
}
}
}
/* Get info from modules.
* Returned when the user asked for "everything", "modules", or a specific module section.
* We're not aware of the module section names here, and we rather avoid the search when we can.
@@ -6567,7 +6685,7 @@ void dismissMemory(void* ptr, size_t size_hint) {
void dismissClientMemory(client *c) {
/* Dismiss client query buffer and static reply buffer. */
dismissMemory(c->buf, c->buf_usable_size);
dismissSds(c->querybuf);
if (c->querybuf) dismissSds(c->querybuf);
/* Dismiss argv array only if we estimate it contains a big buffer. */
if (c->argc && c->argv_len_sum/c->argc >= server.page_size) {
for (int i = 0; i < c->argc; i++) {
@@ -7212,6 +7330,11 @@ int main(int argc, char **argv) {
loadDataFromDisk();
aofOpenIfNeededOnServerStart();
aofDelHistoryFiles();
/* While loading data, we delay applying "appendonly" config change.
* If there was a config change while we were inside loadDataFromDisk()
* above, we'll apply it here. */
applyAppendOnlyConfig();
if (server.cluster_enabled) {
serverAssert(verifyClusterConfigWithData() == C_OK);
}
+128 -27
View File
@@ -41,10 +41,6 @@
#include <systemd/sd-daemon.h>
#endif
#ifndef static_assert
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
#endif
typedef long long mstime_t; /* millisecond time type. */
typedef long long ustime_t; /* microsecond time type. */
@@ -65,6 +61,7 @@ typedef long long ustime_t; /* microsecond time type. */
N-elements flat arrays */
#include "rax.h" /* Radix tree */
#include "connection.h" /* Connection abstraction */
#include "eventnotifier.h" /* Event notification */
#define REDISMODULE_CORE 1
typedef struct redisObject robj;
@@ -188,6 +185,14 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
/* Hash table parameters */
#define HASHTABLE_MAX_LOAD_FACTOR 1.618 /* Maximum hash table load factor. */
/* Max number of IO threads */
#define IO_THREADS_MAX_NUM 128
/* Main thread id for doing IO work, whatever we enable or disable io thread
* the main thread always does IO work, so we can consider that the main thread
* is the io thread 0. */
#define IOTHREAD_MAIN_THREAD_ID 0
/* Command flags. Please check the definition of struct redisCommand in this file
* for more information about the meaning of every flag. */
#define CMD_WRITE (1ULL<<0)
@@ -392,6 +397,29 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
/* Any flag that does not let optimize FLUSH SYNC to run it in bg as blocking client ASYNC */
#define CLIENT_AVOID_BLOCKING_ASYNC_FLUSH (CLIENT_DENY_BLOCKING|CLIENT_MULTI|CLIENT_LUA_DEBUG|CLIENT_LUA_DEBUG_SYNC|CLIENT_MODULE)
/* Client flags for client IO */
#define CLIENT_IO_READ_ENABLED (1ULL<<0) /* Client can read from socket. */
#define CLIENT_IO_WRITE_ENABLED (1ULL<<1) /* Client can write to socket. */
#define CLIENT_IO_PENDING_COMMAND (1ULL<<2) /* Similar to CLIENT_PENDING_COMMAND. */
#define CLIENT_IO_REUSABLE_QUERYBUFFER (1ULL<<3) /* The client is using the reusable query buffer. */
#define CLIENT_IO_CLOSE_ASAP (1ULL<<4) /* Close this client ASAP in IO thread. */
/* Definitions for client read errors. These error codes are used to indicate
* various issues that can occur while reading or parsing data from a client. */
#define CLIENT_READ_TOO_BIG_INLINE_REQUEST 1
#define CLIENT_READ_UNBALANCED_QUOTES 2
#define CLIENT_READ_MASTER_USING_INLINE_PROTOCAL 3
#define CLIENT_READ_TOO_BIG_MBULK_COUNT_STRING 4
#define CLIENT_READ_TOO_BIG_BUCK_COUNT_STRING 5
#define CLIENT_READ_EXPECTED_DOLLAR 6
#define CLIENT_READ_INVALID_BUCK_LENGTH 7
#define CLIENT_READ_UNAUTH_BUCK_LENGTH 8
#define CLIENT_READ_INVALID_MULTIBUCK_LENGTH 9
#define CLIENT_READ_UNAUTH_MBUCK_COUNT 10
#define CLIENT_READ_CONN_DISCONNECTED 11
#define CLIENT_READ_CONN_CLOSED 12
#define CLIENT_READ_REACHED_MAX_QUERYBUF 13
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
typedef enum blocking_type {
@@ -580,6 +608,12 @@ typedef enum {
#define SHUTDOWN_NOW 4 /* Don't wait for replicas to catch up. */
#define SHUTDOWN_FORCE 8 /* Don't let errors prevent shutdown. */
/* IO thread pause status */
#define IO_THREAD_UNPAUSED 0
#define IO_THREAD_PAUSING 1
#define IO_THREAD_PAUSED 2
#define IO_THREAD_RESUMING 3
/* Command call flags, see call() function */
#define CMD_CALL_NONE 0
#define CMD_CALL_PROPAGATE_AOF (1<<0)
@@ -697,6 +731,7 @@ typedef enum {
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
#define OBJ_TYPE_BASIC_MAX 5 /* Max number of basic object types. */
/* The "module" object type is a special one that signals that the object
* is one directly managed by a Redis module. In this case the value points
@@ -820,6 +855,8 @@ struct RedisModule {
int blocked_clients; /* Count of RedisModuleBlockedClient in this module. */
RedisModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
RedisModuleDefragFunc defrag_cb; /* Callback for global data defrag. */
RedisModuleDefragFunc defrag_start_cb; /* Callback indicating defrag started. */
RedisModuleDefragFunc defrag_end_cb; /* Callback indicating defrag ended. */
struct moduleLoadQueueEntry *loadmod; /* Module load arguments for config rewrite. */
int num_commands_with_acl_categories; /* Number of commands in this module included in acl categories */
int onload; /* Flag to identify if the call is being made from Onload (0 or 1) */
@@ -966,7 +1003,7 @@ typedef struct replBufBlock {
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
kvstore *keys; /* The keyspace for this DB */
kvstore *keys; /* The keyspace for this DB. As metadata, holds keysizes histogram */
kvstore *expires; /* Timeout of keys with a timeout set */
ebuckets hexpires; /* Hash expiration DS. Single TTL per hash (of next min field to expire) */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
@@ -1158,6 +1195,10 @@ typedef struct client {
uint64_t id; /* Client incremental unique ID. */
uint64_t flags; /* Client flags: CLIENT_* macros. */
connection *conn;
uint8_t tid; /* Thread assigned ID this client is bound to. */
uint8_t running_tid; /* Thread assigned ID this client is running on. */
uint8_t io_flags; /* Accessed by both main and IO threads, but not modified concurrently */
uint8_t read_error; /* Client read error: CLIENT_READ_* macros. */
int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
@@ -1225,8 +1266,8 @@ typedef struct client {
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
listNode *client_list_node; /* list node in client list */
listNode *io_thread_client_list_node; /* list node in io thread client list */
listNode *postponed_list_node; /* list node within the postponed list */
listNode *pending_read_list_node; /* list node in clients pending read list */
void *module_blocked_client; /* Pointer to the RedisModuleBlockedClient associated with this
* client. This is set in case of module authentication before the
* unblocked client is reprocessed to handle reply callbacks. */
@@ -1279,6 +1320,20 @@ typedef struct client {
#endif
} client;
typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) {
uint8_t id; /* The unique ID assigned, if IO_THREADS_MAX_NUM is more
* than 256, we should also promote the data type. */
pthread_t tid; /* Pthread ID */
redisAtomic int paused; /* Paused status for the io thread. */
aeEventLoop *el; /* Main event loop of io thread. */
list *pending_clients; /* List of clients with pending writes. */
list *processing_clients; /* List of clients being processed. */
eventNotifier *pending_clients_notifier; /* Used to wake up the loop when write should be performed. */
pthread_mutex_t pending_clients_mutex; /* Mutex for pending write list */
list *pending_clients_to_main_thread; /* Clients that are waiting to be executed by the main thread. */
list *clients; /* IO thread managed clients. */
} IOThread;
/* ACL information */
typedef struct aclInfo {
long long user_auth_failures; /* Auth failure counts on user level */
@@ -1566,6 +1621,7 @@ struct redisServer {
int errors_enabled; /* If true, errorstats is enabled, and we will add new errors. */
unsigned int lruclock; /* Clock for LRU eviction */
volatile sig_atomic_t shutdown_asap; /* Shutdown ordered by signal handler. */
volatile sig_atomic_t crashing; /* Server is crashing report. */
mstime_t shutdown_mstime; /* Timestamp to limit graceful shutdown. */
int last_sig_received; /* Indicates the last SIGNAL received, if any (e.g., SIGINT or SIGTERM). */
int shutdown_flags; /* Flags passed to prepareForShutdown(). */
@@ -1636,6 +1692,7 @@ struct redisServer {
redisAtomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */
int io_threads_num; /* Number of IO threads to use. */
int io_threads_clients_num[IO_THREADS_MAX_NUM]; /* Number of clients assigned to each IO thread. */
int io_threads_do_reads; /* Read and parse from IO threads? */
int io_threads_active; /* Is IO threads currently active? */
long long events_processed_while_blocked; /* processEventsWhileBlocked() */
@@ -1708,8 +1765,8 @@ struct redisServer {
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
redisAtomic long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
redisAtomic long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
redisAtomic long long stat_total_reads_processed; /* Total number of read events processed */
redisAtomic long long stat_total_writes_processed; /* Total number of write events processed */
redisAtomic long long stat_client_qbuf_limit_disconnections; /* Total number of clients reached query buf length limit */
@@ -1741,7 +1798,7 @@ struct redisServer {
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_expire_enabled; /* Can be disabled for testing purposes. */
int active_expire_effort; /* From 1 (default) to 10, active effort. */
int lazy_expire_disabled; /* If > 0, don't trigger lazy expire */
int allow_access_expired; /* If > 0, allow access to logically expired keys */
int active_defrag_enabled;
int sanitize_dump_payload; /* Enables deep sanitization for ziplist and listpack in RDB and RESTORE. */
int skip_checksum_validation; /* Disable checksum validation for RDB and RESTORE payload. */
@@ -2459,11 +2516,6 @@ typedef struct {
#define OBJ_HASH_KEY 1
#define OBJ_HASH_VALUE 2
#define IO_THREADS_OP_IDLE 0
#define IO_THREADS_OP_READ 1
#define IO_THREADS_OP_WRITE 2
extern int io_threads_op;
/* Hash-field data type (of t_hash.c) */
typedef mstr hfield;
extern mstrKind mstrFieldKind;
@@ -2555,6 +2607,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
int moduleDefragValue(robj *key, robj *obj, int dbid);
int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long endtime, int dbid);
void moduleDefragGlobals(void);
void moduleDefragStart(void);
void moduleDefragEnd(void);
void *moduleGetHandleByName(char *modulename);
int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd);
@@ -2627,6 +2681,7 @@ 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 addReplyLongLongFromStr(client *c, robj* str);
void addReplyArrayLen(client *c, long length);
void addReplyMapLen(client *c, long length);
void addReplySetLen(client *c, long length);
@@ -2675,9 +2730,6 @@ void whileBlockedCron(void);
void blockingOperationStarts(void);
void blockingOperationEnds(void);
int handleClientsWithPendingWrites(void);
int handleClientsWithPendingWritesUsingThreads(void);
int handleClientsWithPendingReadsUsingThreads(void);
int stopThreadedIOIfNeeded(void);
int clientHasPendingReplies(client *c);
int updateClientMemUsageAndBucket(client *c);
void removeClientFromMemUsageBucket(client *c, int allow_eviction);
@@ -2686,10 +2738,31 @@ int writeToClient(client *c, int handler_installed);
void linkClient(client *c);
void protectClient(client *c);
void unprotectClient(client *c);
void initThreadedIO(void);
client *lookupClientByID(uint64_t id);
int authRequired(client *c);
void putClientInPendingWriteQueue(client *c);
/* reply macros */
#define ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c, str) addReplyBulkCBuffer(c, str, strlen(str))
/* iothread.c - the threaded io implementation */
void initThreadedIO(void);
void killIOThreads(void);
void pauseIOThread(int id);
void resumeIOThread(int id);
void pauseAllIOThreads(void);
void resumeAllIOThreads(void);
void pauseIOThreadsRange(int start, int end);
void resumeIOThreadsRange(int start, int end);
int resizeAllIOThreadsEventLoops(size_t newsize);
int sendPendingClientsToIOThreads(void);
void enqueuePendingClientsToMainThread(client *c, int unbind);
void putInPendingClienstForIOThreads(client *c);
void handleClientReadError(client *c);
void unbindClientFromIOThreadEventLoop(client *c);
void processClientsOfAllIOThreads(void);
void assignClientToIOThread(client *c);
void fetchClientFromIOThread(client *c);
int isClientMustHandledByMainThread(client *c);
/* logreqres.c - logging of requests and responses */
void reqresReset(client *c, int free_buf);
@@ -2740,7 +2813,7 @@ robj *listTypeGet(listTypeEntry *entry);
unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval);
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
void listTypeReplace(listTypeEntry *entry, robj *value);
int listTypeEqual(listTypeEntry *entry, robj *o);
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len);
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
robj *listTypeDup(robj *o);
void listTypeDelRange(robj *o, long start, long stop);
@@ -2791,6 +2864,7 @@ int isSdsRepresentableAsLongLong(sds s, long long *llval);
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
robj *tryObjectEncoding(robj *o);
robj *tryObjectEncodingEx(robj *o, int try_trim);
size_t getObjectLength(robj *o);
robj *getDecodedObject(robj *o);
size_t stringObjectLen(robj *o);
robj *createStringObjectFromLongLong(long long value);
@@ -2877,6 +2951,8 @@ const char *getFailoverStateString(void);
/* Generic persistence functions */
void startLoadingFile(size_t size, char* filename, int rdbflags);
void startLoading(size_t size, int rdbflags, int async);
void loadingSetFlags(char *filename, size_t size, int async);
void loadingFireEvent(int rdbflags);
void loadingAbsProgress(off_t pos);
void loadingIncrProgress(off_t size);
void stopLoading(int success);
@@ -2904,9 +2980,10 @@ int rewriteAppendOnlyFileBackground(void);
int loadAppendOnlyFiles(aofManifest *am);
void stopAppendOnly(void);
int startAppendOnly(void);
void startAppendOnlyWithRetry(void);
void applyAppendOnlyConfig(void);
void backgroundRewriteDoneHandler(int exitcode, int bysignal);
void killAppendOnlyChild(void);
void restartAOFAfterSYNC(void);
void aofLoadManifestFromDisk(void);
void aofOpenIfNeededOnServerStart(void);
void aofManifestFree(aofManifest *am);
@@ -3127,6 +3204,8 @@ void checkChildrenDone(void);
int setOOMScoreAdj(int process_class);
void rejectCommandFormat(client *c, const char *fmt, ...);
void *activeDefragAlloc(void *ptr);
void *activeDefragAllocRaw(size_t size);
void activeDefragFreeRaw(void *ptr);
robj *activeDefragStringOb(robj* ob);
void dismissSds(sds s);
void dismissMemory(void* ptr, size_t size_hint);
@@ -3208,6 +3287,7 @@ typedef struct dictExpireMetadata {
#define HFE_LAZY_AVOID_HASH_DEL (1<<1) /* Avoid deleting hash if the field is the last one */
#define HFE_LAZY_NO_NOTIFICATION (1<<2) /* Do not send notification, used when multiple fields
* may expire and only one notification is desired. */
#define HFE_LAZY_ACCESS_EXPIRED (1<<3) /* Avoid lazy expire and allow access to expired fields */
void hashTypeConvert(robj *o, int enc, ebuckets *hexpires);
void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end);
@@ -3349,16 +3429,20 @@ long long getModuleNumericConfig(ModuleConfig *module_config);
int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err);
/* db.c -- Keyspace access API */
void updateKeysizesHist(redisDb *db, int didx, uint32_t type, uint64_t oldLen, uint64_t newLen);
int removeExpire(redisDb *db, robj *key);
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
void deleteEvictedKeyAndPropagate(redisDb *db, robj *keyobj, long long *key_mem_freed);
void propagateDeletion(redisDb *db, robj *key, int lazy);
int keyIsExpired(redisDb *db, robj *key);
long long getExpire(redisDb *db, robj *key);
void setExpire(client *c, redisDb *db, robj *key, long long when);
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde);
int checkAlreadyExpired(long long when);
int parseExtendedExpireArgumentsOrReply(client *c, int *flags);
robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref);
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
@@ -3368,16 +3452,18 @@ robj *objectCommandLookupOrReply(client *c, robj *key, 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
#define LOOKUP_NOTOUCH (1<<0) /* Don't update LRU. */
#define LOOKUP_NONOTIFY (1<<1) /* Don't trigger keyspace event on key misses. */
#define LOOKUP_NOSTATS (1<<2) /* Don't update keyspace hits/misses counters. */
#define LOOKUP_WRITE (1<<3) /* Delete expired keys even in replicas. */
#define LOOKUP_NOEXPIRE (1<<4) /* Avoid deleting lazy expired keys. */
#define LOOKUP_NOTOUCH (1<<0) /* Don't update LRU. */
#define LOOKUP_NONOTIFY (1<<1) /* Don't trigger keyspace event on key misses. */
#define LOOKUP_NOSTATS (1<<2) /* Don't update keyspace hits/misses counters. */
#define LOOKUP_WRITE (1<<3) /* Delete expired keys even in replicas. */
#define LOOKUP_NOEXPIRE (1<<4) /* Avoid deleting lazy expired keys. */
#define LOOKUP_ACCESS_EXPIRED (1<<5) /* Allow lookup to expired key. */
#define LOOKUP_NOEFFECTS (LOOKUP_NONOTIFY | LOOKUP_NOSTATS | LOOKUP_NOTOUCH | LOOKUP_NOEXPIRE) /* Avoid any effects from fetching the key */
dictEntry *dbAdd(redisDb *db, robj *key, robj *val);
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
void dbReplaceValue(redisDb *db, robj *key, robj *val);
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de);
#define SETKEY_KEEPTTL 1
#define SETKEY_NO_SIGNAL 2
@@ -3385,12 +3471,26 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
#define SETKEY_DOESNT_EXIST 8
#define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de);
robj *dbRandomKey(redisDb *db);
int dbGenericDelete(redisDb *db, robj *key, int async, int flags);
int dbSyncDelete(redisDb *db, robj *key);
int dbDelete(redisDb *db, robj *key);
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de);
#define FLUSH_TYPE_ALL 0
#define FLUSH_TYPE_DB 1
#define FLUSH_TYPE_SLOTS 2
typedef struct SlotRange {
unsigned short first, last;
} SlotRange;
typedef struct SlotsFlush {
int numRanges;
SlotRange ranges[];
} SlotsFlush;
void replySlotsFlushAndFree(client *c, SlotsFlush *sflush);
int flushCommandCommon(client *c, int type, int flags, SlotsFlush *sflush);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
#define EMPTYDB_NOFUNCTIONS (1<<1) /* Indicate not to flush the functions. */
@@ -3399,7 +3499,7 @@ long long emptyDbStructure(redisDb *dbarray, int dbnum, int async, void(callback
void flushAllDataAndResetRDB(int flags);
long long dbTotalServerKeyCount(void);
redisDb *initTempDb(void);
void discardTempDb(redisDb *tempDb, void(callback)(dict*));
void discardTempDb(redisDb *tempDb);
int selectDb(client *c, int id);
@@ -3634,6 +3734,7 @@ void scardCommand(client *c);
void spopCommand(client *c);
void srandmemberCommand(client *c);
void sinterCommand(client *c);
void smembersCommand(client *c);
void sinterCardCommand(client *c);
void sinterstoreCommand(client *c);
void sunionCommand(client *c);
@@ -3760,6 +3861,7 @@ void migrateCommand(client *c);
void askingCommand(client *c);
void readonlyCommand(client *c);
void readwriteCommand(client *c);
void sflushCommand(client *c);
int verifyDumpPayload(unsigned char *p, size_t len, uint16_t *rdbver_ptr);
void dumpCommand(client *c);
void objectCommand(client *c);
@@ -3863,7 +3965,6 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
sds catSubCommandFullname(const char *parent_name, const char *sub_name);
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand, const char *declared_name);
void debugDelay(int usec);
void killIOThreads(void);
void killThreads(void);
void makeThreadKillable(void);
void swapMainDbWithTempDb(redisDb *tempDb);
+25 -12
View File
@@ -53,11 +53,12 @@ static ConnectionType CT_Socket;
* be embedded in different structs, not just client.
*/
static connection *connCreateSocket(void) {
static connection *connCreateSocket(struct aeEventLoop *el) {
connection *conn = zcalloc(sizeof(connection));
conn->type = &CT_Socket;
conn->fd = -1;
conn->iovcnt = IOV_MAX;
conn->el = el;
return conn;
}
@@ -72,9 +73,9 @@ static connection *connCreateSocket(void) {
* is not in an error state (which is not possible for a socket connection,
* but could but possible with other protocols).
*/
static connection *connCreateAcceptedSocket(int fd, void *priv) {
static connection *connCreateAcceptedSocket(struct aeEventLoop *el, int fd, void *priv) {
UNUSED(priv);
connection *conn = connCreateSocket();
connection *conn = connCreateSocket(el);
conn->fd = fd;
conn->state = CONN_STATE_ACCEPTING;
return conn;
@@ -93,7 +94,7 @@ static int connSocketConnect(connection *conn, const char *addr, int port, const
conn->state = CONN_STATE_CONNECTING;
conn->conn_handler = connect_handler;
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
aeCreateFileEvent(conn->el, conn->fd, AE_WRITABLE,
conn->type->ae_handler, conn);
return C_OK;
@@ -114,7 +115,7 @@ static void connSocketShutdown(connection *conn) {
/* Close the connection and free resources. */
static void connSocketClose(connection *conn) {
if (conn->fd != -1) {
aeDeleteFileEvent(server.el,conn->fd, AE_READABLE | AE_WRITABLE);
if (conn->el) aeDeleteFileEvent(conn->el, conn->fd, AE_READABLE | AE_WRITABLE);
close(conn->fd);
conn->fd = -1;
}
@@ -190,6 +191,15 @@ static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_hand
return ret;
}
/* Rebind the connection to another event loop, read/write handlers must not
* be installed in the current event loop, otherwise it will cause two event
* loops to manage the same connection at the same time. */
static int connSocketRebindEventLoop(connection *conn, aeEventLoop *el) {
serverAssert(!conn->el && !conn->read_handler && !conn->write_handler);
conn->el = el;
return C_OK;
}
/* Register a write handler, to be called when the connection is writable.
* If NULL, the existing handler is removed.
*
@@ -207,9 +217,9 @@ static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc fu
else
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
if (!conn->write_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
aeDeleteFileEvent(conn->el,conn->fd,AE_WRITABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
if (aeCreateFileEvent(conn->el,conn->fd,AE_WRITABLE,
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
@@ -222,9 +232,9 @@ static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc fun
conn->read_handler = func;
if (!conn->read_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
aeDeleteFileEvent(conn->el,conn->fd,AE_READABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,
if (aeCreateFileEvent(conn->el,conn->fd,
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
@@ -250,7 +260,7 @@ static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientD
conn->state = CONN_STATE_CONNECTED;
}
if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
if (!conn->write_handler) aeDeleteFileEvent(conn->el, conn->fd, AE_WRITABLE);
if (!callHandler(conn, conn->conn_handler)) return;
conn->conn_handler = NULL;
@@ -291,7 +301,6 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
int cport, cfd;
int max = server.max_new_conns_per_cycle;
char cip[NET_IP_STR_LEN];
UNUSED(el);
UNUSED(mask);
UNUSED(privdata);
@@ -304,7 +313,7 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(connCreateAcceptedSocket(cfd, NULL),0,cip);
acceptCommonHandler(connCreateAcceptedSocket(el,cfd,NULL), 0, cip);
}
}
@@ -397,6 +406,10 @@ static ConnectionType CT_Socket = {
.blocking_connect = connSocketBlockingConnect,
.accept = connSocketAccept,
/* event loop */
.unbind_event_loop = NULL,
.rebind_event_loop = connSocketRebindEventLoop,
/* IO */
.write = connSocketWrite,
.writev = connSocketWritev,
+8 -4
View File
@@ -7,7 +7,7 @@
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
#include "fast_float_strtod.h"
#include "server.h"
#include "pqsort.h" /* Partial qsort for SORT+LIMIT */
#include <math.h> /* isnan() */
@@ -241,8 +241,12 @@ void sortCommandGeneric(client *c, int readonly) {
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
/* If GET is specified with a real pattern, we can't accept it in cluster mode,
* unless we can make sure the keys formed by the pattern are in the same slot
* as the key to sort. */
if (server.cluster_enabled && patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) != getKeySlot(c->argv[1]->ptr)) {
* as the key to sort. The pattern # represents the key itself, so just skip
* pattern slot check. */
if (server.cluster_enabled &&
strcmp(c->argv[j+1]->ptr, "#") &&
patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) != getKeySlot(c->argv[1]->ptr))
{
addReplyError(c, "GET option of SORT denied in Cluster mode when "
"keys formed by the pattern may be in different slots.");
syntax_error++;
@@ -472,7 +476,7 @@ void sortCommandGeneric(client *c, int readonly) {
if (sdsEncodedObject(byval)) {
char *eptr;
vector[j].u.score = strtod(byval->ptr,&eptr);
vector[j].u.score = fast_float_strtod(byval->ptr,&eptr);
if (eptr[0] != '\0' || errno == ERANGE ||
isnan(vector[j].u.score))
{
+98 -13
View File
@@ -422,8 +422,13 @@ void listpackExExpire(redisDb *db, robj *o, ExpireInfo *info) {
expired++;
}
if (expired)
if (expired) {
lpt->lp = lpDeleteRange(lpt->lp, 0, expired * 3);
/* update keysizes */
unsigned long l = lpLength(lpt->lp) / 3;
updateKeysizesHist(db, getKeySlot(lpt->key), OBJ_HASH, l + expired, l);
}
min = hashTypeGetMinExpire(o, 1 /*accurate*/);
info->nextExpireTime = min;
@@ -546,6 +551,11 @@ SetExRes hashTypeSetExpiryListpack(HashTypeSetEx *ex, sds field,
if (unlikely(checkAlreadyExpired(expireAt))) {
propagateHashFieldDeletion(ex->db, ex->key->ptr, field, sdslen(field));
hashTypeDelete(ex->hashObj, field, 1);
/* get listpack length */
listpackEx *lpt = ((listpackEx *) ex->hashObj->ptr);
unsigned long length = lpLength(lpt->lp) / 3;
updateKeysizesHist(ex->db, getKeySlot(ex->key->ptr), OBJ_HASH, length+1, length);
server.stat_expired_subkeys++;
ex->fieldDeleted++;
return HSETEX_DELETED;
@@ -734,7 +744,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
serverPanic("Unknown hash encoding");
}
if (expiredAt >= (uint64_t) commandTimeSnapshot())
if ((expiredAt >= (uint64_t) commandTimeSnapshot()) || (hfeFlags & HFE_LAZY_ACCESS_EXPIRED))
return GETF_OK;
if (server.masterhost) {
@@ -747,7 +757,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
}
if ((server.loading) ||
(server.lazy_expire_disabled) ||
(server.allow_access_expired) ||
(hfeFlags & HFE_LAZY_AVOID_FIELD_DEL) ||
(isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)))
return GETF_EXPIRED;
@@ -1042,6 +1052,8 @@ SetExRes hashTypeSetExpiryHT(HashTypeSetEx *exInfo, sds field, uint64_t expireAt
/* If expired, then delete the field and propagate the deletion.
* If replica, continue like the field is valid */
if (unlikely(checkAlreadyExpired(expireAt))) {
unsigned long length = dictSize(ht);
updateKeysizesHist(exInfo->db, getKeySlot(exInfo->key->ptr), OBJ_HASH, length, length-1);
/* replicas should not initiate deletion of fields */
propagateHashFieldDeletion(exInfo->db, exInfo->key->ptr, field, sdslen(field));
hashTypeDelete(exInfo->hashObj, field, 1);
@@ -1134,6 +1146,20 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
dictEntry *de = dbFind(c->db, key->ptr);
serverAssert(de != NULL);
lpt->key = dictGetKey(de);
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
listpackEx *lpt = o->ptr;
/* If the hash previously had HFEs but later no longer does, the key ref
* (lpt->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
* operation. These commands maintain the key ref only if HFEs are present.
* That is, we can only be sure that key ref is valid as long as it is not
* "trash". (TODO: dbFind() can be avoided. Instead need to extend the
* lookupKey*() to return dictEntry). */
if (lpt->meta.trash) {
dictEntry *de = dbFind(c->db, key->ptr);
serverAssert(de != NULL);
lpt->key = dictGetKey(de);
}
} else if (o->encoding == OBJ_ENCODING_HT) {
/* Take care dict has HFE metadata */
if (!isDictWithMetaHFE(ht)) {
@@ -1151,6 +1177,18 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
m->key = dictGetKey(de); /* reference key in keyspace */
m->hfe = ebCreate(); /* Allocate HFE DS */
m->expireMeta.trash = 1; /* mark as trash (as long it wasn't ebAdd()) */
} else {
dictExpireMetadata *m = (dictExpireMetadata *) dictMetadata(ht);
/* If the hash previously had HFEs but later no longer does, the key ref
* (m->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
* operation. These commands maintain the key ref only if HFEs are present.
* That is, we can only be sure that key ref is valid as long as it is not
* "trash". */
if (m->expireMeta.trash) {
dictEntry *de = dbFind(db, key->ptr);
serverAssert(de != NULL);
m->key = dictGetKey(de); /* reference key in keyspace */
}
}
}
@@ -1903,7 +1941,7 @@ static int hashTypeExpireIfNeeded(redisDb *db, robj *o) {
/* Follow expireIfNeeded() conditions of when not lazy-expire */
if ( (server.loading) ||
(server.lazy_expire_disabled) ||
(server.allow_access_expired) ||
(server.masterhost) || /* master-client or user-client, don't delete */
(isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)))
return 0;
@@ -1985,6 +2023,21 @@ uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o) {
return expireTime;
}
int hashTypeIsFieldsWithExpire(robj *o) {
if (o->encoding == OBJ_ENCODING_LISTPACK) {
return 0;
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
return EB_EXPIRE_TIME_INVALID != listpackExGetMinExpire(o);
} else { /* o->encoding == OBJ_ENCODING_HT */
dict *d = o->ptr;
/* If dict doesn't holds HFE metadata */
if (!isDictWithMetaHFE(d))
return 0;
dictExpireMetadata *meta = (dictExpireMetadata *) dictMetadata(d);
return ebGetTotalItems(meta->hfe, &hashFieldExpireBucketsType) != 0;
}
}
/* Add hash to global HFE DS and update key for notifications.
*
* key - must be the same key instance that is persisted in db->dict
@@ -2091,6 +2144,7 @@ ebuckets *hashTypeGetDictMetaHFE(dict *d) {
*----------------------------------------------------------------------------*/
void hsetnxCommand(client *c) {
unsigned long hlen;
int isHashDeleted;
robj *o;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
@@ -2111,6 +2165,8 @@ void hsetnxCommand(client *c) {
addReply(c, shared.cone);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
hlen = hashTypeLength(o, 0);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, hlen - 1, hlen);
server.dirty++;
}
@@ -2139,6 +2195,8 @@ void hsetCommand(client *c) {
addReply(c, shared.ok);
}
signalModifiedKey(c,c->db,c->argv[1]);
unsigned long l = hashTypeLength(o, 0);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l - created, l);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty += (c->argc - 2)/2;
}
@@ -2164,11 +2222,14 @@ void hincrbyCommand(client *c) {
} /* Else hashTypeGetValue() already stored it into &value */
} else if ((res == GETF_NOT_FOUND) || (res == GETF_EXPIRED)) {
value = 0;
unsigned long l = hashTypeLength(o, 0);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l, l + 1);
} else {
/* Field expired and in turn hash deleted. Create new one! */
o = createHashObject();
dbAdd(c->db,c->argv[1],o);
value = 0;
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, 0, 1);
}
oldvalue = value;
@@ -2213,11 +2274,14 @@ void hincrbyfloatCommand(client *c) {
}
} else if ((res == GETF_NOT_FOUND) || (res == GETF_EXPIRED)) {
value = 0;
unsigned long l = hashTypeLength(o, 0);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l, l + 1);
} else {
/* Field expired and in turn hash deleted. Create new one! */
o = createHashObject();
dbAdd(c->db,c->argv[1],o);
value = 0;
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, 0, 1);
}
value += incr;
@@ -2315,6 +2379,16 @@ void hdelCommand(client *c) {
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
unsigned long oldLen = hashTypeLength(o, 0);
/* Hash field expiration is optimized to avoid frequent update global HFE DS for
* each field deletion. Eventually active-expiration will run and update or remove
* the hash from global HFE DS gracefully. Nevertheless, statistic "subexpiry"
* might reflect wrong number of hashes with HFE to the user if it is the last
* field with expiration. The following logic checks if this is indeed the last
* field with expiration and removes it from global HFE DS. */
int isHFE = hashTypeIsFieldsWithExpire(o);
for (j = 2; j < c->argc; j++) {
if (hashTypeDelete(o,c->argv[j]->ptr,1)) {
deleted++;
@@ -2326,11 +2400,17 @@ void hdelCommand(client *c) {
}
}
if (deleted) {
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, oldLen, oldLen - deleted);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
if (keyremoved) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
} else {
if (isHFE && (hashTypeIsFieldsWithExpire(o) == 0)) /* is it last HFE */
ebRemove(&c->db->hexpires, &hashExpireBucketsType, o);
}
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
@@ -2401,7 +2481,11 @@ void genericHgetallCommand(client *c, int flags) {
/* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */
length = hashTypeLength(o, 1 /*subtractExpiredFields*/);
if ((length = hashTypeLength(o, 1 /*subtractExpiredFields*/)) == 0) {
addReply(c, emptyResp);
return;
}
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
addReplyMapLen(c, length);
} else {
@@ -2410,11 +2494,7 @@ void genericHgetallCommand(client *c, int flags) {
hi = hashTypeInitIterator(o);
/* Skip expired fields if the hash has an expire time set at global HFE DS. We could
* set it to constant 1, but then it will make another lookup for each field expiration */
int skipExpiredFields = (EB_EXPIRE_TIME_INVALID == hashTypeGetMinExpire(o, 0)) ? 0 : 1;
while (hashTypeNext(hi, skipExpiredFields) != C_ERR) {
while (hashTypeNext(hi, 1 /*skipExpiredFields*/) != C_ERR) {
if (flags & OBJ_HASH_KEY) {
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
count++;
@@ -2890,6 +2970,11 @@ static ExpireAction onFieldExpire(eItem item, void *ctx) {
dict *d = expCtx->hashObj->ptr;
dictExpireMetadata *dictExpireMeta = (dictExpireMetadata *) dictMetadata(d);
propagateHashFieldDeletion(expCtx->db, dictExpireMeta->key, hf, hfieldlen(hf));
/* update keysizes */
unsigned long l = hashTypeLength(expCtx->hashObj, 0);
updateKeysizesHist(expCtx->db, getKeySlot(dictExpireMeta->key), OBJ_HASH, l, l - 1);
serverAssert(hashTypeDelete(expCtx->hashObj, hf, 0) == 1);
server.stat_expired_subkeys++;
return ACT_REMOVE_EXP_ITEM;
+31 -12
View File
@@ -7,6 +7,7 @@
*/
#include "server.h"
#include "util.h"
/*-----------------------------------------------------------------------------
* List API
@@ -383,12 +384,12 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) {
}
/* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) {
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len) {
serverAssertWithInfo(NULL,o,sdsEncodedObject(o));
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
return quicklistCompare(&entry->entry,o->ptr,sdslen(o->ptr));
return quicklistCompare(&entry->entry,o->ptr,object_len);
} else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) {
return lpCompare(entry->lpe,o->ptr,sdslen(o->ptr));
return lpCompare(entry->lpe,o->ptr,object_len);
} else {
serverPanic("Unknown list encoding");
}
@@ -462,6 +463,7 @@ void listTypeDelRange(robj *subject, long start, long count) {
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
* 'xx': push if key exists. */
void pushGenericCommand(client *c, int where, int xx) {
unsigned long llen;
int j;
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
@@ -482,11 +484,13 @@ void pushGenericCommand(client *c, int where, int xx) {
server.dirty++;
}
addReplyLongLong(c, listTypeLength(lobj));
llen = listTypeLength(lobj);
addReplyLongLong(c, llen);
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, llen - (c->argc - 2), llen);
}
/* LPUSH <key> <element> [<element> ...] */
@@ -538,8 +542,9 @@ void linsertCommand(client *c) {
/* Seek pivot from head to tail */
iter = listTypeInitIterator(subject,0,LIST_TAIL);
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(iter,&entry)) {
if (listTypeEqual(&entry,c->argv[3])) {
if (listTypeEqual(&entry,c->argv[3],object_len)) {
listTypeInsert(&entry,c->argv[4],where);
inserted = 1;
break;
@@ -552,6 +557,8 @@ void linsertCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
server.dirty++;
unsigned long ll = listTypeLength(subject);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, ll-1, ll);
} else {
/* Notify client of a failed insert */
addReplyLongLong(c,-1);
@@ -735,9 +742,11 @@ void addListRangeReply(client *c, robj *o, long start, long end, int reverse) {
* if the key got deleted by this function. */
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted) {
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
unsigned long llen = listTypeLength(o);
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
if (listTypeLength(o) == 0) {
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_LIST, llen + count, llen);
if (llen == 0) {
if (deleted) *deleted = 1;
dbDelete(c->db, key);
@@ -869,7 +878,7 @@ void lrangeCommand(client *c) {
/* LTRIM <key> <start> <stop> */
void ltrimCommand(client *c) {
robj *o;
long start, end, llen, ltrim, rtrim;
long start, end, llen, ltrim, rtrim, llenNew;;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
@@ -907,12 +916,13 @@ void ltrimCommand(client *c) {
}
notifyKeyspaceEvent(NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
if (listTypeLength(o) == 0) {
if ((llenNew = listTypeLength(o)) == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
} else {
listTypeTryConversion(o,LIST_CONV_SHRINKING,NULL,NULL);
}
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, llen, llenNew);
signalModifiedKey(c,c->db,c->argv[1]);
server.dirty += (ltrim + rtrim);
addReply(c,shared.ok);
@@ -999,8 +1009,9 @@ void lposCommand(client *c) {
listTypeEntry entry;
long llen = listTypeLength(o);
long index = 0, matches = 0, matchindex = -1, arraylen = 0;
const size_t ele_len = sdslen(ele->ptr);
while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) {
if (listTypeEqual(&entry,ele)) {
if (listTypeEqual(&entry,ele,ele_len)) {
matches++;
matchindex = (direction == LIST_TAIL) ? index : llen - index - 1;
if (matches >= rank) {
@@ -1052,8 +1063,9 @@ void lremCommand(client *c) {
}
listTypeEntry entry;
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) {
if (listTypeEqual(&entry,obj,object_len)) {
listTypeDelete(li, &entry);
server.dirty++;
removed++;
@@ -1063,8 +1075,11 @@ void lremCommand(client *c) {
listTypeReleaseIterator(li);
if (removed) {
long ll = listTypeLength(subject);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, ll + removed, ll);
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
if (listTypeLength(subject) == 0) {
if (ll == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
} else {
@@ -1086,6 +1101,10 @@ void lmoveHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value,
listTypeTryConversionAppend(dstobj,&value,0,0,NULL,NULL);
listTypePush(dstobj,value,where);
signalModifiedKey(c,c->db,dstkey);
long ll = listTypeLength(dstobj);
updateKeysizesHist(c->db, getKeySlot(dstkey->ptr), OBJ_LIST, ll - 1, ll);
notifyKeyspaceEvent(NOTIFY_LIST,
where == LIST_HEAD ? "lpush" : "rpush",
dstkey,
+80 -5
View File
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
@@ -598,6 +603,8 @@ void saddCommand(client *c) {
if (setTypeAdd(set,c->argv[j]->ptr)) added++;
}
if (added) {
unsigned long size = setTypeSize(set);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size - added, size);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
@@ -612,6 +619,8 @@ void sremCommand(client *c) {
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,OBJ_SET)) return;
unsigned long oldSize = setTypeSize(set);
for (j = 2; j < c->argc; j++) {
if (setTypeRemove(set,c->argv[j]->ptr)) {
deleted++;
@@ -623,6 +632,8 @@ void sremCommand(client *c) {
}
}
if (deleted) {
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, oldSize, oldSize - deleted);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
if (keyremoved)
@@ -664,8 +675,12 @@ void smoveCommand(client *c) {
}
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
/* Update keysizes histogram */
unsigned long srcLen = setTypeSize(srcset);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, srcLen + 1, srcLen);
/* Remove the src set from the database when empty */
if (setTypeSize(srcset) == 0) {
if (srcLen == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
@@ -681,6 +696,8 @@ void smoveCommand(client *c) {
/* An extra key has changed when ele was successfully added to dstset */
if (setTypeAdd(dstset,ele->ptr)) {
unsigned long dstLen = setTypeSize(dstset);
updateKeysizesHist(c->db, getKeySlot(c->argv[2]->ptr), OBJ_SET, dstLen - 1, dstLen);
server.dirty++;
signalModifiedKey(c,c->db,c->argv[2]);
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
@@ -738,7 +755,7 @@ void scardCommand(client *c) {
void spopWithCountCommand(client *c) {
long l;
unsigned long count, size;
unsigned long count, size, toRemove;
robj *set;
/* Get the count argument */
@@ -758,10 +775,12 @@ void spopWithCountCommand(client *c) {
}
size = setTypeSize(set);
toRemove = (count >= size) ? size : count;
/* Generate an SPOP keyspace notification */
notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id);
server.dirty += (count >= size) ? size : count;
server.dirty += toRemove;
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size, size - toRemove);
/* CASE 1:
* The number of requested elements is greater than or equal to
@@ -944,6 +963,7 @@ void spopWithCountCommand(client *c) {
}
void spopCommand(client *c) {
unsigned long size;
robj *set, *ele;
if (c->argc == 3) {
@@ -959,6 +979,9 @@ void spopCommand(client *c) {
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return;
size = setTypeSize(set);
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size, size-1);
/* Pop a random element from the set */
ele = setTypePopRandom(set);
@@ -1244,7 +1267,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
return 0;
}
/* SINTER / SMEMBERS / SINTERSTORE / SINTERCARD
/* SINTER / SINTERSTORE / SINTERCARD
*
* 'cardinality_only' work for SINTERCARD, only return the cardinality
* with minimum processing and memory overheads.
@@ -1420,6 +1443,36 @@ void sinterCommand(client *c) {
sinterGenericCommand(c, c->argv+1, c->argc-1, NULL, 0, 0);
}
/* SMEMBERS key */
void smembersCommand(client *c) {
setTypeIterator *si;
char *str;
size_t len;
int64_t intobj;
robj *setobj = lookupKeyRead(c->db, c->argv[1]);
if (checkType(c,setobj,OBJ_SET)) return;
if (!setobj) {
addReply(c, shared.emptyset[c->resp]);
return;
}
/* Prepare the response. */
unsigned long length = setTypeSize(setobj);
addReplySetLen(c,length);
/* Iterate through the elements of the set. */
si = setTypeInitIterator(setobj);
while (setTypeNext(si, &str, &len, &intobj) != -1) {
if (str != NULL)
addReplyBulkCBuffer(c, str, len);
else
addReplyBulkLongLong(c, intobj);
length--;
}
setTypeReleaseIterator(si);
serverAssert(length == 0); /* fail on corrupt data */
}
/* SINTERCARD numkeys key [key ...] [LIMIT limit] */
void sinterCardCommand(client *c) {
long j;
@@ -1462,6 +1515,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
robj *dstset = NULL;
int dstset_encoding = OBJ_ENCODING_INTSET;
char *str;
size_t len;
int64_t llval;
@@ -1480,6 +1534,23 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
zfree(sets);
return;
}
/* For a SET's encoding, according to the factory method setTypeCreate(), currently have 3 types:
* 1. OBJ_ENCODING_INTSET
* 2. OBJ_ENCODING_LISTPACK
* 3. OBJ_ENCODING_HT
* 'dstset_encoding' is used to determine which kind of encoding to use when initialize 'dstset'.
*
* If all sets are all OBJ_ENCODING_INTSET encoding or 'dstkey' is not null, keep 'dstset'
* OBJ_ENCODING_INTSET encoding when initialize. Otherwise it is not efficient to create the 'dstset'
* from intset and then convert to listpack or hashtable.
*
* If one of the set is OBJ_ENCODING_LISTPACK, let's set 'dstset' to hashtable default encoding,
* the hashtable is more efficient when find and compare than the listpack. The corresponding
* time complexity are O(1) vs O(n). */
if (!dstkey && dstset_encoding == OBJ_ENCODING_INTSET &&
(setobj->encoding == OBJ_ENCODING_LISTPACK || setobj->encoding == OBJ_ENCODING_HT)) {
dstset_encoding = OBJ_ENCODING_HT;
}
sets[j] = setobj;
if (j > 0 && sets[0] == sets[j]) {
sameset = 1;
@@ -1522,7 +1593,11 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
/* We need a temp set object to store our union/diff. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE/SDIFFSTORE operation) then
* this set object will be the resulting object to set into the target key*/
dstset = createIntsetObject();
if (dstset_encoding == OBJ_ENCODING_INTSET) {
dstset = createIntsetObject();
} else {
dstset = createSetObject();
}
if (op == SET_OP_UNION) {
/* Union is trivial, just add every element of every set to the
+21 -11
View File
@@ -1405,11 +1405,6 @@ int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) {
return 0;
}
if (streamCompareID(&s->first_id,&s->max_deleted_entry_id) > 0) {
/* The latest tombstone is before the first entry. */
return 0;
}
if (start) {
start_id = *start;
} else {
@@ -1446,6 +1441,17 @@ void streamReplyWithCGLag(client *c, stream *s, streamCG *cg) {
/* The lag of a newly-initialized stream is 0. */
lag = 0;
valid = 1;
} else if (!s->length) { /* All entries deleted, now empty. */
lag = 0;
valid = 1;
} else if (streamCompareID(&cg->last_id,&s->first_id) < 0 &&
streamCompareID(&s->max_deleted_entry_id,&s->first_id) < 0)
{
/* When both the consumer group's last_id and the maximum tombstone are behind
* the stream's first entry, the consumer group's lag will always be equal to
* the number of remainin entries in the stream. */
lag = s->length;
valid = 1;
} else if (cg->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&cg->last_id,NULL)) {
/* No fragmentation ahead means that the group's logical reads counter
* is valid for performing the lag calculation. */
@@ -1502,6 +1508,10 @@ long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id) {
return s->entries_added;
}
/* There are fragmentations between the `id` and the stream's last-generated-id. */
if (!streamIDEqZero(id) && streamCompareID(id,&s->max_deleted_entry_id) < 0)
return SCG_INVALID_ENTRIES_READ;
int cmp_last = streamCompareID(id,&s->last_id);
if (cmp_last == 0) {
/* Return the exact counter of the last entry in the stream. */
@@ -1684,10 +1694,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Update the group last_id if needed. */
if (group && streamCompareID(&id,&group->last_id) > 0) {
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&id,NULL)) {
/* A valid counter and no future tombstones mean we can
* increment the read counter to keep tracking the group's
* progress. */
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&group->last_id,NULL)) {
/* A valid counter and no tombstones between the group's last-delivered-id
* and the stream's last-generated-id mean we can increment the read counter
* to keep tracking the group's progress. */
group->entries_read++;
} else if (s->entries_added) {
/* The group's counter may be invalid, so we try to obtain it. */
@@ -2190,9 +2200,9 @@ void xreadCommand(client *c) {
streams_arg = i+1;
streams_count = (c->argc-streams_arg);
if ((streams_count % 2) != 0) {
char symbol = xreadgroup ? '>' : '$';
const char *symbol = xreadgroup ? "ID or '>'" : "ID, '+', or '$'";
addReplyErrorFormat(c,"Unbalanced '%s' list of streams: "
"for each stream key an ID or '%c' must be "
"for each stream key an %s must be "
"specified.", c->cmd->fullname,symbol);
return;
}
+44 -44
View File
@@ -73,7 +73,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
if (getGenericCommand(c) == C_ERR) return;
}
found = (lookupKeyWrite(c->db,key) != NULL);
dictEntry *de = NULL;
found = (lookupKeyWriteWithDictEntry(c->db,key,&de) != NULL);
if ((flags & OBJ_SET_NX && found) ||
(flags & OBJ_SET_XX && !found))
@@ -88,12 +89,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0;
setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;
setKey(c,c->db,key,val,setkey_flags);
setKeyWithDictEntry(c,c->db,key,val,setkey_flags,de);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) {
setExpire(c,c->db,key,milliseconds);
setExpireWithDictEntry(c,c->db,key,milliseconds,de);
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is
* EX/PX/EXAT flag. */
if (!(flags & OBJ_PXAT)) {
@@ -419,9 +420,11 @@ void getsetCommand(client *c) {
}
void setrangeCommand(client *c) {
size_t oldLen = 0, newLen;
robj *o;
long offset;
sds value = c->argv[3]->ptr;
const size_t value_len = sdslen(value);
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
return;
@@ -431,51 +434,53 @@ void setrangeCommand(client *c) {
return;
}
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Return 0 when setting nothing on a non-existing string */
if (sdslen(value) == 0) {
if (value_len == 0) {
addReply(c,shared.czero);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+value_len));
dbAdd(c->db,c->argv[1],o);
} else {
size_t olen;
/* Key exists, check type */
if (checkType(c,o,OBJ_STRING))
return;
/* Return existing string length when setting nothing */
olen = stringObjectLen(o);
if (sdslen(value) == 0) {
addReplyLongLong(c,olen);
oldLen = stringObjectLen(o);
if (value_len == 0) {
addReplyLongLong(c, oldLen);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
/* Create a copy when the object is shared or encoded. */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
}
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
if (value_len > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+value_len);
memcpy((char*)o->ptr+offset,value,value_len);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,
"setrange",c->argv[1],c->db->id);
server.dirty++;
}
addReplyLongLong(c,sdslen(o->ptr));
newLen = sdslen(o->ptr);
updateKeysizesHist(c->db,getKeySlot(c->argv[1]->ptr),OBJ_STRING,oldLen,newLen);
addReplyLongLong(c,newLen);
}
void getrangeCommand(client *c) {
@@ -499,24 +504,15 @@ void getrangeCommand(client *c) {
strlen = sdslen(str);
}
/* Convert negative indexes */
if (start < 0 && end < 0 && start > end) {
if (start < 0) start += strlen;
if (end < 0) end += strlen;
if (strlen == 0 || start >= (long long)strlen || end < 0 || start > end) {
addReply(c,shared.emptybulk);
return;
}
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
if ((unsigned long long)end >= strlen) end = strlen-1;
/* Precondition: end >= 0 && end < strlen, so the only condition where
* nothing can be returned is: start > end. */
if (start > end || strlen == 0) {
addReply(c,shared.emptybulk);
} else {
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
}
if (end >= (long long)strlen) end = strlen-1;
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
}
void mgetCommand(client *c) {
@@ -580,8 +576,8 @@ void msetnxCommand(client *c) {
void incrDecrCommand(client *c, long long incr) {
long long value, oldvalue;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
@@ -602,7 +598,7 @@ void incrDecrCommand(client *c, long long incr) {
} else {
new = createStringObjectFromLongLongForValue(value);
if (o) {
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
} else {
dbAdd(c->db,c->argv[1],new);
}
@@ -610,7 +606,7 @@ void incrDecrCommand(client *c, long long incr) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReplyLongLong(c, value);
addReplyLongLongFromStr(c,new);
}
void incrCommand(client *c) {
@@ -644,7 +640,8 @@ void incrbyfloatCommand(client *c) {
long double incr, value;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
@@ -657,7 +654,7 @@ void incrbyfloatCommand(client *c) {
}
new = createStringObjectFromLongDouble(value,1);
if (o)
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c,c->db,c->argv[1]);
@@ -674,16 +671,17 @@ void incrbyfloatCommand(client *c) {
}
void appendCommand(client *c) {
size_t totlen;
size_t totlen, append_len;
robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Create the key */
c->argv[2] = tryObjectEncoding(c->argv[2]);
dbAdd(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
totlen = stringObjectLen(c->argv[2]);
append_len = totlen = stringObjectLen(c->argv[2]);
} else {
/* Key exists, check type */
if (checkType(c,o,OBJ_STRING))
@@ -691,17 +689,19 @@ void appendCommand(client *c) {
/* "append" is an argument, so always an sds */
append = c->argv[2];
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
append_len = sdslen(append->ptr);
if (checkStringLength(c,stringObjectLen(o),append_len) != C_OK)
return;
/* Append the value */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
o->ptr = sdscatlen(o->ptr,append->ptr,append_len);
totlen = sdslen(o->ptr);
}
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
server.dirty++;
updateKeysizesHist(c->db,getKeySlot(c->argv[1]->ptr),OBJ_STRING, totlen - append_len, totlen);
addReplyLongLong(c,totlen);
}
+38 -56
View File
@@ -1,31 +1,15 @@
/*
* Copyright (c) 2009-current, Redis Ltd.
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
/* t_zset.c -- zset data type implementation.
*
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
/*-----------------------------------------------------------------------------
@@ -55,7 +39,7 @@
* c) there is a back pointer, so it's a doubly linked list with the back
* pointers being only at "level 1". This allows to traverse the list
* from tail to head, useful for ZREVRANGE. */
#include "fast_float_strtod.h"
#include "server.h"
#include "intset.h" /* Compact integer set structure */
#include <math.h>
@@ -566,11 +550,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec->min = (long)min->ptr;
} else {
if (((char*)min->ptr)[0] == '(') {
spec->min = strtod((char*)min->ptr+1,&eptr);
spec->min = fast_float_strtod((char*)min->ptr+1,&eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
spec->minex = 1;
} else {
spec->min = strtod((char*)min->ptr,&eptr);
spec->min = fast_float_strtod((char*)min->ptr,&eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
}
}
@@ -578,11 +562,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec->max = (long)max->ptr;
} else {
if (((char*)max->ptr)[0] == '(') {
spec->max = strtod((char*)max->ptr+1,&eptr);
spec->max = fast_float_strtod((char*)max->ptr+1,&eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
spec->maxex = 1;
} else {
spec->max = strtod((char*)max->ptr,&eptr);
spec->max = fast_float_strtod((char*)max->ptr,&eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
}
}
@@ -789,7 +773,7 @@ double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
vlen = sizeof(buf) - 1;
memcpy(buf,vstr,vlen);
buf[vlen] = '\0';
return strtod(buf,NULL);
return fast_float_strtod(buf,NULL);
}
double zzlGetScore(unsigned char *sptr) {
@@ -1859,6 +1843,7 @@ void zaddGenericCommand(client *c, int flags) {
zsetTypeMaybeConvert(zobj, elements);
}
unsigned long llen = zsetLength(zobj);
for (j = 0; j < elements; j++) {
double newscore;
score = scores[j];
@@ -1876,6 +1861,7 @@ void zaddGenericCommand(client *c, int flags) {
score = newscore;
}
server.dirty += (added+updated);
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, llen, llen+added);
reply_to_client:
if (incr) { /* ZINCRBY or INCR option. */
@@ -1923,8 +1909,13 @@ void zremCommand(client *c) {
if (deleted) {
notifyKeyspaceEvent(NOTIFY_ZSET,"zrem",key,c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
if (keyremoved) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
/* No need updateKeysizesHist(). dbDelete() done it already. */
} else {
unsigned long len = zsetLength(zobj);
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, len + deleted, len);
}
signalModifiedKey(c,c->db,key);
server.dirty += deleted;
}
@@ -2039,8 +2030,13 @@ void zremrangeGenericCommand(client *c, zrange_type rangetype) {
if (deleted) {
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_ZSET,notify_type,key,c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
if (keyremoved) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
/* No need updateKeysizesHist(). dbDelete() done it already. */
} else {
unsigned long len = zsetLength(zobj);
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, len + deleted, len);
}
}
server.dirty += deleted;
addReplyLongLong(c,deleted);
@@ -2611,16 +2607,6 @@ static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen,
}
}
dictType setAccumulatorDictType = {
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
NULL, /* key destructor */
NULL, /* val destructor */
NULL /* allow to expand */
};
/* The zunionInterDiffGenericCommand() function is called in order to implement the
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
* ZINTERCARD.
@@ -2816,7 +2802,6 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
zuiClearIterator(&src[0]);
}
} else if (op == SET_OP_UNION) {
dict *accumulator = dictCreate(&setAccumulatorDictType);
dictIterator *di;
dictEntry *de, *existing;
double score;
@@ -2824,7 +2809,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if (setnum) {
/* Our union is at least as large as the largest set.
* Resize the dictionary ASAP to avoid useless rehashing. */
dictExpand(accumulator,zuiLength(&src[setnum-1]));
dictExpand(dstzset->dict,zuiLength(&src[setnum-1]));
}
/* Step 1: Create a dictionary of elements -> aggregated-scores
@@ -2839,7 +2824,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if (isnan(score)) score = 0;
/* Search for this element in the accumulating dictionary. */
de = dictAddRaw(accumulator,zuiSdsFromValue(&zval),&existing);
de = dictAddRaw(dstzset->dict,zuiSdsFromValue(&zval),&existing);
/* If we don't have it, we need to create a new entry. */
if (!existing) {
tmp = zuiNewSdsFromValue(&zval);
@@ -2849,7 +2834,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
totelelen += sdslen(tmp);
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
/* Update the element with its initial score. */
dictSetKey(accumulator, de, tmp);
dictSetKey(dstzset->dict, de, tmp);
dictSetDoubleVal(de,score);
} else {
/* Update the score with the score of the new instance
@@ -2866,21 +2851,15 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
/* Step 2: convert the dictionary into the final sorted set. */
di = dictGetIterator(accumulator);
/* We now are aware of the final size of the resulting sorted set,
* let's resize the dictionary embedded inside the sorted set to the
* right size, in order to save rehashing time. */
dictExpand(dstzset->dict,dictSize(accumulator));
di = dictGetIterator(dstzset->dict);
while((de = dictNext(di)) != NULL) {
sds ele = dictGetKey(de);
score = dictGetDoubleVal(de);
znode = zslInsert(dstzset->zsl,score,ele);
dictAdd(dstzset->dict,ele,&znode->score);
dictSetVal(dstzset->dict,de,&znode->score);
}
dictReleaseIterator(di);
dictRelease(accumulator);
} else if (op == SET_OP_DIFF) {
zdiff(src, setnum, dstzset, &maxelelen, &totelelen);
} else {
@@ -4064,6 +4043,9 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
dbDelete(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
/* No need updateKeysizesHist(). dbDelete() done it already. */
} else {
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, llen, llen - result_count);
}
signalModifiedKey(c,c->db,key);
+82 -32
View File
@@ -75,10 +75,6 @@ static int parseProtocolsConfig(const char *str) {
return protocols;
}
/* list of connections with pending data already read from the socket, but not
* served to the reader yet. */
static list *pending_list = NULL;
/**
* OpenSSL global initialization and locking handling callbacks.
* Note that this is only required for OpenSSL < 1.1.0.
@@ -144,8 +140,6 @@ static void tlsInit(void) {
if (!RAND_poll()) {
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
}
pending_list = listCreate();
}
static void tlsCleanup(void) {
@@ -435,20 +429,21 @@ typedef struct tls_connection {
listNode *pending_list_node;
} tls_connection;
static connection *createTLSConnection(int client_side) {
static connection *createTLSConnection(struct aeEventLoop *el, int client_side) {
SSL_CTX *ctx = redis_tls_ctx;
if (client_side && redis_tls_client_ctx)
ctx = redis_tls_client_ctx;
tls_connection *conn = zcalloc(sizeof(tls_connection));
conn->c.type = &CT_TLS;
conn->c.fd = -1;
conn->c.el = el;
conn->c.iovcnt = IOV_MAX;
conn->ssl = SSL_new(ctx);
return (connection *) conn;
}
static connection *connCreateTLS(void) {
return createTLSConnection(1);
static connection *connCreateTLS(struct aeEventLoop *el) {
return createTLSConnection(el, 1);
}
/* Fetch the latest OpenSSL error and store it in the connection */
@@ -468,10 +463,11 @@ static void updateTLSError(tls_connection *conn) {
* Callers should use connGetState() and verify the created connection
* is not in an error state.
*/
static connection *connCreateAcceptedTLS(int fd, void *priv) {
static connection *connCreateAcceptedTLS(struct aeEventLoop *el, int fd, void *priv) {
int require_auth = *(int *)priv;
tls_connection *conn = (tls_connection *) createTLSConnection(0);
tls_connection *conn = (tls_connection *) createTLSConnection(el, 0);
conn->c.fd = fd;
conn->c.el = el;
conn->c.state = CONN_STATE_ACCEPTING;
if (!conn->ssl) {
@@ -575,17 +571,17 @@ static int updateStateAfterSSLIO(tls_connection *conn, int ret_value, int update
}
static void registerSSLEvent(tls_connection *conn, WantIOType want) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
switch (want) {
case WANT_READ:
if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
if (!(mask & AE_READABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE,
tlsEventHandler, conn);
break;
case WANT_WRITE:
if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE,
tlsEventHandler, conn);
break;
default:
@@ -595,19 +591,42 @@ static void registerSSLEvent(tls_connection *conn, WantIOType want) {
}
static void updateSSLEvent(tls_connection *conn) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
serverAssert(conn->c.el);
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
if (need_read && !(mask & AE_READABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
if (!need_read && (mask & AE_READABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
if (need_write && !(mask & AE_WRITABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
if (!need_write && (mask & AE_WRITABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
}
/* Add a connection to the list of connections with pending data that has
* already been read from the socket but has not yet been served to the reader. */
static void tlsPendingAdd(tls_connection *conn) {
if (!conn->c.el->privdata[1])
conn->c.el->privdata[1] = listCreate();
list *pending_list = conn->c.el->privdata[1];
if (!conn->pending_list_node) {
listAddNodeTail(pending_list, conn);
conn->pending_list_node = listLast(pending_list);
}
}
/* Removes a connection from the list of connections with pending data. */
static void tlsPendingRemove(tls_connection *conn) {
if (conn->pending_list_node) {
list *pending_list = conn->c.el->privdata[1];
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
}
static void tlsHandleEvent(tls_connection *conn, int mask) {
@@ -718,13 +737,9 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
* to a list of pending connection that should be handled anyway. */
if ((mask & AE_READABLE)) {
if (SSL_pending(conn->ssl) > 0) {
if (!conn->pending_list_node) {
listAddNodeTail(pending_list, conn);
conn->pending_list_node = listLast(pending_list);
}
tlsPendingAdd(conn);
} else if (conn->pending_list_node) {
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
tlsPendingRemove(conn);
}
}
@@ -734,7 +749,8 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
break;
}
updateSSLEvent(conn);
/* The event loop may have been unbound during the event processing above. */
if (conn->c.el) updateSSLEvent(conn);
}
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
@@ -748,7 +764,6 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
int cport, cfd;
int max = server.max_new_tls_conns_per_cycle;
char cip[NET_IP_STR_LEN];
UNUSED(el);
UNUSED(mask);
UNUSED(privdata);
@@ -761,7 +776,7 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(connCreateAcceptedTLS(cfd, &server.tls_auth_clients),0,cip);
acceptCommonHandler(connCreateAcceptedTLS(el,cfd,&server.tls_auth_clients), 0, cip);
}
}
@@ -806,6 +821,7 @@ static void connTLSClose(connection *conn_) {
}
if (conn->pending_list_node) {
list *pending_list = conn->c.el->privdata[1];
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
@@ -863,6 +879,33 @@ static int connTLSConnect(connection *conn_, const char *addr, int port, const c
return C_OK;
}
static void connTLSUnbindEventLoop(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
/* We need to remove all events from the old event loop. The subsequent
* updateSSLEvent() will add the appropriate events to the new event loop. */
if (conn->c.el) {
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
/* Check if there are pending events and handle accordingly. */
int has_pending = conn->pending_list_node != NULL;
if (has_pending) tlsPendingRemove(conn);
}
}
static int connTLSRebindEventLoop(connection *conn_, aeEventLoop *el) {
tls_connection *conn = (tls_connection *) conn_;
serverAssert(!conn->c.el && !conn->c.read_handler &&
!conn->c.write_handler && !conn->pending_list_node);
conn->c.el = el;
if (el && SSL_pending(conn->ssl)) tlsPendingAdd(conn);
/* Add the appropriate events to the new event loop. */
updateSSLEvent((tls_connection *) conn);
return C_OK;
}
static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
@@ -1044,16 +1087,19 @@ static const char *connTLSGetType(connection *conn_) {
return CONN_TYPE_TLS;
}
static int tlsHasPendingData(void) {
static int tlsHasPendingData(struct aeEventLoop *el) {
list *pending_list = el->privdata[1];
if (!pending_list)
return 0;
return listLength(pending_list) > 0;
}
static int tlsProcessPendingData(void) {
static int tlsProcessPendingData(struct aeEventLoop *el) {
listIter li;
listNode *ln;
list *pending_list = el->privdata[1];
if (!pending_list) return 0;
int processed = listLength(pending_list);
listRewind(pending_list,&li);
while((ln = listNext(&li))) {
@@ -1114,6 +1160,10 @@ static ConnectionType CT_TLS = {
.blocking_connect = connTLSBlockingConnect,
.accept = connTLSAccept,
/* event loop */
.unbind_event_loop = connTLSUnbindEventLoop,
.rebind_event_loop = connTLSRebindEventLoop,
/* IO */
.read = connTLSRead,
.write = connTLSWrite,
+18 -1
View File
@@ -253,6 +253,7 @@ void trackingRememberKeys(client *tracking, client *executing) {
* - Following a flush command, to send a single RESP NULL to indicate
* that all keys are now invalid. */
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
int paused = 0;
uint64_t old_flags = c->flags;
c->flags |= CLIENT_PUSHING;
@@ -275,6 +276,11 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
c = redir;
using_redirection = 1;
/* Start to touch another client data. */
if (c->running_tid != IOTHREAD_MAIN_THREAD_ID) {
pauseIOThread(c->running_tid);
paused = 1;
}
old_flags = c->flags;
c->flags |= CLIENT_PUSHING;
}
@@ -296,7 +302,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
* it since RESP2 does not support push messages in the same
* connection. */
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
return;
goto done;
}
/* Send the "value" part, which is the array of keys. */
@@ -308,6 +314,17 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
}
updateClientMemUsageAndBucket(c);
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
done:
if (paused) {
if (clientHasPendingReplies(c)) {
serverAssert(!(c->flags & CLIENT_PENDING_WRITE));
/* Actually we install write handler of client which is in IO thread
* event loop, it is safe since the io thread is paused */
connSetWriteHandler(c->conn, sendReplyToClient);
}
resumeIOThread(c->running_tid);
}
}
/* This function is called when a key is modified in Redis and in the case
+13 -4
View File
@@ -74,18 +74,19 @@ static int connUnixListen(connListener *listener) {
return C_OK;
}
static connection *connCreateUnix(void) {
static connection *connCreateUnix(struct aeEventLoop *el) {
connection *conn = zcalloc(sizeof(connection));
conn->type = &CT_Unix;
conn->fd = -1;
conn->iovcnt = IOV_MAX;
conn->el = el;
return conn;
}
static connection *connCreateAcceptedUnix(int fd, void *priv) {
static connection *connCreateAcceptedUnix(struct aeEventLoop *el, int fd, void *priv) {
UNUSED(priv);
connection *conn = connCreateUnix();
connection *conn = connCreateUnix(el);
conn->fd = fd;
conn->state = CONN_STATE_ACCEPTING;
return conn;
@@ -107,7 +108,7 @@ static void connUnixAcceptHandler(aeEventLoop *el, int fd, void *privdata, int m
return;
}
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
acceptCommonHandler(connCreateAcceptedUnix(cfd, NULL),CLIENT_UNIX_SOCKET,NULL);
acceptCommonHandler(connCreateAcceptedUnix(el, cfd, NULL),CLIENT_UNIX_SOCKET,NULL);
}
}
@@ -123,6 +124,10 @@ static int connUnixAccept(connection *conn, ConnectionCallbackFunc accept_handle
return connectionTypeTcp()->accept(conn, accept_handler);
}
static int connUnixRebindEventLoop(connection *conn, aeEventLoop *el) {
return connectionTypeTcp()->rebind_event_loop(conn, el);
}
static int connUnixWrite(connection *conn, const void *data, size_t data_len) {
return connectionTypeTcp()->write(conn, data, data_len);
}
@@ -186,6 +191,10 @@ static ConnectionType CT_Unix = {
.blocking_connect = NULL,
.accept = connUnixAccept,
/* event loop */
.unbind_event_loop = NULL,
.rebind_event_loop = connUnixRebindEventLoop,
/* IO */
.write = connUnixWrite,
.writev = connUnixWritev,
+15 -4
View File
@@ -30,6 +30,7 @@
#include "fmacros.h"
#include "fpconv_dtoa.h"
#include "fast_float_strtod.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -53,10 +54,20 @@
#define UNUSED(x) ((void)(x))
/* Selectively define static_assert. Attempt to avoid include server.h in this file. */
#ifndef static_assert
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
#endif
static_assert(UINTPTR_MAX == 0xffffffffffffffff || UINTPTR_MAX == 0xffffffff, "Unsupported pointer size");
/* Glob-style pattern matching. */
static int stringmatchlen_impl(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase, int *skipLongerMatches)
const char *string, int stringLen, int nocase, int *skipLongerMatches, int nesting)
{
/* Protection against abusive patterns. */
if (nesting > 1000) return 0;
while(patternLen && stringLen) {
switch(pattern[0]) {
case '*':
@@ -68,7 +79,7 @@ static int stringmatchlen_impl(const char *pattern, int patternLen,
return 1; /* match */
while(stringLen) {
if (stringmatchlen_impl(pattern+1, patternLen-1,
string, stringLen, nocase, skipLongerMatches))
string, stringLen, nocase, skipLongerMatches, nesting+1))
return 1; /* match */
if (*skipLongerMatches)
return 0; /* no match */
@@ -190,7 +201,7 @@ static int stringmatchlen_impl(const char *pattern, int patternLen,
int stringmatchlen(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase) {
int skipLongerMatches = 0;
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches);
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches,0);
}
int stringmatch(const char *pattern, const char *string, int nocase) {
@@ -612,7 +623,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
int string2d(const char *s, size_t slen, double *dp) {
errno = 0;
char *eptr;
*dp = strtod(s, &eptr);
*dp = fast_float_strtod(s, &eptr);
if (slen == 0 ||
isspace(((const char*)s)[0]) ||
(size_t)(eptr-(char*)s) != slen ||
+13
View File
@@ -79,6 +79,19 @@ int snprintf_async_signal_safe(char *to, size_t n, const char *fmt, ...);
size_t redis_strlcpy(char *dst, const char *src, size_t dsize);
size_t redis_strlcat(char *dst, const char *src, size_t dsize);
/* to keep it opt without conditions Works only for: 0 < x < 2^63 */
static inline int log2ceil(size_t x) {
#if UINTPTR_MAX == 0xffffffffffffffff
return 63 - __builtin_clzll(x);
#else
return 31 - __builtin_clz(x);
#endif
}
#ifndef static_assert
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
#endif
#ifdef REDIS_TEST
int utilTest(int argc, char **argv, int flags);
#endif
+40 -8
View File
@@ -31,6 +31,7 @@ void zlibc_free(void *ptr) {
#include <string.h>
#include "zmalloc.h"
#include "atomicvar.h"
#include "redisassert.h"
#define UNUSED(x) ((void)(x))
@@ -67,10 +68,34 @@ void zlibc_free(void *ptr) {
#define dallocx(ptr,flags) je_dallocx(ptr,flags)
#endif
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))
#define MAX_THREADS 16 /* Keep it a power of 2 so we can use '&' instead of '%'. */
#define THREAD_MASK (MAX_THREADS - 1)
static redisAtomic size_t used_memory = 0;
typedef struct used_memory_entry {
redisAtomic long long used_memory;
char padding[CACHE_LINE_SIZE - sizeof(long long)];
} used_memory_entry;
static __attribute__((aligned(CACHE_LINE_SIZE))) used_memory_entry used_memory[MAX_THREADS];
static redisAtomic size_t num_active_threads = 0;
static __thread long my_thread_index = -1;
static inline void init_my_thread_index(void) {
if (unlikely(my_thread_index == -1)) {
atomicGetIncr(num_active_threads, my_thread_index, 1);
my_thread_index &= THREAD_MASK;
}
}
static void update_zmalloc_stat_alloc(long long num) {
init_my_thread_index();
atomicIncr(used_memory[my_thread_index].used_memory, num);
}
static void update_zmalloc_stat_free(long long num) {
init_my_thread_index();
atomicDecr(used_memory[my_thread_index].used_memory, num);
}
static void zmalloc_default_oom(size_t size) {
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
@@ -436,9 +461,18 @@ char *zstrdup(const char *s) {
}
size_t zmalloc_used_memory(void) {
size_t um;
atomicGet(used_memory,um);
return um;
size_t local_num_active_threads;
long long total_mem = 0;
atomicGet(num_active_threads,local_num_active_threads);
if (local_num_active_threads > MAX_THREADS) {
local_num_active_threads = MAX_THREADS;
}
for (size_t i = 0; i < local_num_active_threads; ++i) {
long long thread_used_mem;
atomicGet(used_memory[i].used_memory, thread_used_mem);
total_mem += thread_used_mem;
}
return total_mem;
}
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
@@ -655,8 +689,6 @@ size_t zmalloc_get_rss(void) {
#if defined(USE_JEMALLOC)
#include "redisassert.h"
/* Compute the total memory wasted in fragmentation of inside small arena bins.
* Done by summing the memory in unused regs in all slabs of all small bins.
*
+17 -1
View File
@@ -32,6 +32,13 @@ proc get_one_of_my_replica {id} {
}
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
set replica_id_num [get_instance_id_by_port redis $replica_port]
# To avoid -LOADING reply, wait until replica syncs with master.
wait_for_condition 1000 50 {
[RI $replica_id_num master_link_status] eq {up}
} else {
fail "Replica did not sync in time."
}
return $replica_id_num
}
@@ -70,6 +77,7 @@ proc test_slave_load_expired_keys {aof} {
# wait for replica to be in sync with master
wait_for_condition 500 10 {
[RI $replica_id master_link_status] eq {up} &&
[R $replica_id dbsize] eq [R $master_id dbsize]
} else {
fail "replica didn't sync"
@@ -104,8 +112,15 @@ proc test_slave_load_expired_keys {aof} {
# start the replica again (loading an RDB or AOF file)
restart_instance redis $replica_id
# Replica may start a full sync after restart, trying in a loop to avoid
# -LOADING reply in that case.
wait_for_condition 1000 50 {
[catch {set replica_dbsize_3 [R $replica_id dbsize]} e] == 0
} else {
fail "Replica is not up."
}
# make sure the keys are still there
set replica_dbsize_3 [R $replica_id dbsize]
assert {$replica_dbsize_3 > $replica_dbsize_0}
# restore settings
@@ -113,6 +128,7 @@ proc test_slave_load_expired_keys {aof} {
# wait for the master to expire all keys and replica to get the DELs
wait_for_condition 500 10 {
[RI $replica_id master_link_status] eq {up} &&
[R $replica_id dbsize] eq $master_dbsize_0
} else {
fail "keys didn't expire"
+6 -1
View File
@@ -124,6 +124,10 @@ test "Verify health as fail for killed node" {
}
}
test "Verify that other nodes can correctly output the new master's slots" {
assert_not_equal {} [dict get [get_node_info_from_shard [R 4 CLUSTER MYID] 8 "shard"] slots]
}
set primary_id 4
set replica_id 0
@@ -133,7 +137,8 @@ test "Restarting primary node" {
test "Instance #0 gets converted into a replica" {
wait_for_condition 1000 50 {
[RI $replica_id role] eq {slave}
[RI $replica_id role] eq {slave} &&
[RI $replica_id master_link_status] eq {up}
} else {
fail "Old primary was not converted into replica"
}
@@ -32,6 +32,17 @@ test "Cluster nodes hard reset" {
} else {
set node_timeout 3000
}
# Wait until slave is synced. Otherwise, it may reply -LOADING
# for any commands below.
if {[RI $id role] eq {slave}} {
wait_for_condition 50 1000 {
[RI $id master_link_status] eq {up}
} else {
fail "Slave were not able to sync."
}
}
catch {R $id flushall} ; # May fail for readonly slaves.
R $id MULTI
R $id cluster reset hard
+46
View File
@@ -655,4 +655,50 @@ tags {"aof external:skip"} {
}
}
}
start_server {overrides {loading-process-events-interval-bytes 1024}} {
test "Allow changing appendonly config while loading from AOF on startup" {
# Set AOF enabled, populate db and restart.
r config set appendonly yes
r config set key-load-delay 100
r config rewrite
populate 10000
restart_server 0 false false
# Disable AOF while loading from the disk.
assert_equal 1 [s loading]
r config set appendonly no
assert_equal 1 [s loading]
# Speed up loading, verify AOF disabled.
r config set key-load-delay 0
wait_done_loading r
assert_equal {10000} [r dbsize]
assert_equal 0 [s aof_enabled]
}
test "Allow changing appendonly config while loading from RDB on startup" {
# Set AOF disabled, populate db and restart.
r flushall
r config set appendonly no
r config set key-load-delay 100
r config rewrite
populate 10000
r save
restart_server 0 false false
# Enable AOF while loading from the disk.
assert_equal 1 [s loading]
r config set appendonly yes
assert_equal 1 [s loading]
# Speed up loading, verify AOF enabled, do a quick sanity.
r config set key-load-delay 0
wait_done_loading r
assert_equal {10000} [r dbsize]
assert_equal 1 [s aof_enabled]
r set t 1
assert_equal {1} [r get t]
}
}
}
+13 -2
View File
@@ -122,6 +122,7 @@ foreach sanitize_dump {no yes} {
set restore_failed false
set report_and_restart false
set sent {}
set expired_subkeys [s expired_subkeys]
# RESTORE can fail, but hopefully not terminate
if { [catch { r restore "_$k" 0 $dump REPLACE } err] } {
set restore_failed true
@@ -145,7 +146,17 @@ foreach sanitize_dump {no yes} {
# if RESTORE didn't fail or terminate, run some random traffic on the new key
incr stat_successful_restore
if { [ catch {
set sent [generate_fuzzy_traffic_on_key "_$k" 1] ;# traffic for 1 second
set type [r type "_$k"]
if {$type eq {none}} {
# The key has been removed due to expiration.
# Ensure the server didn't terminate during expiration and verify
# expire stats to confirm the key was removed due to expiration.
r ping
assert_morethan [s expired_subkeys] $expired_subkeys
} else {
set sent [generate_fuzzy_traffic_on_key "_$k" $type 1] ;# traffic for 1 second
}
incr stat_traffic_commands_sent [llength $sent]
r del "_$k" ;# in case the server terminated, here's where we'll detect it.
if {$dbsize != [r dbsize]} {
@@ -182,7 +193,7 @@ foreach sanitize_dump {no yes} {
dump_server_log $srv
}
puts "Server crashed (by signal: $by_signal), with payload: $printable_dump"
puts "Server crashed (by signal: $by_signal, err: $err), with payload: $printable_dump"
set print_commands true
}
}
+46
View File
@@ -885,5 +885,51 @@ test {corrupt payload: fuzzer findings - set with duplicate elements causes sdif
}
} {} {logreqres:skip} ;# This test violates {"uniqueItems": true}
test {corrupt payload: fuzzer findings - set with invalid length causes smembers to hang} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
# In the past, it generated a broken protocol and left the client hung in smembers
r config set sanitize-dump-payload no
assert_equal {OK} [r restore _set 0 "\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd" replace]
assert_encoding listpack _set
catch { r SMEMBERS _set } err
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
}
}
test {corrupt payload: fuzzer findings - set with invalid length causes sscan to hang} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
# In the past, it generated a broken protocol and left the client hung in smembers
r config set sanitize-dump-payload no
assert_equal {OK} [r restore _set 0 "\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd" replace]
assert_encoding listpack _set
catch { r SSCAN _set 0 } err
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
}
}
test {corrupt payload: zset listpack encoded with invalid length causes zscan to hang} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no
assert_equal {OK} [r restore _zset 0 "\x11\x16\x16\x00\x00\x00\x1a\x00\x81\x61\x02\x01\x01\x81\x62\x02\x02\x01\x81\x63\x02\x03\x01\xff\x0c\x00\x81\xa7\xcd\x31\x22\x6c\xef\xf7" replace]
assert_encoding listpack _zset
catch { r ZSCAN _zset 0 } err
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
}
}
test {corrupt payload: hash listpack encoded with invalid length causes hscan to hang} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no
assert_equal {OK} [r restore _hash 0 "\x10\x17\x17\x00\x00\x00\x0e\x00\x82\x66\x31\x03\x82\x76\x31\x03\x82\x66\x32\x03\x82\x76\x32\x03\xff\x0c\x00\xf1\xc5\x36\x92\x29\x6a\x8c\xc5" replace]
assert_encoding listpack _hash
catch { r HSCAN _hash 0 } err
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
}
}
} ;# tags
+34 -2
View File
@@ -6,6 +6,9 @@ if {$::singledb} {
set ::dbnum 9
}
file delete ./.rediscli_history_test
set ::env(REDISCLI_HISTFILE) ".rediscli_history_test"
start_server {tags {"cli"}} {
proc open_cli {{opts ""} {infile ""}} {
if { $opts == "" } {
@@ -68,10 +71,8 @@ start_server {tags {"cli"}} {
set _ [format_output [read_cli $fd]]
}
file delete ./.rediscli_history_test
proc test_interactive_cli_with_prompt {name code} {
set ::env(FAKETTY_WITH_PROMPT) 1
set ::env(REDISCLI_HISTFILE) ".rediscli_history_test"
test_interactive_cli $name $code
unset ::env(FAKETTY_WITH_PROMPT)
}
@@ -808,4 +809,35 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
assert_equal 3 [exec {*}$cmdline ZCARD new_zset]
assert_equal "a\n1\nb\n2\nc\n3" [exec {*}$cmdline ZRANGE new_zset 0 -1 WITHSCORES]
}
test "Send eval command by using --eval option" {
set tmpfile [write_tmpfile {return ARGV[1]}]
set cmdline [rediscli [srv host] [srv port]]
assert_equal "foo" [exec {*}$cmdline --eval $tmpfile , foo]
}
}
start_server {tags {"cli external:skip"}} {
test_interactive_cli_with_prompt "db_num showed in redis-cli after reconnected" {
run_command $fd "select 0\x0D"
run_command $fd "set a zoo-0\x0D"
run_command $fd "select 6\x0D"
run_command $fd "set a zoo-6\x0D"
r save
# kill server and restart
exec kill [s process_id]
wait_for_log_messages 0 {"*Redis is now ready to exit*"} 0 1000 10
catch {[run_command $fd "ping\x0D"]} err
restart_server 0 true false 0
# redis-cli should show '[6]' after reconnected and return 'zoo-6'
write_cli $fd "GET a\x0D"
after 100
set result [format_output [read_cli $fd]]
set regex {not connected> GET a.*"zoo-6".*127\.0\.0\.1:[0-9]*\[6\]>}
assert_equal 1 [regexp $regex $result]
}
}
file delete ./.rediscli_history_test
+140 -1
View File
@@ -652,7 +652,6 @@ foreach testType {Successful Aborted} {
}
test {Blocked commands and configs during async-loading} {
assert_error {LOADING*} {$replica config set appendonly no}
assert_error {LOADING*} {$replica REPLICAOF no one}
}
@@ -1457,3 +1456,143 @@ start_server {tags {"repl external:skip"}} {
}
}
}
foreach disklessload {disabled on-empty-db} {
test "Replica should reply LOADING while flushing a large db (disklessload: $disklessload)" {
start_server {} {
set replica [srv 0 client]
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
$replica config set repl-diskless-load $disklessload
# Populate replica with many keys, master with a few keys.
$replica debug populate 2000000
populate 3 master 10
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 100 100 {
[s -1 loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# If replica has a large db, it may take some time to discard it
# after receiving new db from the master. In this case, replica
# should reply -LOADING. Replica may reply -LOADING while
# loading the new db as well. To test the first case, populated
# replica with large amount of keys and master with a few keys.
# Discarding old db will take a long time and loading new one
# will be quick. So, if we receive -LOADING, most probably it is
# when flushing the db.
wait_for_condition 1 10000 {
[catch {$replica ping} err] &&
[string match *LOADING* $err]
} else {
# There is a chance that we may not catch LOADING response
# if flushing db happens too fast compared to test execution
# Then, we may consider increasing key count or introducing
# artificial delay to db flush.
fail "Replica did not reply LOADING."
}
catch {$replica shutdown nosave}
}
}
} {} {repl external:skip}
}
start_server {tags {"repl external:skip"} overrides {save {}}} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
populate 10000 master 10
start_server {overrides {save {} rdb-del-sync-files yes loading-process-events-interval-bytes 1024}} {
test "Allow appendonly config change while loading rdb on slave" {
set replica [srv 0 client]
# While loading rdb on slave, verify appendonly config changes are allowed
# 1- Change appendonly config from no to yes
$replica config set appendonly no
$replica config set key-load-delay 100
$replica debug populate 1000
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 10 1000 {
[s loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# Change config while replica is loading data
$replica config set appendonly yes
assert_equal 1 [s loading]
# Speed up loading and verify aof is enabled
$replica config set key-load-delay 0
wait_done_loading $replica
assert_equal 1 [s aof_enabled]
# Quick sanity for AOF
$replica replicaof no one
set prev [s aof_current_size]
$replica set x 100
assert_morethan [s aof_current_size] $prev
# 2- While loading rdb, change appendonly from yes to no
$replica config set appendonly yes
$replica config set key-load-delay 100
$replica flushall
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 10 1000 {
[s loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# Change config while replica is loading data
$replica config set appendonly no
assert_equal 1 [s loading]
# Speed up loading and verify aof is disabled
$replica config set key-load-delay 0
wait_done_loading $replica
assert_equal 0 [s 0 aof_enabled]
}
}
}
start_server {tags {"repl external:skip"}} {
set replica [srv 0 client]
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
test "Replica flushes db lazily when replica-lazy-flush enabled" {
$replica config set replica-lazy-flush yes
$replica debug populate 1000
populate 1 master 10
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 100 100 {
[s -1 lazyfreed_objects] >= 1000 &&
[s -1 master_link_status] eq {up}
} else {
fail "Replica did not free db lazily"
}
}
}
}
+10
View File
@@ -156,6 +156,11 @@ test "Shutting down master waits for replica then fails" {
set rd2 [redis_deferring_client -1]
$rd1 shutdown
$rd2 shutdown
wait_for_condition 100 10 {
[llength [regexp -all -inline {cmd=shutdown} [$master client list]]] eq 2
} else {
fail "shutdown did not arrive"
}
set info_clients [$master info clients]
assert_match "*connected_clients:3*" $info_clients
assert_match "*blocked_clients:2*" $info_clients
@@ -209,6 +214,11 @@ test "Shutting down master waits for replica then aborted" {
set rd2 [redis_deferring_client -1]
$rd1 shutdown
$rd2 shutdown
wait_for_condition 100 10 {
[llength [regexp -all -inline {cmd=shutdown} [$master client list]]] eq 2
} else {
fail "shutdown did not arrive"
}
set info_clients [$master info clients]
assert_match "*connected_clients:3*" $info_clients
assert_match "*blocked_clients:2*" $info_clients
+31 -1
View File
@@ -3,6 +3,7 @@
#include "redismodule.h"
#include <stdlib.h>
#include <string.h>
static RedisModuleType *FragType;
@@ -17,9 +18,12 @@ unsigned long int last_set_cursor = 0;
unsigned long int datatype_attempts = 0;
unsigned long int datatype_defragged = 0;
unsigned long int datatype_raw_defragged = 0;
unsigned long int datatype_resumes = 0;
unsigned long int datatype_wrong_cursor = 0;
unsigned long int global_attempts = 0;
unsigned long int defrag_started = 0;
unsigned long int defrag_ended = 0;
unsigned long int global_defragged = 0;
int global_strings_len = 0;
@@ -47,16 +51,29 @@ static void defragGlobalStrings(RedisModuleDefragCtx *ctx)
}
}
static void defragStart(RedisModuleDefragCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
defrag_started++;
}
static void defragEnd(RedisModuleDefragCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
defrag_ended++;
}
static void FragInfo(RedisModuleInfoCtx *ctx, int for_crash_report) {
REDISMODULE_NOT_USED(for_crash_report);
RedisModule_InfoAddSection(ctx, "stats");
RedisModule_InfoAddFieldLongLong(ctx, "datatype_attempts", datatype_attempts);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_defragged", datatype_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_raw_defragged", datatype_raw_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_resumes", datatype_resumes);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_wrong_cursor", datatype_wrong_cursor);
RedisModule_InfoAddFieldLongLong(ctx, "global_attempts", global_attempts);
RedisModule_InfoAddFieldLongLong(ctx, "global_defragged", global_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "defrag_started", defrag_started);
RedisModule_InfoAddFieldLongLong(ctx, "defrag_ended", defrag_ended);
}
struct FragObject *createFragObject(unsigned long len, unsigned long size, int maxstep) {
@@ -79,10 +96,13 @@ static int fragResetStatsCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
datatype_attempts = 0;
datatype_defragged = 0;
datatype_raw_defragged = 0;
datatype_resumes = 0;
datatype_wrong_cursor = 0;
global_attempts = 0;
global_defragged = 0;
defrag_started = 0;
defrag_ended = 0;
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
@@ -141,13 +161,14 @@ size_t FragFreeEffort(RedisModuleString *key, const void *value) {
}
int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value) {
REDISMODULE_NOT_USED(key);
unsigned long i = 0;
int steps = 0;
int dbid = RedisModule_GetDbIdFromDefragCtx(ctx);
RedisModule_Assert(dbid != -1);
RedisModule_Log(NULL, "notice", "Defrag key: %s", RedisModule_StringPtrLen(key, NULL));
/* Attempt to get cursor, validate it's what we're exepcting */
if (RedisModule_DefragCursorGet(ctx, &i) == REDISMODULE_OK) {
if (i > 0) datatype_resumes++;
@@ -188,6 +209,14 @@ int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value)
}
}
/* Defrag the values array itself using RedisModule_DefragAllocRaw
* and RedisModule_DefragFreeRaw for testing purposes. */
void *new_values = RedisModule_DefragAllocRaw(ctx, o->len * sizeof(void*));
memcpy(new_values, o->values, o->len * sizeof(void*));
RedisModule_DefragFreeRaw(ctx, o->values);
o->values = new_values;
datatype_raw_defragged++;
last_set_cursor = 0;
return 0;
}
@@ -230,6 +259,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_RegisterInfoFunc(ctx, FragInfo);
RedisModule_RegisterDefragFunc(ctx, defragGlobalStrings);
RedisModule_RegisterDefragCallbacks(ctx, defragStart, defragEnd);
return REDISMODULE_OK;
}

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