Compare commits

...
84 Commits
Author SHA1 Message Date
Jacob MurphyandGitHub 7b3117ecc1 Release notes for 8.1.1 (#1997)
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 13:38:09 -07:00
4db70fb6d4 Add output buffer limiting for unauthenticated clients (#1994)
This commit introduces a mechanism to track client authentication state
with a new `ever_authenticated` flag. It refactors client authentication
handling by adding a `clientSetUser` function that properly sets both
the `authenticated` and `ever_authenticated` flags.

The implementation limits output buffer size for clients that have never
been authenticated.

Added tests to verify the output buffer limiting behavior for
unauthenticated clients.

---------

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-04-23 20:48:10 +02:00
Vitah LinandViktor Söderqvist 9ab3126921 Fix CI latest fedora: install awk and tcl8, build tcltls from source (#1965)
Fix two problems in fedora CI jobs:

1. Install awk where missing. It's required for building jemalloc.
2. Fix problems with TCL, required for running the tests. Fedora comes
   with TCL 9 by default, but the TLS package 'tcltls' isn't built for TCL 9.
   Install 'tcl8' and build 'tcltls' from source.

---------

Signed-off-by: vitah <vitahlin@gmail.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Yair GottdenkerandViktor Söderqvist cc06c87a13 blocking client followup fix related to log_req_res (#1989)
See
https://github.com/valkey-io/valkey/pull/1819#issuecomment-2817273924

Verified the fix by:
1. Build: `make SERVER_CFLAGS='-Werror -DLOG_REQ_RES' -j10`
2. Running module api tests: `CFLAGS='-Werror' ./runtest-moduleapi
--log-req-res --no-latency --dont-clean --force-resp3 --dont-pre-clean
--verbose --dump-logs`

---------

Signed-off-by: yairgott <yairgott@gmail.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Madelyn OlsonandViktor Söderqvist 7720ee494e Allow scripts to support null characters again (#1984)
We made a change during the refactoring of the engine changes that used
a strlen on an SDS string. SDS are resizable, but they are binary safe
and support NULL characters, which means we are truncating the string at
the null character.

This is a breaking change for modules, so wanted to raise it for the
upcoming patch.

An alternative is we could expose the script code as a server object,
which would require some construction of the object in the function
pathway and freeing. I opted to keep the code flow as similar as
possible for the patch.

There is a test which only passes now.

For some reasons, functions don't support binary data. So as of right
now this patch doesn't fix that case.

Resolves https://github.com/valkey-io/valkey/issues/1942

---------

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Madelyn OlsonandViktor Söderqvist 13a8f3cd0f Only enable defrag for vendored jemalloc (#1985)
Only enable defrag for vendored jemalloc.

Resolves a point of discussion from
https://github.com/valkey-io/valkey/issues/1585, although not the
overall issue. This change should be reverted when we properly validate
using system jemalloc as part of
https://github.com/valkey-io/valkey/issues/1882.

---------

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Harkrishn PatroandViktor Söderqvist 90751b822d Avoid shard id update of replica if not matching with primary shard id (#573)
During cluster setup, the shard id gets established through extensions data propagation and if the engine crashes/restarts while the reconciliation of shard id is in progress, there is a possibility of corrupted config file with temporary shard id stored in the cluster. With this fix, a replica sharing a temporary shard id is ignored and allows the cluster bus to converge for only one shard id for primary and it's replicas.

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Yair GottdenkerandViktor Söderqvist ef1eae8223 Fix engine crash on module client blocking during keyspace events (#1819)
This change enhances user experience and consistency by allowing a
module to block a client on keyspace event notifications. Consistency is
improved by allowing that reads after writes on the same connection
yield expected results. For example, in ValkeySearch, mutations
processed earlier on the same connection will be available for search.

The implementation extends `VM_BlockClient` to support blocking clients
on keyspace event notifications. Internal clients, LUA clients, clients
issueing multi exec and those with the `deny_blocking` flag set are not
blocked. Once blocked, a client’s reply is withheld until it is
explicitly unblocked.

---------

Signed-off-by: yairgott <yairgott@gmail.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
nesty92andViktor Söderqvist ac1ffeb4d1 Fix incorrect lag reported in XINFO GROUPS (#1952)
When a tombstone is created after the last_id of a consumer group.

The consumer group lag is reported in the reply of `XINFO GROUPS mystream`.

Close: #1951

Signed-off-by: Ernesto Alejandro Santana Hidalgo <ernesto.alejandrosantana@gmail.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Jacob MurphyandViktor Söderqvist 1886f70cf2 Fix flaky shutdown tests caused by timing issue (#1960)
This started failing after
https://github.com/valkey-io/valkey/commit/57cd9a51d9c7d8cf9c4551bd1f2af79a0f279eeb,
since processing of SHUTDOWN might happen after the CLIENT INFO is
retrieved. Now we use wait_for_condition to make sure CLIENT INFO is
retried in the case that SHUTDOWN is not processed yet.

Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
BinbinandViktor Söderqvist c02e7e178c Ignore stale gossip packets that arrive out of order (#1777)
There is a failure in the daily:
```
=== ASSERTION FAILED ===
==> cluster_legacy.c:6588 'primary->replicaof == ((void *)0)' is not true
```

This is the logs:
```
- i am fd4318562665b4490ccc86e7f7988017cf960371 and myself become a replica,
- 63c0167232dae95cdcc0a1568cd5368ac3b99f5 is the new primary
27867:M 24 Feb 2025 00:19:11.011 * Failover auth granted to 763c0167232dae95cdcc0a1568cd5368ac3b99f5 () for epoch 9
27867:M 24 Feb 2025 00:19:11.039 * Configuration change detected. Reconfiguring myself as a replica of node 763c0167232dae95cdcc0a1568cd5368ac3b99f5 () in shard c5f6b2a9c74cabd4d1e54d1130dc9cb9419bf76f
27867:S 24 Feb 2025 00:19:11.039 * Before turning into a replica, using my own primary parameters to synthesize a cached primary: I may be able to synchronize with the new primary with just a partial transfer.
27867:S 24 Feb 2025 00:19:11.039 * Connecting to PRIMARY 127.0.0.1:23654
27867:S 24 Feb 2025 00:19:11.039 * PRIMARY <-> REPLICA sync started

- in here myself got an stale message, but we still process the packet and cause this issue
27867:S 24 Feb 2025 00:19:11.040 * Ignore stale message from 763c0167232dae95cdcc0a1568cd5368ac3b99f5 () in shard c5f6b2a9c74cabd4d1e54d1130dc9cb9419bf76f; gossip config epoch: 8, current config epoch: 9
27867:S 24 Feb 2025 00:19:11.040 * Node 763c0167232dae95cdcc0a1568cd5368ac3b99f5 () is now a replica of node fd4318562665b4490ccc86e7f7988017cf960371 () in shard c5f6b2a9c74cabd4d1e54d1130dc9cb9419bf76f
```

We can see myself got a stale message, but we still process it, and
changed
the role and cause a primary replica chain loop.

The reason is that, this text is copy from
https://github.com/valkey-io/valkey/pull/651.

In some rare case, slot config updates (via either PING/PONG or UPDATE)
can be delivered out of order as illustrated below:
```
1. To keep the discussion simple, let's assume we have 2 shards, shard a
   and shard b. Let's also assume there are two slots in total with shard
   a owning slot 1 and shard b owning slot 2.
2. Shard a has two nodes: primary A and replica A*; shard b has primary
   B and replica B*.
3. A manual failover was initiated on A* and A* just wins the election.
4. A* announces to the world that it now owns slot 1 using PING messages.
   These PING messages are queued in the outgoing buffer to every other
   node in the cluster, namely, A, B, and B*.
5. Keep in mind that there is no ordering in the delivery of these PING
   messages. For the stale PING message to appear, we need the following
   events in the exact order as they are laid out.
a. An old PING message before A* becomes the new primary is still queued
   in A*'s outgoing buffer to A. This later becomes the stale message,
   which says A* is a replica of A. It is followed by A*'s election
   winning announcement PING message.
b. B or B* processes A's election winning announcement PING message
   and sets slots[1]=A*.
c. A sends a PING message to B (or B*). Since A hasn't learnt that A*
   wins the election, it claims that it owns slot 1 but with a lower
   epoch than B has on slot 1. This leads to B sending an UPDATE to
   A directly saying A* is the new owner of slot 1 with a higher epoch.
d. A receives the UPDATE from B and executes clusterUpdateSlotsConfigWith.
   A now realizes that it is a replica of A* hence setting myself->replicaof
   to A*.
e. Finally, the pre-failover PING message queued up in A*'s outgoing
   buffer to A is delivered and processed, out of order though, to A.
f. This stale PING message creates the replication loop
```

Closes #1015.

---------

Signed-off-by: Binbin <binloveplay1314@qq.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Jacob MurphyandViktor Söderqvist c6936db7ae Fix panic in primary when blocking shutdown after previous block with timeout (#1948)
When previous timeout isn't zeroed out, shutdown will use the previously
executed blocking commands timeout. Since shutdown is not expected to
have timeouts, this causes a serverPanic.

Without this fix, this scenario leads to a server panic:

1. Call any command that uses blockClient with a timeout (this could be
BLPOP or something that blocks implicitly like CLUSTER SETSLOT)
2. On the same client, call SHUTDOWN with some pending replication data,
which will trigger shutdown to be blocked while we try to catch the
replica up

Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Katie HollyandViktor Söderqvist ab0d886681 Fix cluster slot stats assertion during promotion of replica (#1950)
This affects only the configuration `cluster-slot-stats-enabled yes`.

For the first command a newly promoted primary receives, we send a
`SELECT` command to all replicas via `replicationFeedReplicas`. This
calls a chain of functions (`feedReplicationBufferWithObject` ->
`feedReplicationBuffer` ->
`clusterSlotStatsIncrNetworkBytesOutForReplication` ->
`clusterSlotStatsUpdateNetworkBytesOutForReplication`) that increments
the per-slot `network_bytes_out` by `len * listLength(server.replicas)`.
However, if the new primary receives a command before any replicas
connected yet, `server.replicas` is still empty, causing the increment
to add zero.

Right after, we call `clusterSlotStatsDecrNetworkBytesOutForReplication`
to remove the `SELECT` overhead from the per-slot stats. The assert in
`clusterSlotStatsUpdateNetworkBytesOutForReplication` was using
(basically) `>= -len` instead of `>= -(len *
listLength(server.replicas))`, so it could fail if the previous
increment was zero because we never included the `server.replicas`
length in the assertion.

This patch makes the addition and subtraction use the same computed
value (`rlen = len * listLength(server.replicas)`) in both the stats
update and the assert, ensuring the logic remains consistent when
replicas are promoted.

Fixes #1912

Signed-off-by: Fusl <fusl@meo.ws>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
uriyageandViktor Söderqvist b02cd3fe85 Fix crash during TLS handshake with I/O threads (#1955)
Fix https://github.com/valkey-io/valkey/issues/1883

The issue arises from a missing memory release fence, which can cause
the main thread to see the updated client's IO read state
(`CLIENT_COMPLETED_IO`) but not the updated connection flags. In this
case, it crashes as the flags are not as expected.

Following @skolosov-snap instructions I was able to consistently
reproduce the issue on ARM instances by generating sufficient load to
invoke the IO threads while constantly creating new connections to
trigger the accept flow. I've validated that no crashes occur with this
fix.

I didn't write a regression test for the fix since reproduction is
difficult and flaky.

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Arthur LeeandViktor Söderqvist b8366ee935 fix: add samples to stream object consumer trees (#1825)
Use sampling when calculating the memory usage of consumers as part of
streams in MEMORY USAGE and MEMORY STATS.

Without this fix, all consumers are traversed, which is slow if there
are very many consumers.

Fixes #1824

Signed-off-by: arthur.lee <liziang.arthur@bytedance.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
Nathan ScottandViktor Söderqvist b9ce578cc3 Fix the build on less common platforms in zmalloc.c (#1922)
Corrects a mismatched type declaration for the used_memory_thread
pointer into the used_memory_thread_padded array (global scope) - in the
less used else branch of a cpp platform conditional.

Fixes #1916

Signed-off-by: Nathan Scott <nathans@redhat.com>
Signed-off-by: Jacob Murphy <jkmurphy@google.com>
2025-04-23 18:39:08 +02:00
b84434282e [Backport 8.1] Moved build-release automation to valkey-release-automation (#1977) (#1990)
All the Valkey Release automation has been moved and setup here:
https://github.com/valkey-io/valkey-release-automation

The release automation will now be triggered from the
[trigger-build-release.yml](https://github.com/valkey-io/valkey/blob/unstable/.github/workflows/trigger-build-release.yml)
workflow.

Need to remove these files to stop duplicating push to the s3 bucket.

---------

Signed-off-by: Roshan Khatri <rvkhatri@amazon.com>
Signed-off-by: Nikhil Manglore <nmanglor@amazon.com>
Co-authored-by: Roshan Khatri <117414976+roshkhatri@users.noreply.github.com>
2025-04-22 17:40:16 -07:00
Ran ShidlansikandGitHub 08e388cab8 Valkey release 8.1.0-ga (#1903)
Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
2025-03-31 17:56:45 +03:00
VanessaTangandRan Shidlansik 6348cb52f5 Redact protocol error log when hide-user-data-from-log enabled (#1889)
In this code logic:
https://github.com/valkey-io/valkey/blob/unstable/src/networking.c#L2767-L2773,
`c->querybuf + c->qb_pos` may also include user data.
Update the log message when config `hide-user-data-from-log` is enabled.

---------

Signed-off-by: VanessaTang <yuetan@amazon.com>
Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
Co-authored-by: Ran Shidlansik <ranshid@amazon.com>
2025-03-31 14:05:51 +03:00
fb011c8d5c Remove TLSCONN_DEBUG dead code in tls.c (#1891)
This code is a dead code, there is also a undefined fd error in it.
Obviously this will not compile if this kind of debugging is enabled.

This seems to be broken ever since
2d5ba02f8f
which was done in 2019. It's dead code that we never test and never use.

Fixes #1887

Signed-off-by: WelongZuo <zuowl@qq.com>
Co-authored-by: z00636558 <zuowenlong6@huawei.com>
2025-03-31 14:05:51 +03:00
BinbinandRan Shidlansik 4d3f4d75ef Fix TCL tmp dir leak in the ACL load test (#1895)
Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-31 14:05:51 +03:00
Jim BrunnerandRan Shidlansik e261ae1260 Fix merge error introduced in #1186 (#1894)
Merge error introduced in #1186 .

Prior to the merge, the time value passed to modules was a `monotime`.
The merge reverts it to a wall clock time (on the sending side only).

Signed-off-by: Jim Brunner <brunnerj@amazon.com>
2025-03-31 14:05:51 +03:00
ef4e7d6555 Fix crash in VM_GetCurrentUserName when no valid user (#1885)
VM_GetCurrentUserName make the binary crash when it gets not fully
structured ctx, when it doesn't have a valid ctx, client, user behind
it. Rather than making the binary crash, safely return NULL.

---------

Signed-off-by: jeon1226 <jeon1226@gmail.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-31 14:05:51 +03:00
157a85b9e9 Update COPY and MOVE command description clearer in JSON file (#1843)
As I do the code review for PR
https://github.com/valkey-io/valkey/pull/1671 (Add multi-database
support to cluster mode), I find
the return value message of the command COPY and MOVE is not very clear
to user. Thus I update them in this PR and valkey-doc repo as PR
https://github.com/valkey-io/valkey-doc/pull/249

---------

Signed-off-by: hwware <wen.hui.ware@gmail.com>
Signed-off-by: Wen Hui <wen.hui.ware@gmail.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
2025-03-31 14:05:51 +03:00
Harkrishn PatroandRan Shidlansik 1ae5d2d8f1 [cluster] Add node id to log statement for closing link on first message as lightweight (#1869)
---------

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
2025-03-31 14:05:51 +03:00
Wen HuiandRan Shidlansik 7e6a497ed1 Minor cleanup remove unnecessary cast since slot is int (#1865)
Signed-off-by: hwware <wen.hui.ware@gmail.com>
2025-03-31 14:05:51 +03:00
e2743ca467 Valkey release 8.1.0-rc2 (#1862)
Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-20 18:46:54 +02:00
9951b16d1b Fix RANDOMKEY infinite loop during CLIENT PAUSE (#1850)
When the `client pause write` is set and all the keys in the server
are expired keys, executing the `randomkey` command will lead to an
infinite loop.

The reason is that expired keys are not deleted in this case. Limit
the number of tries and return an expired key after the max number
tries in this case.

Closes #1848.

---------

Signed-off-by: li-benson <1260437731@qq.com>
Signed-off-by: youngmore <youngmore1024@outlook.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: youngmore <youngmore1024@outlook.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
Meinhard ZhouandViktor Söderqvist f5c368e9e1 Support IPv6 address in RDMA test (#1817)
Signed-off-by: Meinhard Zhou <zhouenhua@bytedance.com>
2025-03-18 11:51:02 +01:00
Nikhil MangloreandViktor Söderqvist dc72f47588 Update correct repository name for automation trigger workflow (#1855)
Update automation repository name to `valkey-release-automation`

Signed-off-by: Nikhil Manglore <nmanglor@amazon.com>
2025-03-18 11:51:02 +01:00
0befdee478 Trigger post-release tasks in Valkey for a new release (#1830)
This trigger-build-release.yml file will be used to automate the release
process as described in #1397. It will trigger the post release task after a new release

---------

Signed-off-by: Nikhil Manglore <nmanglor@amazon.com>
Signed-off-by: Nikhil Manglore <nikhilmanglore9@gmail.com>
Co-authored-by: Roshan Khatri <117414976+roshkhatri@users.noreply.github.com>
2025-03-18 11:51:02 +01:00
fa564cdc71 Update valkey-benchmark parseURI function name and comment (#1845)
In Valkey, when user runs the valkey-benchmark tools, the command
**valkey-benchmark -u** could accept 4 schemes of server URI format:  
"valkey://", "valkeys://", "redis://", "rediss://"   

Thus, I add them in the function comment, and update the function name
to parseRedisOrValkeyUri.

Finally, I fix one mistake for valkey-benchmark tool name for this
command.

---------

Signed-off-by: hwware <wen.hui.ware@gmail.com>
Signed-off-by: Wen Hui <wen.hui.ware@gmail.com>
Co-authored-by: Harkrishn Patro <bunty.hari@gmail.com>
2025-03-18 11:51:02 +01:00
Rain ValentineandViktor Söderqvist 4b7ccb3ed3 Fix defrag when type/encoding changes during scan (#1801)
I was hunting for defrag bugs with Jim and found a couple improvements
to make. Jim pointed out that in several of the callbacks, if the
encoding were to change it simply returns without doing anything to
`cursor` to make it reach 0, meaning that it would continue no-op
working on that item without making any progress. Type and encoding can
change while the defrag scan is in progress if the value is mutated or
replaced by something else with the same key.

---------

Signed-off-by: Rain Valentine <rsg000@gmail.com>
2025-03-18 11:51:02 +01:00
31cd2309b8 Fix ACL LOAD crash on replica since the primary client don't has a user (#1842)
Check for NULL before accessing users of a client during the `ACL LOAD`
command.
This prevents the command causing a segmentation fault in the replica.

Fixes #1832.

---------

Signed-off-by: Bogdan Petre <bogdan.petre@aiven.io>
Signed-off-by: Binbin <binloveplay1314@qq.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist 4bbcb0173a Add missing close_replication_stream on multi test (#1841)
Minor cleanup.

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist f363394877 Save config file and brocast the PONG when configEpoch changed (#1813)
This is somehow related with #974 and #1777. When the epoch changes,
we should save the configuration file and broadcast a PONG as much
as possible.

For example, if a primary down after bumping the epoch, its replicas
may initiate a failover, but the other primaries may refuse to vote
because the epoch of the replica has not been updated.

Or for example, for some reasons we bump the epoch, if the epoch
is not updated in time in the cluster, it may affect the judgment
of message staleness.

These broadcasts are expensive in large clusters, but none of these
seem high frequency so it should be fine.

---------

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
c70d660b31 Update CloseKey module API documentation to avoid use-after-free behavior (#1834)
### Issue: https://github.com/valkey-io/valkey/issues/1775

Without a clear comment, users might mistakenly attempt to access or
close the same key object multiple times, leading to use-after-free
errors, undefined behavior, or crashes due to double-free operations.

---------

Signed-off-by: Seungmin Lee <sungming@amazon.com>
Signed-off-by: Seungmin Lee <155032684+sungming2@users.noreply.github.com>
Co-authored-by: Seungmin Lee <sungming@amazon.com>
Co-authored-by: Harkrishn Patro <bunty.hari@gmail.com>
2025-03-18 11:51:02 +01:00
eifrah-awsandViktor Söderqvist 18d6e34ce0 [CMake] Check both arm64 and aarch64 for ARM based system architecture (#1829)
While macOS will report `arm64` for `uname -m` command, others might
report `aarch64`. This PR fixes this

Signed-off-by: Eran Ifrah <eifrah@amazon.com>
2025-03-18 11:51:02 +01:00
Madelyn OlsonandViktor Söderqvist a14d587ff0 Fix bug where invalidation messages were getting sent to closing clients (#1823)
So I think we were seeing [these
timeouts](https://github.com/valkey-io/valkey/actions/runs/13688155322/job/38276139543#step:6:839)
because QUIT behaves differently between IO threading and non-IO
Threading. In both cases, `QUIT` is a close after reply command. Once
the client has written out the results, it gets added to the queue to
that gets cleaned up at the end of the event loop. Normally this is
fine, as before we circle around to the next event loop this client is
definitely killed.

For IO threads, we need to process the pending IO commands to add the
client to the kill queue. This may not happen immediately, which means
we might go down and process that `SET` command *before* we free the
client that is supposedly already quit. This is very sensitive to
timing, so it's not very likely, but still possible. Once the `SET` has
been executed, the invariants in the tests are off since it will get a
correct invalidation.

The fix is to also mark a client as broken if it's being closed. 

The test was also hanging because of a test issue, because the
conditional `lsearch` check was returning 1 or 0 strings, which are both
valid exit criteria for the wait_for.

```
./runtest --io-threads --accurate --verbose --tags network --dump-logs --single unit/tracking --loops 500 --clients 25
```

Fixes #1647 (I believe this now!)

---------

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
2025-03-18 11:51:02 +01:00
Ricardo DiasandViktor Söderqvist 41834b157c Adds a memory leak check after running a unit test (#1798)
This commit adds check after each test function execution of a unit
test, that checks if there is still memory allocated.

This check prevents situations where tests, which do these kind of
checks, fail due to memory leaks caused by previous tests.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
2025-03-18 11:51:02 +01:00
ranshidandViktor Söderqvist 154a633c33 Enable large-memory tests solo runs in daily workflow (#1816)
This change re-introduce large-memory tests to be run as solo tests on
daily runs.

the change includes:
1. separate the large-memory tests into separate test block (mainly
helpful in workflow manual dispatch cases)
2. place all current large memory tests in run solo blocks in order to
prevent potential runner OOM failures.

Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist 1bccc8e3ba Fix timing issue in the module propagate test (#1815)
There is a timing issue in the test:
```
*** [err]: module RM_Call of expired key propagation in tests/unit/moduleapi/propagate.tcl
Expected '1' to be equal to '2' (context: type eval line 27 cmd {assert_equal [$replica propagate-test.obeyed] 2} proc ::test)
```

We should wait for sync, otherwise the replica may not be fully
synchronized before checking. The test was introduced in #1582.

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
zhenwei piandViktor Söderqvist 71b230c62a Use the wrapper from cli_common instead of hiredis (#1802)
There are mixed usage around `redisConnect*`, we should use
`redisConnectWrapper*` wrapper from cli_common instead of
`redisConnect` from hiredis. This is a cleanup.

Signed-off-by: zhenwei pi <zhenwei.pi@linux.dev>
Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
2025-03-18 11:51:02 +01:00
70080e8a97 Fix incorrect assertion in client list operations (#1800)
The current assertion introduced in #11220:
```c
serverAssert(&c->clients_pending_write_node.next != NULL || &c->clients_pending_write_node.prev != NULL);
```

is incorrect for two reasons:
1. Using &pointer.next would always be non-NULL since it's the address
of the field.
2. The check is incorrect even without the & because in a single-node
list, both pointers can be NULL.

Fix:
1. Remove the always-true assertion
2. Add proper assertions in listUnlinkNode to ensure the node membership
in the list to cover all list cases.

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
Signed-off-by: uriyage <78144248+uriyage@users.noreply.github.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
secwallandViktor Söderqvist 1f5d891080 Drop lua object files on clean (#1812)
Let `make clean` delete the files `src/lua/*.o` and `src/lua/*.d`.

---------

Signed-off-by: secwall <secwall@yandex-team.ru>
2025-03-18 11:51:02 +01:00
Madelyn OlsonandViktor Söderqvist b091afc47a Stop running large memory test for address santizer (#1810)
After this change, https://github.com/valkey-io/valkey/pull/1767/files,
the address sanitizer test seems to be causing the test process to just
die, possibly from OOM:
https://github.com/valkey-io/valkey/actions/runs/13620692597/job/38069694391#step:6:4755.

If we switch the large memory tests to be sequential on ASAN, it might
resolve it, but I wanted to open this to propose reverting it to get the
tests passing again.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
2025-03-18 11:51:02 +01:00
zhaozhao.zzandViktor Söderqvist 06d7a561ae make net_input_bytes_curr_cmd more readable (#1756)
The metric `net_input_bytes_curr_cmd` is now computed by aggregating its components separately.

Signed-off-by: zhaozhao.zz <zhaozhao.zz@alibaba-inc.com>
2025-03-18 11:51:02 +01:00
eifrah-awsandViktor Söderqvist 0d4ef40768 Fixed build error with CMake (#1806)
Explicitly cast `long long` -> `long double` to avoid build warnings.
This issue manifests when using `clang 19`. Using the `Makefile` build,
we only get a warning. This small PR fixes this.

Fixes issue: https://github.com/valkey-io/valkey/issues/1805

Signed-off-by: Eran Ifrah <eifrah@amazon.com>
2025-03-18 11:51:02 +01:00
Jim BrunnerandViktor Söderqvist 732bb009bb Update and stabilize defrag tests (#1762)
A number of tests related to defrag have had stability problems.

One reason for stability issues is that the tests are run sequentially
on the same server, and it takes jemalloc some time to release freed
pages. This is especially noticed immediately after a flushall. Over a
period of 10 seconds, it is observable that the "fragmentation bytes"
can decrease by several MB. Another reason is that there's no
standardization between tests. For each test, people have been
independently hacking/tweaking the success criteria, without addressing
underlying issues.

This update revamps all of the defrag tests:
* A fresh server is started for each test. Running each test in
isolation improves stability.
* A uniform function `log_frag` is now used for debug logging
* A uniform function `perform_defrag_test` ensures that each test is
written and executed in a uniform fashion. Limits are imposed to ensure
that the defrag results are consistent/reproducible. The intent is to
eliminate failures do to various tweaks to values in individual tests.
* Latency is tested much more strictly for most tests, reflecting the
recent improvements to defrag latency.
* The test `defrag edge case` has been removed. This test attempted to
create N pages with EXACTLY equal fragmentation in an attempt to confuse
the defrag logic. It's unlikely that this test was performing correctly,
and had questionable value.
* Tests for hash/list/set/zset/stream have been separated and
standardized. It was unlikely that the old test was performing properly
as none of the actual data structures were fragmented!

It's noted that pubsub doesn't appear to be defragging correctly. The
old test was based on deletion of strings (only) which doesn't actually
reflect what happens when a pubsub channel is removed. The test has been
reduced to only check that pubsub is not damaged during defrag - but
doesn't test for defrag efficacy. This isn't likely a significant issue
as it would be unlikely to create many thousands of pubsub channels and
then have associated fragmentation issues.
https://github.com/valkey-io/valkey/issues/1774

Resolves: https://github.com/valkey-io/valkey/issues/1746

---------

Signed-off-by: Jim Brunner <brunnerj@amazon.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
Viktor Söderqvist 8cf8e3fb28 Fix hashTypeEntryDefrag returning bad pointer (#1799)
Fixes #1795.

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist bc8a114957 Add cluster-manual-failover-timeout to configure the timeout for manual failover (#1690)
Allows cluster admins to configure the cluster manual failover timeout
as
needed, admins can configure how long a primary would be paused in the
worst case scenario such as a failover timed out due to the insufficient
votes.

The configuration name is cluster-manual-failover-timeout, the unit is
milliseconds, and the range is [1, INT_MAX]ms.

---------

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
Ricardo DiasandViktor Söderqvist b2ffab404d Fix memory leak in test_quicklist.c unit test (#1797)
In last daily run we had the following failure:

```
test — compress and decomress quicklist plain node large than UINT32_MAX
[test_quicklistCompressAndDecomressQuicklistPlainNodeLargeThanUINT32MAX - unit/test_quicklist.c:2288] Compress and decompress: 4096 MB in 39.27 seconds.

[ok] - test_quicklist.c:test_quicklistCompressAndDecomressQuicklistPlainNodeLargeThanUINT32MAX
[END] - test_quicklist.c: 58 tests, 58 passed, 0 failed
[START] - test_rax.c
[test_raxRandomWalk - unit/test_rax.c:548] Failed assertion: raxAllocSize(t) == zmalloc_used_memory()
[fail] - test_rax.c:test_raxRandomWalk
```

Job Link:
https://github.com/valkey-io/valkey/actions/runs/13555915665/job/37890070586

Although the assert that failed was in a `test_rax.c` test function, the
problem was in the last test function from the `test_quicklist.c` unit
test that ran just before the test that failed.

The

`test_quicklistCompressAndDecomressQuicklistPlainNodeLargeThanUINT32MAX`
test function was not freeing the string allocated in the beginning of
the test.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
2025-03-18 11:51:02 +01:00
Madelyn OlsonandViktor Söderqvist 1eebcd5f81 Consistent look and feel of licenses (#1788)
Use a consistent set of licenses for Valkey files. I took a look and
applied sort of a "did we make a material change in this file?" and
tried to be conservative in adding the trademark. We could also be
liberal as well.

Resolves: https://github.com/valkey-io/valkey/issues/1692.

Included documentation about the licensing here:
https://github.com/valkey-io/valkey/pull/1787.

Licenses are now also always explicitly first, even about documentation
files.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
2025-03-18 11:51:02 +01:00
a1473af68f Fix clang build error in bitops.c (#1794)
In a recent PR https://github.com/valkey-io/valkey/pull/1741 the new
header `<immintrin.h>` was added, which transitively includes
`<mm_malloc.h>` header, where a function called `_mm_malloc(...)` makes
a call to the `malloc` function.

The Valkey server code explicitly sets the malloc function as a
deprecated function in `server.h`:
```c
void *malloc(size_t size) __attribute__((deprecated));
```

The Valkey server code is then compiled with
`-Werror=deprecated-declarations` option to detect the uses of
deprecated functions like `malloc`, and due to this, when the `bitops.c`
file is compiled with Clang, fails with the following error:

```
In file included from bitops.c:33:
In file included from /usr/lib/llvm-18/lib/clang/18/include/immintrin.h:26:
In file included from /usr/lib/llvm-18/lib/clang/18/include/xmmintrin.h:31:
/usr/lib/llvm-18/lib/clang/18/include/mm_malloc.h:35:12: error: 'malloc' is deprecated [-Werror,-Wdeprecated-declarations]
   35 |     return malloc(__size);
      |            ^
./server.h:3874:42: note: 'malloc' has been explicitly marked deprecated here
 3874 | void *malloc(size_t size) __attribute__((deprecated));
```

There is a difference in behavior though, between GCC and Clang. The
`bitops.c` file compiles successfully with GCC.

I don't know exactly why GCC does not issue a warning in this case. My
best guess is that GCC does not issue warnings from code of the standard
library.

To fix the build error in clang, we explicitly use `pragma` macro to
tell clang to ignore deprecated declarations warnings in `bitops.c`.

---------

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <rjd15372@gmail.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
2025-03-18 11:51:02 +01:00
Roshan KhatriandViktor Söderqvist fcd9f0ffac Migrate binaries build to ARM github runners (#1790)
This PR migrates the workflows to run on github hosted arm runners for each ubuntu version that is currently supported. It is much faster than the emulated version that used to take around 21 mins. Currently, it takes around 4-5 mins.

Signed-off-by: Roshan Khatri <rvkhatri@amazon.com>
2025-03-18 11:51:02 +01:00
Wen HuiandViktor Söderqvist c0a9643312 Add Cluster Bus Port out of range error message for Cluster Meet command (#1686)
Now for the cluster meet command, we first check the port and cport
number, and then the function clusterStartHandshake() is called.
In the function clusterStartHandshake(), ip address is checked first and
then port and cport range are checked.
Even port or cport range is out of bound, only error message "Invalid
node address specified" is reported.
This is not correct.

In this PR, I just make the port and cport check together before
clusterStartHandshake() function. Thus the port number and range can be
checked together.

One example:

Current behavior:

127.0.0.1:7000> cluster meet 10.21.96.98 65000
(error) ERR Invalid node address specified: 10.21.96.98:65000
127.0.0.1:7000> cluster meet 1928.2292.2983.3884.26622 6379
(error) ERR Invalid node address specified:
1928.2292.2983.3884.26622:6379

Whatever the wrong ip address or incorrect bus port number, user get the
same error message.

New behavior:

127.0.0.1:7000> cluster meet 10.21.96.98 65000
(error) ERR Cluster bus port number is out of range
127.0.0.1:7000> cluster meet 1928.2292.2983.3884.26622 6379
(error) ERR Invalid node address specified:
1928.2292.2983.3884.26622:6379

User can get much clearer error message.

Related note pr: https://github.com/valkey-io/valkey-doc/pull/240

---------

Signed-off-by: hwware <wen.hui.ware@gmail.com>
Signed-off-by: Wen Hui <wen.hui.ware@gmail.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
zhaozhao.zzandViktor Söderqvist 908f863870 cmd's out bytes need count deferred reply (#1760)
the special deferred reply is ignored in current command's
`net_output_bytes_curr_cmd` counting

Signed-off-by: zhaozhao.zz <zhaozhao.zz@alibaba-inc.com>
2025-03-18 11:51:02 +01:00
xbaselandViktor Söderqvist a681073365 valkey-cli: ensure output ends with a newline if missing when printing reply (#1782) 2025-03-18 11:51:02 +01:00
Ricardo DiasandViktor Söderqvist 2bdb021d93 Remove unicode optimization in Lua cjson library (#1785)
The Lua cjson library implements an optimization that pre-allocates a
string buffer by estimating the maximum memory used if all characters in
a string require to be represented as unicode escapes, which may take 6
bytes each. Therefore, if a string has `len` bytes, the pre-allocated
buffer will have `len * 6` bytes.

This optimization can easily cause OOM errors, because if someone uses a
string with a few gigabytes of size, the pre-allocator will require 6
times the size of that string, and when running the Lua script in a
small instance with low memory, it will make the valkey-server process
to abort.

I ran the following Lua script to check if there is a significant
performance regression:

```
local s = string.rep("a", 1024 * 1024 * 1024)
return #cjson.encode(s..s..s)
```

The execution duration, in seconds, for 3 runs before and after this
commit is the following:

Before: 46.309; 42.443; 42.242

After: 46.729; 42.969; 42.774

---------

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <rjd15372@gmail.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
Viktor Söderqvist 10c00687d8 Fix undefined behaviour in bitops unit test (#1786)
The unit test was added in #1741. It fails when compiled with UBSAN.
Using a local array like `char buf[size]` is undefined behaviour when
`size == 0`. This fix just makes it size 1 in this case.

The failure I got locally:

unit/test_bitops.c:28:13: runtime error: variable length array bound
evaluates to non-positive value 0

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
chzhooandViktor Söderqvist 6fc7ecb575 Optimize bitcount command by SIMD (#1741)
**Background**

- Currently, we implement bitcount using a lookup table method
- By SIMD, parallel table lookups can be achieved, which boosts
performance
- Most x86 servers support the AVX2 instruction set

**BenchMark**
| Value Size | QPS (After optimization) | QPS (Before optimization) |
change |
| ---- | ---- | ---- | ---- |
|16 B | 114925| 115924 |  -0.8%|
|256 B| 112619 | 112201|  +0.3%|
|4 KB| 105523|96251| +9.6%|
|64 KB|79723|36796| +116%|
|1MB|21306|3466|+514%|

CPU: AMD EPYC 9754 128-Core Processor * 8
OS:   Ubuntu Server 22.04 LTS 64bit
Memory: 16GB
VM: Tencent cloud SA5.2XLARGE16

**Test Plan**
Pending. Will add test if it looks okay

**Other**
This PR is based on
https://github.com/WojciechMula/sse-popcount/blob/master/popcnt-avx2-lookup.cpp

---------

Signed-off-by: chzhoo <czawyx@163.com>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist 5dd14d8f4d Fix temp file leak druing replication error handling (#1721)
Before actually entering REPL_STATE_TRANSFER, we usually have
some other things to do, such as registering the ae handler, etc.
If an error occurs at this time, we may leak the previously opened
temp file.

This commit adds a new cleanupTransferResources function to do the
cleanup, avoiding code duplication.

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
Jim BrunnerandViktor Söderqvist a9656d44cc defrag: remove assert on defrag_later (#1779)
If a flushall is performed in the middle of defrag, the current stage
will be aborted when the change in kvs is detected. However the defrag
cycle will continue and defrag will complete normally. In a normal
termination, we expect the `defrag_later` list to be empty - and it
might not be in this condition.

This update removes the incorrect assertion, allowing the list to be
freed just like for an abnormal termination.

Signed-off-by: Jim Brunner <brunnerj@amazon.com>
2025-03-18 11:51:02 +01:00
a8622d8f75 Enable TCP_NODELAY by default in incoming and outgoing connections (#1763)
Fixes https://github.com/valkey-io/valkey/issues/1758
This is a follow up issue from
https://github.com/valkey-io/valkey/pull/1706#issuecomment-2669567875


### Problem: 
The absence of TCP_NODELAY can cause unnecessary latency due to [Nagle’s
algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm) (buffers
small TCP packets), which could be disabled for traffic.
We need to consider enabling TCP_NODELAY by default in both incoming and
outgoing connections
---------

Signed-off-by: Seungmin Lee <sungming@amazon.com>
Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Seungmin Lee <sungming@amazon.com>
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
2025-03-18 11:51:02 +01:00
Viktor Söderqvist 6bfd2abe00 Embed hash value in hash type entry (#1579)
For a hash type key represented as a hash table, embed the field and
value in one allocation when they fit together in an allocation of up to
128 bytes. For larger fields and values, another layout is used where
the value is a separately allocated sds string.

**Implementation**

The hashTypeEntry pointer is changed to be the field sds, i.e. a pointer
to the embedded field content, which is located after the sds header in
memory. We encode the entry layout in the some unused bits in the sds
header.

Entry with embedded field and value, used when they're both small. The
value is stored as SDS_TYPE_8. The field can use any SDS type.

    +--------------+---------------+
    | field        | value         |
    | hdr "foo" \0 | hdr8 "bar" \0 |
    +------^-------+---------------+
           |
           |
         entry pointer = field sds

Entry with value-pointer and embedded field, used for larger field-value
pairs. The field is SDS type 8 or higher.

    +-------+--------------+
    | value | field        |
    | ptr   | hdr "foo" \0 |
    +-------+------^-------+
                   |
                   |
                entry pointer = field sds

An embedded sds5 field implies the first entry layout.

Fixes #1551.

---------

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
Anastasia AlexandrovaandViktor Söderqvist c2c9d1a36d Fixed active-expire-effort description in conf file (#1773)
Expired keys search happens without user actions. Therefore, the
"interactively" word in the description of the active-expire-key
parameter is confusing and is changed to "incrementally."
 
modified:   valkey.conf

---------

Signed-off-by: Anastasia Alexadrova <anastasia.alexandrova@percona.com>
2025-03-18 11:51:02 +01:00
ranshidandViktor Söderqvist 17420f6540 Add large memory flag for asan tests (#1767)
Currently we do not use --large-memory flavor in our daily tests.
There is some value enabling this flavor in order to identify different issues related to buffer overrun and bad memory access.

---------

Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
2025-03-18 11:51:02 +01:00
Nikhil MangloreandViktor Söderqvist 67d4826522 Fixed issue with CONFIG RESETSTAT in cluster module message callback test (#1768)
**Problem**
The cluster module test "Cluster module message DING/DONG
acknowledgment" is failing despite successful message exchanges between
nodes. While these messages are being properly exchanged between the
nodes, running multiple tests on the same cluster instance is causing
issues with message statistics tracking. `CONFIG RESETSTAT` doesn't seem
to reset the stats under certain cases which causes the error as the
stats are adding onto each other from previous tests.

**Solution**
Remove some overlap with the previous test case so we don't need `CONFIG
RESETSTAT` between the test cases.

Resolves #1764

---------

Signed-off-by: Nikhil Manglore <nmanglor@amazon.com>
2025-03-18 11:51:02 +01:00
Viktor Söderqvist 0915747ff4 Disable Fedora Fawhide in Daily runs (#1769)
Because of a packaging problem of TCL in Fedora Rawhide (Fedora's
unstable branch), temporarily disable tests until the packaging is fixed
in the distro.

---------

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
secwallandViktor Söderqvist 55a5d5c53d Fix murmur32 on large strings (#1748)
Current murmur32 implementation fails on large strings.
Way to reproduce crash:
```
eval 'local s = string.rep("a", 1024 * 1024 * 1024) return #cjson.encode(s..s..s)' 0
```
(Basically this is a copy-paste from large test in
`tests/unit/scripting.tcl`).

Shouldn't we run tests with `--large-memory` on daily CI so we could
find this earlier?

I think we need to backport this to 8.1 branch.

Signed-off-by: secwall <secwall@yandex-team.ru>
2025-03-18 11:51:02 +01:00
cf89dd54e5 Fix error "SSL routines::bad length" when connTLSWrite is called second time with smaller buffer (#1737)
Issue described in #1135 

When call to `connTLSWrite` fails on next attempt `connTLSWritev` should
provide buffer with at least same amount of bytes,
but if first call was with buffer smaller than
`NET_MAX_WRITES_PER_EVENT` then buffer gets larger and exceed
`NET_MAX_WRITES_PER_EVENT`, `connTLSWritev` will not combine `iov` into
one buffer resulting into chance that first element of `iov` is smaller
than last failed call to `connTLSWrite` and causing `SSL routines::bad
length`.

This change force combining `iov`s into one buffer if first element of
`iov` is smaller than last failed write by `connTLSWrite`

---------

Signed-off-by: Marek Zoremba <marek@janeasystems.com>
Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
Signed-off-by: ranshid <ranshid@amazon.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: ranshid <ranshid@amazon.com>
2025-03-18 11:51:02 +01:00
743db67670 Move TCP/TLS specific options from generic client to connection type (#1706)
Fixes https://github.com/valkey-io/valkey/issues/1702

Signed-off-by: Seungmin Lee <sungming@amazon.com>
Co-authored-by: Seungmin Lee <sungming@amazon.com>
2025-03-18 11:51:02 +01:00
5b2ad0fe2c Add new module API flag to bypass command validation (#1357)
### Issue https://github.com/valkey-io/valkey/issues/1175
### Problem
Performance degradation occurs due to the sanity
check([lookupCommandByCString](https://github.com/valkey-io/valkey/blob/unstable/src/module.c#L3539))
in the VM_Replicate function, which converts const char* into sds and
free them for command dictionary lookup. This check is mainly used for
debugging purpose, but it is unnecessary for trusted modules. The user
seeks a way to bypass this check for performance gains.

### Solution
Introduce a new SKIP_COMMAND_VALIDATION option which allows individual
modules to opt out of command validation

### Test
ADded a unit test

---------

Signed-off-by: Seungmin Lee <sungming@amazon.com>
Co-authored-by: Seungmin Lee <sungming@amazon.com>
2025-03-18 11:51:02 +01:00
Ricardo DiasandViktor Söderqvist 475153931b Allow to specify the random number generator seed in unit tests (#1751)
In this commit, we add a new parameter to the unit tests binary, called
`--seed`, that allows to specify the seed used by the several random
numbers generators used in the Valkey server code.

We also now print the seed information right before starting the unit
tests execution, so that someone that needs to reproduce an error, can
use the same seed to re-run the failed tests.

---------

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
2025-03-18 11:51:02 +01:00
Nikhil MangloreandViktor Söderqvist 5a1141d344 Pass null-terminated node ID for VM_RegisterClusterMessageReceiver and add test coverage (#1708)
* Pass `sender_id` as `NULL` terminated string as part of
`ValkeyModuleClusterMessageReceiver` for ease of usage by the module(s).

* Implement test coverage described in #1656 and ensures that nodes in
the cluster module properly acknowledge a "DING" message by sending a
"DONG" response.

Closes #1656.

---------

Signed-off-by: Nikhil Manglore <nmanglor@amazon.com>
2025-03-18 11:51:02 +01:00
skyfireleeandViktor Söderqvist e449cc9752 Fix hashtable memory leak and kvstore overhead_hashtable_lut assert (#1750)
When releasing a kvstore and its hashtables, if it is still rehashed and
table 0 has no data, it may still have allocated buckets that were not
released. This lead to allocated hashtable buckets being leaked. This
looked like missed statistics but it was actually a memory leak.

fix: https://github.com/valkey-io/valkey/issues/1657

---------

Signed-off-by: artikell <739609084@qq.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
Viktor Söderqvist 65d31e744d Comment out assert in kvstore for overhead lut (#1745)
Until we found out why the assert is flaky, let's comment it out. We can
live with this overhead lut being slightly wrong, but we don't want the
assert to abort the program.

Fixes #1657.

Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-03-18 11:51:02 +01:00
bbd96ab81f Fix raxRemove crash at memcpy() due to key size exceeds max Rax size (#1722)
Fix raxRemove crash at memcpy() (line 1181) due to key size exceeds
`RAX_NODE_MAX_SIZE`. Note that this could happen when key size was more
than 512MB if we allow it by increasing the default
`proto-max-bulk-len`. The crash could happen when we recompress the rax
after removing a key due to expiry or DEL while memcpy() merge the key
that exceed 512MB limit. While the counting phase has the size check,
the actual compress logic is missing it which lead to this crash.

---------

Signed-off-by: Ram Prasad Voleti <ramvolet@amazon.com>
Co-authored-by: Ram Prasad Voleti <ramvolet@amazon.com>
2025-03-18 11:51:02 +01:00
ranshidandViktor Söderqvist 0ccc15fd0b Explicitly use github arm runners for ARM release (#1742)
In the last 8.1.0-rc1 release attempt we identified that the cross
compilation on x86 for ARM is broken
(example:
https://github.com/valkey-io/valkey/actions/runs/13324096504/job/37225952044)

This PR sets use of arm runners during release binaries.

Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
2025-03-18 11:51:02 +01:00
Madelyn OlsonandViktor Söderqvist 699d8c64d3 Add a daily test running for ARM (#1738)
Use official arm github runners to verify the ARM build as part of daily run. Also, updated the daily notify job failure list to a natural yaml list.
---------

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
2025-03-18 11:51:02 +01:00
Ray CaoandViktor Söderqvist 1691a000ef Modify parameter of clientMatchesFilter function. (#1733)
small change to [#1401](https://github.com/valkey-io/valkey/pull/1401/).
pass `clientFilter *` to clientMatchesFilter as suggest in
[comment](https://github.com/valkey-io/valkey/pull/1401/commits/6bc64ca5373a9c53a65529ac3999a87e327f7d03#r1875844273).

Signed-off-by: Ray Cao <zisong.cw@alibaba-inc.com>
2025-03-18 11:51:02 +01:00
BinbinandViktor Söderqvist c8687e7326 Simplify isNodeAvailable function and add comments (#994)
Use getNodeReplicationOffset to replace the original logic
and add the corresponding annotations.

Signed-off-by: Binbin <binloveplay1314@qq.com>
2025-03-18 11:51:02 +01:00
04e7c62ae6 Apply better sorting to the release notes thank-you section (#1735)
Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
Signed-off-by: ranshid <ranshid@amazon.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2025-02-16 08:50:51 +02:00
ranshidandGitHub 0010dc7d3c Valkey release 8.1.0-rc1 (#1713)
This is the PR for 8.1.0 release. 
It includes: 
1. Release note changes
2. version changes

---------

Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
2025-02-14 08:49:17 +02:00
119 changed files with 3235 additions and 1603 deletions
@@ -1,44 +0,0 @@
name: Generate target matrix.
description: Matrix creation for building Valkey for different architectures and platforms.
inputs:
ref:
description: The commit, tag or branch of Valkey to checkout to determine what version to use.
required: true
outputs:
x86_64-build-matrix:
description: The x86_64 build matrix.
value: ${{ steps.set-matrix.outputs.x86matrix }}
arm64-build-matrix:
description: The arm64 build matrix.
value: ${{ steps.set-matrix.outputs.armmatrix }}
runs:
using: "composite"
steps:
- name: Checkout code for version check
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
path: version-check
- name: Get targets
run: |
x86_arch=$(jq -c '[.linux_targets[] | select(.arch=="x86_64")]' .github/actions/generate-package-build-matrix/build-config.json)
x86_matrix=$(echo "{ \"distro\" : $x86_arch }" | jq -c .)
echo "X86_MATRIX=$x86_matrix" >> $GITHUB_ENV
arm_arch=$(jq -c '[.linux_targets[] | select(.arch=="arm64")]' .github/actions/generate-package-build-matrix/build-config.json)
arm_matrix=$(echo "{ \"distro\" : $arm_arch }" | jq -c .)
echo "ARM_MATRIX=$arm_matrix" >> $GITHUB_ENV
shell: bash
- id: set-matrix
run: |
echo $X86_MATRIX
echo $X86_MATRIX| jq .
echo "x86matrix=$X86_MATRIX" >> $GITHUB_OUTPUT
echo $ARM_MATRIX
echo $ARM_MATRIX| jq .
echo "armmatrix=$ARM_MATRIX" >> $GITHUB_OUTPUT
shell: bash
@@ -1,35 +0,0 @@
{
"linux_targets": [
{
"arch": "x86_64",
"target": "ubuntu-20.04",
"type": "deb",
"platform": "focal"
},
{
"arch": "x86_64",
"target": "ubuntu-22.04",
"type": "deb",
"platform": "jammy"
},
{
"arch": "x86_64",
"target": "ubuntu-24.04",
"type": "deb",
"platform": "noble"
},
{
"arch": "arm64",
"target": "ubuntu20.04",
"type": "deb",
"platform": "focal"
},
{
"arch": "arm64",
"target": "ubuntu22.04",
"type": "deb",
"platform": "jammy"
}
]
}
@@ -0,0 +1,31 @@
name: 'Install TCL8'
description: 'Installs tcl8 and tcltls from source on Fedora-based or uses the system package on others.'
inputs:
matrix_name:
description: 'The name of the matrix to check for fedora'
required: true
runs:
using: "composite"
steps:
# Fedora 42 comes with Tcl 9 by default, but tcltls is currently incompatible with Tcl 9.
# As a workaround, we install Tcl 8 and manually build tcltls 1.7.22 from source.
# Once tcltls adds support for Tcl 9, this logic can be removed and system packages used instead.
- run: |
if [[ "${{ inputs.matrix_name }}" =~ "fedora" ]]; then
dnf -y install tcl8 tcl8-devel gcc make awk openssl openssl-devel
ln -s /usr/bin/tclsh8.6 /usr/bin/tclsh
curl -LO https://core.tcl-lang.org/tcltls/uv/tcltls-1.7.22.tar.gz
tar -xzf tcltls-1.7.22.tar.gz
pushd tcltls-1.7.22
./configure --with-tcl=/usr/lib64/tcl8.6
make
mkdir -p /usr/lib64/tcl8.6/tls1.7.22
cp tcltls.so pkgIndex.tcl /usr/lib64/tcl8.6/tls1.7.22/
popd
else
dnf -y install tcl tcltls
fi
./utils/gen-test-certs.sh
shell: bash
@@ -1,112 +0,0 @@
name: Build Release Packages
on:
release:
types: [published]
push:
paths:
- '.github/workflows/build-release-packages.yml'
- '.github/workflows/call-build-linux-arm-packages.yml'
- '.github/workflows/call-build-linux-x86-packages.yml'
- '.github/actions/generate-package-build-matrix/build-config.json'
workflow_dispatch:
inputs:
version:
description: Version of Valkey to build
required: true
permissions:
id-token: write
contents: read
jobs:
# This job provides the version metadata from the tag for the other jobs to use.
release-build-get-meta:
name: Get metadata to build
if: github.event_name == 'workflow_dispatch' || github.repository == 'valkey-io/valkey'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.VERSION }}
is_test: ${{ steps.check-if-testing.outputs.IS_TEST }}
steps:
- run: |
echo "Version: ${{ inputs.version || github.ref_name }}"
shell: bash
# This step is to consolidate the three different triggers into a single "version"
# 1. If manual dispatch - use the version provided.
# 3. If tag trigger, use that tag.
- name: Get the version
id: get_version
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
VERSION=${{ github.ref_name }}
else
VERSION="${INPUT_VERSION}"
fi
if [ -z "${VERSION}" ]; then
echo "Error: No version specified"
exit 1
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
shell: bash
env:
# Use the dispatch variable in preference, if empty use the context ref_name which should
# only ever be a tag
INPUT_VERSION: ${{ inputs.version || github.ref_name }}
- name: Check if we are testing
id: check-if-testing
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
echo "This is a test workflow -> We will upload to the Test S3 Bucket"
echo "IS_TEST=true" >> $GITHUB_OUTPUT
else
echo "This is a Release workflow -> We will upload to the Release S3 Bucket"
echo "IS_TEST=false" >> $GITHUB_OUTPUT
fi
shell: bash
generate-build-matrix:
name: Generating build matrix
if: github.event_name == 'workflow_dispatch' || github.repository == 'valkey-io/valkey'
runs-on: ubuntu-latest
outputs:
x86_64-build-matrix: ${{ steps.set-matrix.outputs.x86_64-build-matrix }}
arm64-build-matrix: ${{ steps.set-matrix.outputs.arm64-build-matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Set up the list of target to build so we can pass the JSON to the reusable job
- uses: ./.github/actions/generate-package-build-matrix
id: set-matrix
with:
ref: ${{ needs.release-build-get-meta.outputs.version }}
release-build-linux-x86-packages:
needs:
- release-build-get-meta
- generate-build-matrix
uses: ./.github/workflows/call-build-linux-x86-packages.yml
with:
version: ${{ needs.release-build-get-meta.outputs.version }}
ref: ${{ inputs.version || github.ref_name }}
build_matrix: ${{ needs.generate-build-matrix.outputs.x86_64-build-matrix }}
region: us-west-2
secrets:
bucket_name: ${{ needs.release-build-get-meta.outputs.is_test == 'true' && secrets.AWS_S3_TEST_BUCKET || secrets.AWS_S3_BUCKET }}
role_to_assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
release-build-linux-arm-packages:
needs:
- release-build-get-meta
- generate-build-matrix
uses: ./.github/workflows/call-build-linux-arm-packages.yml
with:
version: ${{ needs.release-build-get-meta.outputs.version }}
ref: ${{ inputs.version || github.ref_name }}
build_matrix: ${{ needs.generate-build-matrix.outputs.arm64-build-matrix }}
region: us-west-2
secrets:
bucket_name: ${{ needs.release-build-get-meta.outputs.is_test == 'true' && secrets.AWS_S3_TEST_BUCKET || secrets.AWS_S3_BUCKET }}
role_to_assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
@@ -1,74 +0,0 @@
name: Builds Linux arm binary packages into S3 bucket.
on:
workflow_call:
inputs:
version:
description: The version of Valkey to create.
type: string
required: true
ref:
description: The commit, tag or branch of Valkey to checkout for building that creates the version above.
type: string
required: true
build_matrix:
description: The build targets to produce as a JSON matrix.
type: string
required: true
region:
description: The AWS region to push packages into.
type: string
required: true
secrets:
bucket_name:
description: The S3 bucket to push packages into.
required: true
role_to_assume:
description: The role to assume for the S3 bucket.
required: true
permissions:
id-token: write
contents: read
jobs:
build-valkey:
# Capture source tarball and generate checksum for it
name: Build package ${{ matrix.distro.target }} ${{ matrix.distro.arch }}
runs-on: "ubuntu-latest"
strategy:
fail-fast: false
matrix: ${{ fromJSON(inputs.build_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.version }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ inputs.region }}
role-to-assume: ${{ secrets.role_to_assume }}
- name: Make Valkey
uses: uraimo/run-on-arch-action@v2
with:
arch: aarch64
distro: ${{matrix.distro.target}}
install: apt-get update && apt-get install -y build-essential libssl-dev libsystemd-dev
run: make -C src all BUILD_TLS=yes USE_SYSTEMD=yes
- name: Create Tarball and SHA256sums
run: |
TAR_FILE_NAME=valkey-${{inputs.version}}-${{matrix.distro.platform}}-${{ matrix.distro.arch}}
mkdir -p "$TAR_FILE_NAME/bin" "$TAR_FILE_NAME/share"
rsync -av --exclude='*.c' --exclude='*.d' --exclude='*.o' src/valkey-* "$TAR_FILE_NAME/bin/"
cp -v /home/runner/work/valkey/valkey/COPYING "$TAR_FILE_NAME/share/LICENSE"
tar -czvf $TAR_FILE_NAME.tar.gz $TAR_FILE_NAME
sha256sum $TAR_FILE_NAME.tar.gz > $TAR_FILE_NAME.tar.gz.sha256
mkdir -p packages-files
cp -rfv $TAR_FILE_NAME.tar* packages-files/
- name: Sync to S3
run: aws s3 sync packages-files s3://${{ secrets.bucket_name }}/releases/
@@ -1,72 +0,0 @@
name: Builds Linux X86 binary packages into S3 bucket.
on:
workflow_call:
inputs:
version:
description: The version of Valkey to create.
type: string
required: true
ref:
description: The commit, tag or branch of Valkey to checkout for building that creates the version above.
type: string
required: true
build_matrix:
description: The build targets to produce as a JSON matrix.
type: string
required: true
region:
description: The AWS region to upload the packages to.
type: string
required: true
secrets:
bucket_name:
description: The name of the S3 bucket to upload the packages to.
required: true
role_to_assume:
description: The role to assume for the S3 bucket.
required: true
permissions:
id-token: write
contents: read
jobs:
build-valkey:
# Capture source tarball and generate checksum for it
name: Build package ${{ matrix.distro.target }} ${{ matrix.distro.arch }}
runs-on: ${{matrix.distro.target}}
strategy:
fail-fast: false
matrix: ${{ fromJSON(inputs.build_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.version }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ inputs.region }}
role-to-assume: ${{ secrets.role_to_assume }}
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y build-essential libssl-dev libsystemd-dev
- name: Make Valkey
run: make -C src all BUILD_TLS=yes USE_SYSTEMD=yes
- name: Create Tarball and SHA256sums
run: |
TAR_FILE_NAME=valkey-${{inputs.version}}-${{matrix.distro.platform}}-${{ matrix.distro.arch}}
mkdir -p "$TAR_FILE_NAME/bin" "$TAR_FILE_NAME/share"
rsync -av --exclude='*.c' --exclude='*.d' --exclude='*.o' src/valkey-* "$TAR_FILE_NAME/bin/"
cp -v /home/runner/work/valkey/valkey/COPYING "$TAR_FILE_NAME/share/LICENSE"
tar -czvf $TAR_FILE_NAME.tar.gz $TAR_FILE_NAME
sha256sum $TAR_FILE_NAME.tar.gz > $TAR_FILE_NAME.tar.gz.sha256
mkdir -p packages-files
cp -rfv $TAR_FILE_NAME.tar* packages-files/
- name: Sync to S3
run: aws s3 sync packages-files s3://${{ secrets.bucket_name }}/releases/
+100 -15
View File
@@ -12,10 +12,10 @@ on:
inputs:
skipjobs:
description: "jobs to skip (delete the ones you wanna keep, do not leave empty)"
default: "valgrind,sanitizer,tls,freebsd,macos,alpine,32bit,iothreads,ubuntu,rpm-distros,malloc,specific,fortify,reply-schema"
default: "valgrind,sanitizer,tls,freebsd,macos,alpine,32bit,iothreads,ubuntu,rpm-distros,malloc,specific,fortify,reply-schema,arm"
skiptests:
description: "tests to skip (delete the ones you wanna keep, do not leave empty)"
default: "valkey,modules,sentinel,cluster,unittest"
default: "valkey,modules,sentinel,cluster,unittest,large-memory"
test_args:
description: "extra test arguments"
default: ""
@@ -79,6 +79,48 @@ jobs:
if: true && !contains(github.event.inputs.skiptests, 'unittest')
run: ./src/valkey-unit-tests --accurate
test-ubuntu-arm:
runs-on: ubuntu-24.04-arm
if: |
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'schedule' && github.repository == 'valkey-io/valkey') ||
(github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'run-extra-tests') || github.event.pull_request.base.ref != 'unstable'))) &&
(!contains(github.event.inputs.skipjobs, 'ubuntu') || !contains(github.event.inputs.skipjobs, 'arm'))
timeout-minutes: 14400
steps:
- name: prep
if: github.event_name == 'workflow_dispatch'
run: |
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
echo "skipjobs: ${{github.event.inputs.skipjobs}}"
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: make all-with-unit-tests SERVER_CFLAGS='-Werror'
- name: testprep
run: sudo apt-get install tcl8.6 tclx
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: ./runtest ${{ github.event_name != 'pull_request' && '--accurate' || '' }} --verbose --dump-logs ${{github.event.inputs.test_args}}
- name: module api test
if: true && !contains(github.event.inputs.skiptests, 'modules')
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
- name: sentinel tests
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
- name: cluster tests
if: true && !contains(github.event.inputs.skiptests, 'cluster')
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
- name: unittest
if: true && !contains(github.event.inputs.skiptests, 'unittest')
run: ./src/valkey-unit-tests --accurate
test-ubuntu-jemalloc-fortify:
runs-on: ubuntu-latest
if: |
@@ -675,7 +717,13 @@ jobs:
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
- name: unittest
if: true && !contains(github.event.inputs.skiptests, 'unittest')
run: ./src/valkey-unit-tests
run: ./src/valkey-unit-tests --large-memory
- name: large memory tests
if: true && !contains(github.event.inputs.skiptests, 'valkey') && !contains(github.event.inputs.skiptests, 'large-memory')
run: ./runtest --accurate --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
- name: large memory module api tests
if: true && !contains(github.event.inputs.skiptests, 'modules') && !contains(github.event.inputs.skiptests, 'large-memory')
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
test-sanitizer-undefined:
runs-on: ubuntu-latest
@@ -725,7 +773,13 @@ jobs:
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
- name: unittest
if: true && !contains(github.event.inputs.skiptests, 'unittest')
run: ./src/valkey-unit-tests --accurate
run: ./src/valkey-unit-tests --accurate --large-memory
- name: large memory tests
if: true && !contains(github.event.inputs.skiptests, 'valkey') && !contains(github.event.inputs.skiptests, 'large-memory')
run: ./runtest --accurate --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
- name: large memory module api tests
if: true && !contains(github.event.inputs.skiptests, 'modules') && !contains(github.event.inputs.skiptests, 'large-memory')
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
test-sanitizer-force-defrag:
runs-on: ubuntu-latest
@@ -822,10 +876,12 @@ jobs:
run: dnf -y install epel-release
- name: make
run: |
dnf -y install gcc make procps-ng which /usr/bin/kill
dnf -y install gcc make procps-ng openssl-devel openssl which /usr/bin/kill /usr/bin/awk
make -j SERVER_CFLAGS='-Werror'
- name: testprep
run: dnf -y install tcl tcltls
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: ./runtest ${{ github.event_name != 'pull_request' && '--accurate' || '' }} --verbose --dump-logs ${{github.event.inputs.test_args}}
@@ -888,12 +944,12 @@ jobs:
run: dnf -y install epel-release
- name: make
run: |
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
- name: testprep
run: |
dnf -y install tcl tcltls
./utils/gen-test-certs.sh
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: |
@@ -960,12 +1016,12 @@ jobs:
run: dnf -y install epel-release
- name: make
run: |
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
- name: testprep
run: |
dnf -y install tcl tcltls
./utils/gen-test-certs.sh
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: |
@@ -1262,7 +1318,36 @@ jobs:
notify-about-job-results:
runs-on: ubuntu-latest
if: always() && github.event_name == 'schedule' && github.repository == 'valkey-io/valkey'
needs: [test-ubuntu-jemalloc, test-ubuntu-jemalloc-fortify, test-ubuntu-libc-malloc, test-ubuntu-no-malloc-usable-size, test-ubuntu-32bit, test-ubuntu-tls, test-ubuntu-tls-no-tls, test-ubuntu-io-threads, test-ubuntu-tls-io-threads, test-ubuntu-reclaim-cache, test-valgrind-test, test-valgrind-misc, test-valgrind-no-malloc-usable-size-test, test-valgrind-no-malloc-usable-size-misc, test-sanitizer-address, test-sanitizer-undefined, test-sanitizer-force-defrag, test-rpm-distros-jemalloc, test-rpm-distros-tls-module, test-rpm-distros-tls-module-no-tls, test-macos-latest, test-macos-latest-sentinel, test-macos-latest-cluster, build-macos, test-freebsd, test-alpine-jemalloc, test-alpine-libc-malloc, reply-schemas-validator]
needs:
- test-ubuntu-jemalloc
- test-ubuntu-arm
- test-ubuntu-jemalloc-fortify
- test-ubuntu-libc-malloc
- test-ubuntu-no-malloc-usable-size
- test-ubuntu-32bit
- test-ubuntu-tls
- test-ubuntu-tls-no-tls
- test-ubuntu-io-threads
- test-ubuntu-tls-io-threads
- test-ubuntu-reclaim-cache
- test-valgrind-test
- test-valgrind-misc
- test-valgrind-no-malloc-usable-size-test
- test-valgrind-no-malloc-usable-size-misc
- test-sanitizer-address
- test-sanitizer-undefined
- test-sanitizer-force-defrag
- test-rpm-distros-jemalloc
- test-rpm-distros-tls-module
- test-rpm-distros-tls-module-no-tls
- test-macos-latest
- test-macos-latest-sentinel
- test-macos-latest-cluster
- build-macos
- test-freebsd
- test-alpine-jemalloc
- test-alpine-libc-malloc
- reply-schemas-validator
steps:
- name: Collect job status
run: |
@@ -0,0 +1,50 @@
name: Trigger Build Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: Version of Valkey to build
required: true
environment:
description: Environment to build
required: true
type: choice
options:
- dev
- prod
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Determine version and environment
id: determine-vars
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
echo "Triggered by a release event."
VERSION=${{ github.event.release.tag_name }}
ENVIRONMENT="prod"
else
echo "Triggered manually (workflow_dispatch)."
VERSION=${{ inputs.version }}
ENVIRONMENT=${{ inputs.environment }}
fi
# Set the outputs for version and environment
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
- name: Trigger build
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # Version 3
with:
token: ${{ secrets.AUTOMATION_PAT }}
repository: ${{ github.repository_owner }}/valkey-release-automation
event-type: build-release
client-payload: >
{
"version": "${{ steps.determine-vars.outputs.version }}",
"environment": "${{ steps.determine-vars.outputs.environment }}"
}
+219 -11
View File
@@ -1,16 +1,224 @@
Hello! This file is just a placeholder, since this is the "unstable" branch
of Valkey, the place where all the development happens.
Valkey 8.1 release notes
========================
--------------------------------------------------------------------------------
Upgrade urgency levels:
LOW: No need to upgrade unless there are new features you want to use.
MODERATE: Program an upgrade of the server, but it's not urgent.
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release.
--------------------------------------------------------------------------------
There is no release notes for this branch, it gets forked into another branch
every time there is a partial feature freeze in order to eventually create
a new stable release.
================================================================================
Valkey 8.1.1 - Released Wed 23 Apr 2025
================================================================================
Usually "unstable" is stable enough for you to use it in development environments
however you should never use it in production environments. It is possible
to download the latest stable release here:
Upgrade urgency SECURITY: This release includes security fixes we recommend you
apply as soon as possible.
https://valkey.io/download/
Bug fixes
=========
* Fix the build on less common platforms in zmalloc.c (#1922)
* fix: add samples to stream object consumer trees (#1825)
* Fix crash during TLS handshake with I/O threads (#1955)
* Fix cluster slot stats assertion during promotion of replica (#1950)
* Fix panic in primary when blocking shutdown after previous block with timeout (#1948)
* Ignore stale gossip packets that arrive out of order (#1777)
* Fix incorrect lag reported in XINFO GROUPS (#1952)
* Fix engine crash on module client blocking during keyspace events (#1819)
* Avoid shard id update of replica if not matching with primary shard id (#573)
* Only enable defrag for vendored jemalloc (#1985)
* Allow scripts to support null characters again (#1984)
More information is available at https://valkey.io
Security fixes
==============
* CVE-2025-21605 Limit output buffer for unauthenticated clients (#1994)
Happy hacking!
================================================================================
Valkey 8.1.0 GA - Released Mon 31 March 2025
================================================================================
Upgrade urgency LOW: This is the first release of Valkey 8.1,
a minor version update designed to further enhance performance, reliability, observability and usability
over Valkey 8.0 for all Valkey installations. This release is fully compatible with all previous Valkey releases
as well as Redis OSS 7.2.4.
Behavior Changes
================
* Hide input buffer data from being logged on protocol error when hide-user-data-from-log is enabled (#1889)
Bug fixes
=========
* Fix a bug in VM_GetCurrentUserName which leads to engine crash when no valid username provided (#1885)
================================================================================
Valkey 8.1.0 RC2 - Released Thu 20 March 2025
================================================================================
Upgrade urgency LOW: This is the second release candidate of Valkey 8.1, with several bug fixes,
control on manual-failover timeout and expended module API to reduce module executed commands overhead.
Performance/Efficiency Improvements - Core
==========================================
* Optimize bitcount command by using x86 SIMD instructions (#1741)
* Embed hash value in hash data type entries to reduce memory footprint (#1579)
Cluster modifications
====================
* Add cluster-manual-failover-timeout configuration to control the timeout for manual failover (#1690)
* Improve error message reporting when invalid port is provided for cluster meet command. (#1686)
* broadcast epoch ASAP when configEpoch changed (#1813)
Module Improvements
===================
* Add new module API flag to bypass command validation in order to reduce processing overhead (#1357)
Behavior Changes
================
* Enable TCP_NODELAY for engine initiated cluster and replication connections (#1763)
Bug Fixes
=========
* Fix `ACL LOAD` crash on a connected replica node (#1842)
* Fix bug where no tracking-redir-broken is issued when the redirect client is in the process of getting closed. (#1823)
* Fix replica sometimes disconnecting when replication is using TLS. (#1737)
* Fix file descriptor leak when aborting dual channel replication due to error (#1721)
* Fix rax crash when using keys larger than 512MB (#1722)
* Fix RANDOMKEY command leading to infinite loop during when all CLIENT are PAUSED and all keys are with expiry (#1850)
* Removing unicode optimization in Lua cjson library to avoid OOM when very large strings are used. (#1785)
* Fix update large-reply in COMMANDLOG when reply is deferred (#1760)
* Avoid setting TCP/TLS specific options for UNIX Domain Socket connections (#1706)
* Fix a bug in the valkey-cli which would incorrectly render commands with text output in multi/exec (#1782)
Build and Packaging changes
=================================
* Check both arm64 and aarch64 for ARM based system architecture during CMake builds (#1829)
* Cleanup lua object files on make distclean (#1812)
* Fixed build error with CMake when using clang v19 (#1806)
================================================================================
Valkey 8.1.0 RC1 - Released Thu 11 Feb 2025
================================================================================
Upgrade urgency LOW: This is the first release candidate of Valkey 8.1, with
performance improvements, extended observability and cluster improvements and different bug fixes.
It includes a new implementation of the Valkey dictionary which is more memory and cache efficient,
better performance for encryption in transit, reduced replication overhead by offloading work to I/O threads,
faster failover support in cluster mode, major improvements to the active defrag process to reduce the impact on command processing,
different API changes for improved usability and ability to track large requests and replies.
Valkey now supports new new check-and-set feature for native STRINGs.
API and Interface changes
=========================
* Introduce cancel argument to bgsave command (#757)
* Add conditional update support to the `SET` command using `IFEQ` argument (#1324)
* Add more filters to `CLIENT LIST` (#1401)
* Add `availability_zone` to the HELLO response (#1487)
Observability and Monitoring changes
====================================
* Extend `LATENCY LATEST` to add sum / cnt stats (#1570)
* Add `paused_actions` and `paused_timeout_milliseconds` for `INFO CLIENTS` (#1519)
* Add paused_reason to `INFO CLIENTS` (#1564)
* Added `COMMANDLOG` to record slow executions and large requests/replies (#1294)
* Fix cluster info sent stats for message with light header (#1563)
* Add latency stats around cluster config file operations (#1534)
* Add new flag in `CLIENT LIST` for import-source client (#1398)
* Show client capabilities in `CLIENT LIST` / `CLIENT INFO` (#1698)
Performance/Efficiency Improvements - Core
==========================================
* Introduce a new memory efficient hash table to store keys (#1186)
* Accelerate hash table iterator with prefetching (#1501)
* Accelerate hash table iterator with value prefetching (#1568)
* Replace dict with new hashtable: hash datatype (#1502)
* Replace dict with new hashtable for sets datatype (#1176)
* Replace dict with new hashtable: sorted set datatype (#1427)
* Free strings during BGSAVE/BGAOFRW to reduce copy-on-write (#905)
* Create an empty lua table with specified initial capacity as much as possible (#1092)
* Move prepareClientToWrite out of loop for HGETALL command (#1119)
* Improved hashing algorithm for Lua tables (#1168)
* Replace dict with new hashtable for sets datatype (#1176)
* Do security attack check only when command not found to reduce the critical path. (#1212)
* Trim free space from inline command argument strings to avoid excess memory usage (#1213)
* Increase the max number of io threads to 256. (#1220)
* Refactor of ActiveDefrag to reduce latencies (#1242)
* Integrate fast_float to optionally replace strtod (#1260)
* Improvements for TLS with I/O threads (#1271)
* Optimize PFCOUNT, PFMERGE command by SIMD acceleration (#1293)
* Optimize sdscatrepr by batch processing printable characters (#1342)
* Optimize ZRANK to avoid path comparisons (#1389)
* Move clientCron onto a separate timer (#1387)
* Client struct: lazy init components and optimize struct layout (#1405)
* Offload reading the replication stream to IO threads (#1449)
* Skip CRC checksumming during diskless full sync with TLS enabled. (#1479)
New/Modified configurations
===========================
* Deprecate `io-threads-do-reads`, which has no effect since io threads will now always do reads. (#1138)
* Introduce `import-mode` config to avoid expiration and eviction during data syncing (#1185)
* Introduce new `rdb-version-check` config which allows for relaxed RDB version verification (#1604)
* Deprecate `dynamic-hz`, since server cron jobs are handled dynamically by default (#1387)
* Introduce `log-format` and `log-timestamp-format` to control the log format (#1022)
* Introducing `active-defrag-cycle-us` for more fine-grinned control of memory defragmentation run time (#1242)
* Introduce new configurations to control the new `COMMANDLOG` reporting thresholds (#1294)
Build and Packaging changes
=================================
* Introduce CMake build system for valkey (#1196)
* RDMA builtin support (#1209)
* Fix Valkey binary build workflow, version support changes. (#1429)
* Remove Valkey specific changes in jemalloc source code (#1266)
Module Improvements
===================
* Add API UpdateRuntimeArgs for updating the module arguments during runtime (#1041)
* Add support for MustObeyClient Module API (#1582)
* Adds support for scripting engines as Valkey modules (#1277, #1497)
Cluster improvements
====================
* Do election in order based on failed primary rank to avoid voting conflicts (#1018)
* Make replica `CLUSTER RESET` flush async based on `lazyfree-lazy-user-flush` (#1190)
* Trigger the election as soon as possible when doing a forced manual failover (#1067)
* Make manual failover reset the on-going election to promote failover (#1274)
* Broadcast a PONG to all node in cluster when role changed (#1295)
* Manual failover vote is not limited by two times the node timeout (#1305)
* Automatic failover vote is not limited by two times the node timeout (#1356)
Behavior Changes
================
* Streams use an additional 8 bytes to track their internal size (#688)
* Take hz into account in activerehashing to avoid CPU spikes (#977)
* Incr `expired_keys` if the expiration time is already expired (#1517)
* Fix replica not able to initiate election in time when epoch fails (#1009)
* Make `FUNCTION RESTORE FLUSH` flush async based on `lazyfree-lazy-user-flush` (#1254)
* Allow `MEMORY MALLOC-STATS` and `MEMORY PURGE` during loading phase (#1317)
* Use `DEEPBIND` flag when loading external modules in order to avoid symbol conflicts (#1703)
Logging and Tooling Improvements
================================
* Remove the restriction that cli --cluster create requires at least 3 primary nodes (#1075)
* Add short client info log to CLUSTER MEET / FORGET / RESET commands (#1249)
* Support for reading from replicas in valkey-benchmark (#1392)
* valkey-cli will now re-select previously selected database after reconnect (#1694)
* valkey-cli will now auto-exit from subscribed mode when there are no more active subscriptions (#1432)
Bug Fixes
=========
* Mark the node as FAIL when the node is marked as NOADDR and broadcast the FAIL (#1191)
* [Bug Fix] Optimize RDB Load Performance and Fix Cluster Mode Resizing (#1199)
* Log as primary role (M) instead of child process (C) during startup (#1282)
* Fix empty primary may have dirty slots data due to bad migration (#1285)
* RDMA: Fix dead loop when transfer large data (20KB) (#1386)
We appreciate the efforts of all who contributed code to this release!
Alan Scherger (flyinprogrammer), Amit Nagler (naglera), Anastasia Alexandrova (nastena1606), Basel Naamna (xbasel), Ben Totten (bentotten), Benson-li (li-benson),
Binbin (enjoy-binbin), Bogdan Petre (bogdanp05), chzhoo, Caiyi Wu (Codebells), Danish Mehmood (danish-mehmood), Eran Ifrah (eifrah-aws), Guillaume Koenig (knggk),
Harkrishn Patro (hpatro), Jacob Murphy (murphyjacob4), jeon1226, Jim Brunner (JimB123), Josef Šimánek (simi), Jungwoo Song (bluayer), Karthick Ariyaratnam (karthyuom),
Karthik Subbarao (KarthikSubbarao), kronwerk, Lipeng Zhu (lipzhu), Madelyn Olson (madolson), Marek Zoremba (zori-janea), Masahiro Ide (imasahiro), Meinhard Zhou (MeinhardZhou), Melroy van den Berg (melroy89), Mikhail Koviazin (mkmkme), Nadav Gigi (NadavGigi), Nadav Levanoni (nadav-levanoni), Nikhil Manglore (Nikhil-Manglore), Parth Patel (parthpatel), Pierre (pieturin), Ping Xie (PingXie), Qu Chen (QuChen88), Rain Valentine (SoftlyRaining), Ray Cao (RayaCoo), Ran Shidlansik (ranshid), Ray Cao (RayaCoo), Ricardo Dias (rjd15372), Romain Geissler (Romain-Geissler-1A), Roman Gershman (romange), Roshan Khatri (roshkhatri), Rueian (rueian), Sarthak Aggarwal (sarthakaggarwal97), Seungmin Lee (sungming2),
Shai Zarka (zarkash-aws), Shivshankar (Shivshankar-Reddy), Simon Baatz (gmbnomis), Sinkevich Artem (ArtSin), Stav Ben-Tov (stav-bentov), Stefan Mueller (muelstefamzn), secwall,
Tal Shachar (talxsha), Thalia Archibald (thaliaarchi), Uri Yagelnik (uriyage), Vadym Khoptynets (poiuj), Vanessa Tang (YueTang-Vanessa), Viktor Söderqvist (zuiderkwast), Viktor Szépe (szepeviktor), VoletiRam,
Vu Diep (vudiep411), Wen Hui (hwware), WelongZuo, Yanqi Lv (lyq2333), Yury Fridlyand (Yury-Fridlyand), Zvi Schneider (zvi-code), bodong.ybd (yangbodong22011), chx9 (chx9), otheng (otheng03), skyfirelee (artikell), Shawn Wang (xingbowang), Xuyang WANG (Nugine), zhaozhao.zz (soloestoy), zhenwei pi (pizhenwei), zixuan zhao (azuredream), 烈香 (hengyoush),
风去幽墨 (fengquyoumo)
+2
View File
@@ -1,5 +1,7 @@
# License 1
SPDX-License-Identifier: BSD-3-Clause
BSD 3-Clause License
Copyright (c) 2024-present, Valkey contributors
+1 -1
View File
@@ -240,7 +240,7 @@ endif ()
set(BUILDING_ARM64 0)
set(BUILDING_ARM32 0)
if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64")
if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
set(BUILDING_ARM64 1)
endif ()
+2 -2
View File
@@ -1,7 +1,7 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "../fast_float/fast_float.h"
+5
View File
@@ -5,6 +5,11 @@ objroot=$1
cat <<EOF
#ifndef JEMALLOC_H_
#define JEMALLOC_H_
/* A macro that is used to indicate that this the jemalloc vendored with the project
* and has been tested with active defragmentation. */
#define VALKEY_VENDORED_JEMALLOC 1
#ifdef __cplusplus
extern "C" {
#endif
+3 -3
View File
@@ -81,9 +81,9 @@ uint32_t murmur32(const uint8_t* key, size_t len, uint32_t seed) {
static const uint32_t n = 0xe6546b64;
uint32_t hash = seed;
const int nblocks = len / 4;
const size_t nblocks = len / 4;
const uint32_t* blocks = (const uint32_t*) key;
for (int i = 0; i < nblocks; i++) {
for (size_t i = 0; i < nblocks; i++) {
uint32_t k = blocks[i];
k *= c1;
k = (k << r1) | (k >> (32 - r1));
@@ -116,7 +116,7 @@ uint32_t murmur32(const uint8_t* key, size_t len, uint32_t seed) {
hash ^= (hash >> 16);
return hash;
}
}
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
GCObject *o;
+4 -8
View File
@@ -469,13 +469,9 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
str = lua_tolstring(l, lindex, &len);
/* Worst case is len * 6 (all unicode escapes).
* This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */
if (len > SIZE_MAX / 6 - 3)
if (len > SIZE_MAX - 3)
abort(); /* Overflow check */
strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_ensure_empty_length(json, len + 2);
strbuf_append_char_unsafe(json, '\"');
for (i = 0; i < len; i++) {
@@ -483,9 +479,9 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
if (escstr)
strbuf_append_string(json, escstr);
else
strbuf_append_char_unsafe(json, str[i]);
strbuf_append_char(json, str[i]);
}
strbuf_append_char_unsafe(json, '\"');
strbuf_append_char(json, '\"');
}
/* Find the size of the array on the top of the Lua stack
+1 -1
View File
@@ -563,7 +563,7 @@ endif
commands.c: $(COMMANDS_DEF_FILENAME).def
clean:
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d lua/*.o lua/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
rm -f $(DEP)
.PHONY: clean
+6 -4
View File
@@ -495,8 +495,7 @@ void ACLFreeUserAndKillClients(user *u) {
* this may result in some security hole: it's much
* more defensive to set the default user and put
* it in non authenticated mode. */
c->user = DefaultUser;
c->flag.authenticated = 0;
clientSetUser(c, DefaultUser, 0);
/* We will write replies to this client later, so we can't
* close it directly even if async. */
if (c == server.current_client) {
@@ -1455,8 +1454,8 @@ void addAuthErrReply(client *c, robj *err) {
* The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
static int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
if (ACLCheckUserCredentials(username, password) == C_OK) {
c->flag.authenticated = 1;
c->user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
user *user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
clientSetUser(c, user, 1);
moduleNotifyUserChanged(c);
return AUTH_OK;
} else {
@@ -2377,6 +2376,9 @@ static sds ACLLoadFromFile(const char *filename) {
listRewind(server.clients, &li);
while ((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
/* Some clients, e.g. the one from the primary to replica, don't have a user
* associated with them. */
if (!c->user) continue;
user *original = c->user;
list *channels = NULL;
user *new_user = ACLGetUserByName(c->user->name, sdslen(c->user->name));
+16 -5
View File
@@ -31,6 +31,8 @@
#include <stdlib.h>
#include "adlist.h"
#include "serverassert.h"
#include "zmalloc.h"
/* Create a new list. The created list can be freed with
@@ -53,6 +55,7 @@ list *listCreate(void) {
/* Remove all the elements from the list without destroying the list itself. */
void listEmpty(list *list) {
if (!list) return;
unsigned long len;
listNode *current, *next;
@@ -72,7 +75,6 @@ void listEmpty(list *list) {
*
* This function can't fail. */
void listRelease(list *list) {
if (!list) return;
listEmpty(list);
zfree(list);
}
@@ -187,14 +189,23 @@ void listDelNode(list *list, listNode *node) {
* Remove the specified node from the list without freeing it.
*/
void listUnlinkNode(list *list, listNode *node) {
if (node->prev)
assert(list->len > 0);
if (node->prev) {
assert(node->prev->next == node);
node->prev->next = node->next;
else
} else {
assert(list->head == node);
list->head = node->next;
if (node->next)
}
if (node->next) {
assert(node->next->prev == node);
node->next->prev = node->prev;
else
} else {
assert(list->tail == node);
list->tail = node->prev;
}
node->next = NULL;
node->prev = NULL;
+2 -2
View File
@@ -1,8 +1,8 @@
/* Copyright 2024- Valkey contributors
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* This file implements allocator-specific defragmentation logic used
* within the Valkey engine. Below is the relationship between various
+1 -1
View File
@@ -5,7 +5,7 @@
#include <jemalloc/jemalloc.h>
/* We can enable the server defrag capabilities only if we are using Jemalloc
* and the version that has the experimental.utilization namespace in mallctl . */
#if (defined(JEMALLOC_VERSION_MAJOR) && \
#if (defined(VALKEY_VENDORED_JEMALLOC) && defined(JEMALLOC_VERSION_MAJOR) && \
(JEMALLOC_VERSION_MAJOR > 5 || \
(JEMALLOC_VERSION_MAJOR == 5 && JEMALLOC_VERSION_MINOR > 2) || \
(JEMALLOC_VERSION_MAJOR == 5 && JEMALLOC_VERSION_MINOR == 2 && JEMALLOC_VERSION_BUGFIX >= 1))) || \
+7
View File
@@ -496,6 +496,9 @@ static int anetTcpGenericConnect(char *err, const char *addr, int port, const ch
continue;
}
/* Enable TCP_NODELAY by default */
anetEnableTcpNoDelay(NULL, s);
/* If we ended an iteration of the for loop without errors, we
* have a connected socket. Let's return to the caller. */
goto end;
@@ -684,6 +687,10 @@ int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port)
if (ip) inet_ntop(AF_INET6, (void *)&(s->sin6_addr), ip, ip_len);
if (port) *port = ntohs(s->sin6_port);
}
/* Enable TCP_NODELAY by default */
anetEnableTcpNoDelay(NULL, fd);
return fd;
}
+5 -2
View File
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2009-2012, Redis Ltd.
* Copyright (c) 2024, Valkey contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -27,7 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
const char *ascii_logo =
" .+^+. \n"
" .+#########+. \n"
+119 -13
View File
@@ -29,26 +29,116 @@
*/
#include "server.h"
#ifdef HAVE_AVX2
/* Define __MM_MALLOC_H to prevent importing the memory aligned
* allocation functions, which we don't use. */
#define __MM_MALLOC_H
#include <immintrin.h>
#endif
/* -----------------------------------------------------------------------------
* Helpers and low level bit functions.
* -------------------------------------------------------------------------- */
/* 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) */
long long serverPopcount(void *s, long count) {
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};
#ifdef HAVE_AVX2
/* The SIMD version of popcount enhances performance through parallel lookup tables which is based on the following article:
* https://arxiv.org/pdf/1611.07612 */
ATTRIBUTE_TARGET_AVX2
long long popcountAVX2(void *s, long count) {
long i = 0;
unsigned char *p = (unsigned char *)s;
long long bits = 0;
/* clang-format off */
const __m256i lookup = _mm256_setr_epi8(
/* First Lane [0:127] */
/* 0 */ 0, /* 1 */ 1, /* 2 */ 1, /* 3 */ 2,
/* 4 */ 1, /* 5 */ 2, /* 6 */ 2, /* 7 */ 3,
/* 8 */ 1, /* 9 */ 2, /* a */ 2, /* b */ 3,
/* c */ 2, /* d */ 3, /* e */ 3, /* f */ 4,
/* Second Lane [128:255] identical to first lane due to lane isolation in _mm256_shuffle_epi8.
* For more information, see following URL
* https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_shuffle_epi8 */
/* 0 */ 0, /* 1 */ 1, /* 2 */ 1, /* 3 */ 2,
/* 4 */ 1, /* 5 */ 2, /* 6 */ 2, /* 7 */ 3,
/* 8 */ 1, /* 9 */ 2, /* a */ 2, /* b */ 3,
/* c */ 2, /* d */ 3, /* e */ 3, /* f */ 4);
/* clang-format on */
const __m256i low_mask = _mm256_set1_epi8(0x0f);
__m256i acc = _mm256_setzero_si256();
/* Count 32 bytes per iteration. */
#define ITER_32_BYTES \
{ \
const __m256i vec = _mm256_loadu_si256((const __m256i *)(p + i)); \
const __m256i lo = _mm256_and_si256(vec, low_mask); \
const __m256i hi = _mm256_and_si256(_mm256_srli_epi16(vec, 4), low_mask); \
const __m256i popcnt1 = _mm256_shuffle_epi8(lookup, lo); \
const __m256i popcnt2 = _mm256_shuffle_epi8(lookup, hi); \
local = _mm256_add_epi8(local, popcnt1); \
local = _mm256_add_epi8(local, popcnt2); \
i += 32; \
}
/* We divide the array into the following three parts
* Part A Part B Part C
* +-----------------+--------------+---------+
* | 8 * 32bytes * X | 32bytes * Y | Z bytes |
* +-----------------+--------------+---------+
*/
/* Part A: loop unrolling, processing 8 * 32 bytes per iteration. */
while (i + 8 * 32 <= count) {
__m256i local = _mm256_setzero_si256();
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
ITER_32_BYTES
acc = _mm256_add_epi64(acc, _mm256_sad_epu8(local, _mm256_setzero_si256()));
}
/* Part B: when the remaining data length is less than 8 * 32 bytes,
* process 32 bytes per iteration. */
__m256i local = _mm256_setzero_si256();
while (i + 32 <= count) {
ITER_32_BYTES;
}
acc = _mm256_add_epi64(acc, _mm256_sad_epu8(local, _mm256_setzero_si256()));
#undef ITER_32_BYTES
bits += _mm256_extract_epi64(acc, 0);
bits += _mm256_extract_epi64(acc, 1);
bits += _mm256_extract_epi64(acc, 2);
bits += _mm256_extract_epi64(acc, 3);
/* Part C: count the remaining bytes. */
for (; i < count; i++) {
bits += bitsinbyte[p[i]];
}
return bits;
}
#endif
/* The scalar version of popcount based on lookup tables. */
long long popcountScalar(void *s, long count) {
long long bits = 0;
unsigned char *p = s;
uint32_t *p4;
static const unsigned char bitsinbyte[256] = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2,
3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3,
3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4,
3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4,
5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6,
6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
/* Count initial bytes not aligned to 32 bit. */
while ((unsigned long)p & 3 && count) {
@@ -97,6 +187,20 @@ long long serverPopcount(void *s, long count) {
return bits;
}
/* 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) */
long long serverPopcount(void *s, long count) {
#ifdef HAVE_AVX2
/* If length of s >= 256 bits and the CPU supports AVX2,
* we prefer to use the SIMD version */
if (count >= 32) {
return popcountAVX2(s, count);
}
#endif
return popcountScalar(s, count);
}
/* Return the position of the first bit set to one (if 'bit' is 1) or
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
*
@@ -1149,6 +1253,7 @@ void bitfieldGeneric(client *c, int flags) {
}
}
initDeferredReplyBuffer(c);
addReplyArrayLen(c, numops);
/* Actually process the operations. */
@@ -1260,6 +1365,7 @@ void bitfieldGeneric(client *c, int flags) {
notifyKeyspaceEvent(NOTIFY_STRING, "setbit", c->argv[1], c->db->id);
server.dirty += changes;
}
commitDeferredReplyBuffer(c, 1);
zfree(ops);
}
+9 -3
View File
@@ -26,9 +26,13 @@
* 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.
*
* ---------------------------------------------------------------------------
*
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* API:
*
* blockClient() set the CLIENT_BLOCKED flag in the client, and set the
@@ -649,6 +653,8 @@ void blockPostponeClient(client *c) {
/* Block client due to shutdown command */
void blockClientShutdown(client *c) {
initClientBlockingState(c);
c->bstate->timeout = 0;
blockClient(c, BLOCKED_SHUTDOWN);
}
+26 -12
View File
@@ -303,12 +303,12 @@ static sds percentDecode(const char *pe, size_t len) {
/* Parse a URI and extract the server connection information.
* URI scheme is based on the provisional specification[1] excluding support
* for query parameters. Valid URIs are:
* scheme: "valkey://"
* scheme: "valkey://" or "valkeys://" or "redis://" or "rediss://"
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
* path: ["/" [<db>]]
*
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
void parseRedisUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag) {
void parseUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag) {
#ifdef USE_OPENSSL
UNUSED(tool_name);
#else
@@ -428,19 +428,33 @@ sds cliVersion(void) {
}
/* This is a wrapper to call redisConnect or redisConnectWithTimeout. */
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv) {
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
return redisConnect(ip, port);
} else {
return redisConnectWithTimeout(ip, port, tv);
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv, int nonblock) {
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
if (tv.tv_sec || tv.tv_usec) {
options.connect_timeout = &tv;
}
if (nonblock) {
options.options |= REDIS_OPT_NONBLOCK;
}
return redisConnectWithOptions(&options);
}
/* This is a wrapper to call redisConnectUnix or redisConnectUnixWithTimeout. */
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv) {
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
return redisConnectUnix(path);
} else {
return redisConnectUnixWithTimeout(path, tv);
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv, int nonblock) {
redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
if (tv.tv_sec || tv.tv_usec) {
options.connect_timeout = &tv;
}
if (nonblock) {
options.options |= REDIS_OPT_NONBLOCK;
}
return redisConnectWithOptions(&options);
}
+3 -3
View File
@@ -45,7 +45,7 @@ sds *getSdsArrayFromArgv(int argc, char **argv, int quoted);
sds unquoteCString(char *str);
void parseRedisUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag);
void parseUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag);
void freeCliConnInfo(cliConnInfo connInfo);
@@ -53,7 +53,7 @@ sds escapeJsonString(sds s, const char *p, size_t len);
sds cliVersion(void);
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv);
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv, int nonblock);
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv, int nonblock);
#endif /* __CLICOMMON_H */
+11 -9
View File
@@ -26,7 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* cluster.c contains the common parts of a clustering
* implementation, the parts that are shared between
@@ -357,7 +361,6 @@ migrateCachedSocket *migrateGetSocket(client *c, robj *host, robj *port, long ti
sdsfree(name);
return NULL;
}
connEnableTcpNoDelay(conn);
/* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs));
@@ -1341,16 +1344,15 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
* not finished their initial sync, in failed state, or are
* otherwise considered not available to serve read commands. */
int isNodeAvailable(clusterNode *node) {
/* We don't consider PFAIL here because it's not a reliable indicator
* for node available and we don't want clients to use it. */
if (clusterNodeIsFailing(node)) {
return 0;
}
long long repl_offset = clusterNodeReplOffset(node);
if (clusterNodeIsMyself(node)) {
/* Nodes do not update their own information
* in the cluster node list. */
repl_offset = getNodeReplicationOffset(node);
}
return (repl_offset != 0);
/* Hide empty replicas in here, from a data-path POV, an empty replica
* is not available. */
return getNodeReplicationOffset(node) != 0;
}
void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) {
+1 -2
View File
@@ -77,7 +77,7 @@ const char **clusterCommandExtendedHelp(void);
int clusterAllowFailoverCmd(client *c);
void clusterPromoteSelfToPrimary(void);
int clusterManualFailoverTimeLimit(void);
mstime_t clusterManualFailoverTimeLimit(void);
void clusterCommandSlots(client *c);
void clusterCommandMyId(client *c);
@@ -109,7 +109,6 @@ clusterNode *getNodeBySlot(int slot);
int clusterNodeClientPort(clusterNode *n, int use_tls);
char *clusterNodeHostname(clusterNode *node);
const char *clusterNodePreferredEndpoint(clusterNode *n, client *c);
long long clusterNodeReplOffset(clusterNode *node);
clusterNode *clusterLookupNode(const char *name, int length);
int detectAndUpdateCachedNodeHealth(void);
client *createCachedResponseClient(int resp);
+66 -33
View File
@@ -26,7 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* cluster_legacy.c contains the implementation of the cluster API that is
* specific to the standard, cluster-bus based clustering mechanism.
@@ -811,10 +815,7 @@ int clusterLoadConfig(char *filename) {
return C_OK;
fmterr:
serverLog(LL_WARNING, "Unrecoverable error: corrupted cluster config file \"%s\".", line);
zfree(line);
if (fp) fclose(fp);
exit(1);
serverPanic("Unrecoverable error: corrupted cluster config file \"%s\".", line);
}
/* Cluster node configuration is exactly the same as CLUSTER NODES output.
@@ -1085,6 +1086,27 @@ static void updateAnnouncedClientIpV6(clusterNode *node, char *value) {
}
static void updateShardId(clusterNode *node, const char *shard_id) {
/* Ensure replica shard IDs match their primary's to maintain cluster consistency.
*
* Shard ID updates must prioritize the primary, then propagate to replicas.
* This is critical due to the eventual consistency of shard IDs during cluster
* expansion. New replicas might replicate from a primary before fully
* synchronizing shard IDs with the rest of the cluster.
*
* Without this enforcement, a temporary inconsistency can arise where a
* replica's shard ID diverges from its primary's. This inconsistency is
* persisted in the primary's nodes.conf file. While this divergence will
* eventually resolve, if the primary crashes beforehand, it will enter a
* crash-restart loop due to the mismatch in its nodes.conf. */
if (shard_id && nodeIsReplica(node) &&
memcmp(clusterNodeGetPrimary(node)->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
serverLog(
LL_NOTICE,
"Shard id %.40s update request for node id %.40s diverges from existing primary shard id %.40s, rejecting!",
shard_id, node->name, clusterNodeGetPrimary(node)->shard_id);
return;
}
if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
clusterRemoveNodeFromShard(node);
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
@@ -1493,7 +1515,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
connClose(conn);
return;
}
connEnableTcpNoDelay(conn);
connKeepAlive(conn, server.cluster_node_timeout / 1000 * 2);
/* Use non-blocking I/O for cluster messages. */
@@ -1934,7 +1956,7 @@ int clusterBumpConfigEpochWithoutConsensus(void) {
if (myself->configEpoch == 0 || myself->configEpoch != maxEpoch) {
server.cluster->currentEpoch++;
myself->configEpoch = server.cluster->currentEpoch;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
serverLog(LL_NOTICE, "New configEpoch set to %llu", (unsigned long long)myself->configEpoch);
return C_OK;
} else {
@@ -1997,7 +2019,7 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) {
/* Get the next ID available at the best of this node knowledge. */
server.cluster->currentEpoch++;
myself->configEpoch = server.cluster->currentEpoch;
clusterSaveConfigOrDie(1);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
serverLog(LL_NOTICE, "configEpoch collision with node %.40s (%s). configEpoch set to %llu", sender->name,
sender->human_nodename, (unsigned long long)myself->configEpoch);
}
@@ -2197,12 +2219,6 @@ static int clusterStartHandshake(char *ip, int port, int cport) {
return 0;
}
/* Port sanity check */
if (port <= 0 || port > 65535 || cport <= 0 || cport > 65535) {
errno = EINVAL;
return 0;
}
/* Set norm_ip as the normalized string representation of the node
* IP address. */
memset(norm_ip, 0, NET_IP_STR_LEN);
@@ -3045,7 +3061,10 @@ static void clusterProcessModulePacket(clusterMsgModule *module_data, clusterNod
uint32_t len = ntohl(module_data->len);
uint8_t type = module_data->type;
unsigned char *payload = module_data->bulk_data;
moduleCallClusterReceivers(sender->name, module_id, type, payload, len);
sds sender_name = sdsnewlen(sender->name, CLUSTER_NAMELEN);
moduleCallClusterReceivers(sender_name, module_id, type, payload, len);
sdsfree(sender_name);
}
static void clusterProcessLightPacket(clusterNode *sender, clusterLink *link, uint16_t type) {
@@ -3212,7 +3231,8 @@ int clusterProcessPacket(clusterLink *link) {
freeClusterLink(link);
serverLog(
LL_NOTICE,
"Closing link for node that sent a lightweight message of type %s as its first message on the link",
"Closing link for node %.40s that sent a lightweight message of type %s as its first message on the link",
hdr->sender,
clusterGetMessageTypeString(type));
return 0;
}
@@ -3515,14 +3535,17 @@ int clusterProcessPacket(clusterLink *link) {
/* Primary turned into a replica! Reconfigure the node. */
if (sender_claimed_primary && areInSameShard(sender_claimed_primary, sender)) {
/* `sender` was a primary and was in the same shard as its new primary */
if (sender->configEpoch > sender_claimed_config_epoch) {
if (nodeEpoch(sender_claimed_primary) > sender_claimed_config_epoch) {
serverLog(LL_NOTICE,
"Ignore stale message from %.40s (%s) in shard %.40s;"
" gossip config epoch: %llu, current config epoch: %llu",
sender->name, sender->human_nodename, sender->shard_id,
(unsigned long long)sender_claimed_config_epoch,
(unsigned long long)sender->configEpoch);
} else {
(unsigned long long)nodeEpoch(sender_claimed_primary));
/* This packet is stale so we avoid processing it anymore. Otherwise
* this may cause a primary-replica chain issue. */
return 1;
} else if (nodeIsReplica(sender_claimed_primary)) {
/* `primary` is still a `replica` in this observer node's view;
* update its role and configEpoch */
clusterSetNodeAsPrimary(sender_claimed_primary);
@@ -3716,9 +3739,9 @@ int clusterProcessPacket(clusterLink *link) {
/* Manual failover requested from replicas. Initialize the state
* accordingly. */
resetManualFailover();
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
server.cluster->mf_end = now + server.cluster_mf_timeout;
server.cluster->mf_replica = sender;
pauseActions(PAUSE_DURING_FAILOVER, now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT),
pauseActions(PAUSE_DURING_FAILOVER, now + (server.cluster_mf_timeout * CLUSTER_MF_PAUSE_MULT),
PAUSE_ACTIONS_CLIENT_WRITE_SET);
serverLog(LL_NOTICE, "Manual failover requested by replica %.40s (%s).", sender->name, sender->human_nodename);
/* We need to send a ping message to the replica, as it would carry
@@ -4773,13 +4796,13 @@ void clusterFailoverReplaceYourPrimary(void) {
}
}
/* 3) Update state and save config. */
/* 3) Update state, note that we do not use CLUSTER_TODO_UPDATE_STATE since we want the node
* to update the cluster state ASAP after failover. */
clusterUpdateState();
clusterSaveConfigOrDie(1);
/* 4) Pong all the other nodes so that they can update the state
/* 4) Save and fsync the config, and pong all the other nodes so that they can update the state
* accordingly and detect that we switched to primary role. */
clusterDoBeforeSleep(CLUSTER_TODO_BROADCAST_ALL);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
/* 5) If there was a manual failover in progress, clear the state. */
resetManualFailover();
@@ -4962,6 +4985,7 @@ void clusterHandleReplicaFailover(void) {
/* Update my configEpoch to the epoch of the election. */
if (myself->configEpoch < server.cluster->failover_auth_epoch) {
myself->configEpoch = server.cluster->failover_auth_epoch;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
serverLog(LL_NOTICE, "configEpoch set to %llu after successful failover",
(unsigned long long)myself->configEpoch);
}
@@ -5089,7 +5113,7 @@ void clusterHandleReplicaMigration(int max_replicas) {
* setting mf_end to the millisecond unix time at which we'll abort the
* attempt.
* 2) Replica sends a MFSTART message to the primary requesting to pause clients
* for two times the manual failover timeout CLUSTER_MF_TIMEOUT.
* for CLUSTER_MF_PAUSE_MULT times the server.cluster_mf_timeout.
* When primary is paused for manual failover, it also starts to flag
* packets with CLUSTERMSG_FLAG0_PAUSED.
* 3) Replica waits for primary to send its replication offset flagged as PAUSED.
@@ -6219,7 +6243,7 @@ int checkSlotAssignmentsOrReply(client *c, unsigned char *slots, int del, int st
return C_ERR;
}
if (slots[slot]++ == 1) {
addReplyErrorFormat(c, "Slot %d specified multiple times", (int)slot);
addReplyErrorFormat(c, "Slot %d specified multiple times", slot);
return C_ERR;
}
}
@@ -6242,6 +6266,10 @@ void clusterUpdateSlots(client *c, unsigned char *slots, int del) {
}
}
/* Get the replication offset of a node.
*
* Nodes do not update their own information in the cluster state,
* so for self node, we cannot use `node->repl_offset` directly. */
long long getNodeReplicationOffset(clusterNode *node) {
if (node->flags & CLUSTER_NODE_MYSELF) {
return nodeIsReplica(node) ? replicationGetReplicaOffset() : server.primary_repl_offset;
@@ -6471,7 +6499,7 @@ clusterNode *getMyClusterNode(void) {
return server.cluster->myself;
}
int clusterManualFailoverTimeLimit(void) {
mstime_t clusterManualFailoverTimeLimit(void) {
return server.cluster->mf_end;
}
@@ -6894,6 +6922,10 @@ int clusterCommandSpecial(client *c) {
addReplyErrorFormat(c, "Invalid base port specified: %s", (char *)c->argv[3]->ptr);
return 1;
}
if (port <= 0 || port > 65535) {
addReplyErrorFormat(c, "Port number is out of range");
return 1;
}
if (c->argc == 5) {
if (getLongLongFromObject(c->argv[4], &cport) != C_OK) {
@@ -6904,6 +6936,11 @@ int clusterCommandSpecial(client *c) {
cport = port + CLUSTER_PORT_INCR;
}
if (cport <= 0 || cport > 65535) {
addReplyErrorFormat(c, "Cluster bus port number is out of range");
return 1;
}
if (clusterStartHandshake(c->argv[2]->ptr, port, cport) == 0 && errno == EINVAL) {
addReplyErrorFormat(c, "Invalid node address specified: %s:%s", (char *)c->argv[2]->ptr,
(char *)c->argv[3]->ptr);
@@ -7116,7 +7153,7 @@ int clusterCommandSpecial(client *c) {
return 1;
}
resetManualFailover();
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
server.cluster->mf_end = mstime() + server.cluster_mf_timeout;
sds client = catClientInfoShortString(sdsempty(), c, server.hide_user_data_from_log);
if (takeover) {
@@ -7279,10 +7316,6 @@ char *clusterNodeHostname(clusterNode *node) {
return node->hostname;
}
long long clusterNodeReplOffset(clusterNode *node) {
return node->repl_offset;
}
const char *clusterNodePreferredEndpoint(clusterNode *n, client *c) {
char *hostname = clusterNodeHostname(n);
switch (server.cluster_preferred_endpoint_type) {
-1
View File
@@ -7,7 +7,6 @@
* multiplicators of the node timeout value (when ending with MULT). */
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if primary is back. */
#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
#define CLUSTER_MF_PAUSE_MULT 2 /* Primary pause manual failover mult. */
#define CLUSTER_REPLICA_MIGRATION_DELAY 5000 /* Delay for replica migration. */
+8 -5
View File
@@ -1,9 +1,8 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "cluster_slot_stats.h"
#define UNASSIGNED_SLOT 0
@@ -149,10 +148,14 @@ static void clusterSlotStatsUpdateNetworkBytesOutForReplication(long long len) {
client *c = server.current_client;
if (c == NULL || !canAddNetworkBytesOut(c)) return;
/* We multiply the bytes len by the number of replicas to account for us broadcasting to multiple replicas at once. */
len *= (long long)listLength(server.replicas);
serverAssert(c->slot >= 0 && c->slot < CLUSTER_SLOTS);
serverAssert(nodeIsPrimary(server.cluster->myself));
if (len < 0) serverAssert(server.cluster->slot_stats[c->slot].network_bytes_out >= (uint64_t)llabs(len));
server.cluster->slot_stats[c->slot].network_bytes_out += (len * listLength(server.replicas));
/* We sometimes want to adjust the counter downwards (for example when we want to undo accounting for
* SELECT commands that don't belong to any slot) so let's make sure we don't underflow the counter. */
serverAssert(len >= 0 || server.cluster->slot_stats[c->slot].network_bytes_out >= (uint64_t)-len);
server.cluster->slot_stats[c->slot].network_bytes_out += len;
}
/* Increment network bytes out for replication stream. This method will increment `len` value times the active replica
+6 -4
View File
@@ -1,3 +1,9 @@
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/* Commandlog implements a system that is able to remember the latest N
* queries that took more than M microseconds to execute, or consumed
* too much network bandwidth and memory for input/output buffers.
@@ -14,10 +20,6 @@
* but is accessible thanks to the COMMANDLOG command.
*
* ----------------------------------------------------------------------------
*
* Copyright Valkey Contributors.
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
*/
#include "commandlog.h"
+1 -1
View File
@@ -82,7 +82,7 @@
"const": 1
},
{
"description": "Source was not copied.",
"description": "Source was not copied when the destination key already exists",
"const": 0
}
]
+1 -1
View File
@@ -52,7 +52,7 @@
"const": 1
},
{
"description": "Key wasn't moved.",
"description": "Key wasn't moved. When key already exists in the destination database, or it does not exist in the source database",
"const": 0
}
]
+1
View File
@@ -3325,6 +3325,7 @@ standardConfig static_configs[] = {
createLongLongConfig("proto-max-bulk-len", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024 * 1024, LONG_MAX, server.proto_max_bulk_len, 512ll * 1024 * 1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 10 * 1024 * 1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 10mb */
createLongLongConfig("cluster-manual-failover-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.cluster_mf_timeout, 5000, INTEGER_CONFIG, NULL, NULL),
/* Unsigned Long Long configs */
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
-2
View File
@@ -380,8 +380,6 @@ static inline const char *connGetInfo(connection *conn, char *buf, size_t buf_le
/* anet-style wrappers to conns */
int connBlock(connection *conn);
int connNonBlock(connection *conn);
int connEnableTcpNoDelay(connection *conn);
int connDisableTcpNoDelay(connection *conn);
int connKeepAlive(connection *conn, int interval);
int connSendTimeout(connection *conn, long long ms);
int connRecvTimeout(connection *conn, long long ms);
+1 -1
View File
@@ -445,7 +445,7 @@ robj *dbRandomKey(serverDb *db) {
sds key = objectGetKey(valkey);
robj *keyobj = createStringObject(key, sdslen(key));
if (objectIsExpired(valkey)) {
if (allvolatile && (server.primary_host || server.import_mode) && --maxtries == 0) {
if (allvolatile && (server.primary_host || server.import_mode || isPausedActions(PAUSE_ACTION_EXPIRE)) && --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 replica, so the function cannot stop because
+6
View File
@@ -508,6 +508,9 @@ void debugCommand(client *c) {
" Grace period in seconds for replica main channel to establish psync.",
"DICT-RESIZING <0|1>",
" Enable or disable the main dict and expire dict resizing.",
"CLIENT-ENFORCE-REPLY-LIST <0|1>",
"When set to 1, it enforces the use of the client reply list directly",
" and avoids using the client's static buffer.",
NULL};
addExtendedReplyHelp(c, help, clusterDebugCommandExtendedHelp());
} else if (!strcasecmp(c->argv[1]->ptr, "segfault")) {
@@ -1020,6 +1023,9 @@ void debugCommand(client *c) {
} else if (!strcasecmp(c->argv[1]->ptr, "dict-resizing") && c->argc == 3) {
server.dict_resizing = atoi(c->argv[2]->ptr);
addReply(c, shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr, "client-enforce-reply-list") && c->argc == 3) {
server.debug_client_enforce_reply_list = atoi(c->argv[2]->ptr);
addReply(c, shared.ok);
} else if (!handleDebugClusterCommand(c)) {
addReplySubcommandSyntaxError(c);
return;
+21 -18
View File
@@ -32,12 +32,18 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "hashtable.h"
#include "eval.h"
#include "script.h"
#include "module.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef HAVE_DEFRAG
@@ -346,7 +352,7 @@ static void activeDefragSdsDict(dict *d, int val_type) {
} while (cursor != 0);
}
void activeDefragSdsHashtableCallback(void *privdata, void *entry_ref) {
static void activeDefragSdsHashtableCallback(void *privdata, void *entry_ref) {
UNUSED(privdata);
sds *sds_ref = (sds *)entry_ref;
sds new_sds = activeDefragSds(*sds_ref);
@@ -398,7 +404,7 @@ static long scanLaterList(robj *ob, unsigned long *cursor, monotime endtime) {
quicklistNode *node;
long iterations = 0;
int bookmark_failed = 0;
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST) return 0;
serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
if (*cursor == 0) {
/* if cursor is 0, we start new iteration */
@@ -441,7 +447,7 @@ static void scanLaterZsetCallback(void *privdata, void *element_ref) {
}
static void scanLaterZset(robj *ob, unsigned long *cursor) {
if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST) return;
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
zset *zs = (zset *)ob->ptr;
*cursor = hashtableScanDefrag(zs->ht, *cursor, scanLaterZsetCallback, zs->zsl, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
}
@@ -455,7 +461,7 @@ static void scanHashtableCallbackCountScanned(void *privdata, void *elemref) {
}
static void scanLaterSet(robj *ob, unsigned long *cursor) {
if (ob->type != OBJ_SET || ob->encoding != OBJ_ENCODING_HASHTABLE) return;
serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HASHTABLE);
hashtable *ht = ob->ptr;
*cursor = hashtableScanDefrag(ht, *cursor, activeDefragSdsHashtableCallback, NULL, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
}
@@ -470,7 +476,7 @@ static void activeDefragHashTypeEntry(void *privdata, void *element_ref) {
}
static void scanLaterHash(robj *ob, unsigned long *cursor) {
if (ob->type != OBJ_HASH || ob->encoding != OBJ_ENCODING_HASHTABLE) return;
serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HASHTABLE);
hashtable *ht = ob->ptr;
*cursor = hashtableScanDefrag(ht, *cursor, activeDefragHashTypeEntry, NULL, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
}
@@ -557,10 +563,7 @@ static int scanLaterStreamListpacks(robj *ob, unsigned long *cursor, monotime en
static unsigned char last[sizeof(streamID)];
raxIterator ri;
long iterations = 0;
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
*cursor = 0;
return 0;
}
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
stream *s = ob->ptr;
raxStart(&ri, s->rax);
@@ -822,15 +825,15 @@ static void defragPubsubScanCallback(void *privdata, void *elemref) {
* and 1 if time is up and more work is needed. */
static int defragLaterItem(robj *ob, unsigned long *cursor, monotime endtime, int dbid) {
if (ob) {
if (ob->type == OBJ_LIST) {
if (ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST) {
return scanLaterList(ob, cursor, endtime);
} else if (ob->type == OBJ_SET) {
} else if (ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HASHTABLE) {
scanLaterSet(ob, cursor);
} else if (ob->type == OBJ_ZSET) {
} else if (ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST) {
scanLaterZset(ob, cursor);
} else if (ob->type == OBJ_HASH) {
} else if (ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HASHTABLE) {
scanLaterHash(ob, cursor);
} else if (ob->type == OBJ_STREAM) {
} else if (ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM) {
return scanLaterStreamListpacks(ob, cursor, endtime);
} else if (ob->type == OBJ_MODULE) {
/* Fun fact (and a bug since forever): The key is passed to
@@ -839,10 +842,9 @@ static int defragLaterItem(robj *ob, unsigned long *cursor, monotime endtime, in
* callbacks. Nobody can ever have used this, i.e. accessed the key
* name in the defrag module type callback. */
void *sds_key_passed_as_robj = objectGetKey(ob);
long long endtimeWallClock = ustime() + (endtime - getMonotonicUs());
return moduleLateDefrag(sds_key_passed_as_robj, ob, cursor, endtimeWallClock, dbid);
return moduleLateDefrag(sds_key_passed_as_robj, ob, cursor, endtime, dbid);
} else {
*cursor = 0; /* object type may have changed since we schedule it for later */
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
}
} else {
*cursor = 0; /* object may have been deleted already */
@@ -1042,7 +1044,6 @@ static void endDefragCycle(bool normal_termination) {
// For normal termination, we expect...
serverAssert(!defrag.current_stage);
serverAssert(listLength(defrag.remaining_stages) == 0);
serverAssert(!defrag_later || listLength(defrag_later) == 0);
} else {
// Defrag is being terminated abnormally
aeDeleteTimeEvent(server.el, defrag.timeproc_id);
@@ -1058,6 +1059,8 @@ static void endDefragCycle(bool normal_termination) {
listRelease(defrag.remaining_stages);
defrag.remaining_stages = NULL;
/* For a normal termination, this list is usually empty, but it might contain elements
* if a stage was aborted due to a flushall or some other DB swap. */
if (defrag_later) {
listRelease(defrag_later);
defrag_later = NULL;
+5 -1
View File
@@ -32,7 +32,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fmacros.h"
#include <stddef.h>
+6
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* This file initializes the global LUA object and registers functions to call Valkey API from within the LUA language.
@@ -406,6 +411,7 @@ static int evalRegisterNewScript(client *c, robj *body, char **sha) {
scriptingEngineCallCompileCode(engine,
VMSE_EVAL,
(sds)body->ptr + shebang_len,
sdslen(body->ptr) - shebang_len,
0,
&num_compiled_functions,
&_err);
+8 -4
View File
@@ -29,6 +29,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
@@ -677,6 +682,9 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
return;
} else {
obj = setExpire(c, c->db, key, when);
signalModifiedKey(c, c->db, key);
notifyKeyspaceEvent(NOTIFY_GENERIC, "expire", key, c->db->id);
server.dirty++;
addReply(c, shared.cone);
/* Propagate as PEXPIREAT millisecond-timestamp
* Only rewrite the command arg if not already PEXPIREAT */
@@ -690,10 +698,6 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
rewriteClientCommandArgument(c, 2, when_obj);
decrRefCount(when_obj);
}
signalModifiedKey(c, c->db, key);
notifyKeyspaceEvent(NOTIFY_GENERIC, "expire", key, c->db->id);
server.dirty++;
return;
}
}
+6
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "functions.h"
#include "sds.h"
@@ -1007,6 +1012,7 @@ sds functionsCreateWithLibraryCtx(sds code, int replace, sds *err, functionsLibC
scriptingEngineCallCompileCode(engine,
VMSE_FUNCTION,
md.code,
sdslen(md.code),
timeout,
&num_compiled_functions,
&compile_error);
+2 -1
View File
@@ -1032,7 +1032,7 @@ void hashtableEmpty(hashtable *ht, void(callback)(hashtable *)) {
if (ht->bucket_exp[table_index] < 0) {
continue;
}
if (ht->used[table_index] > 0) {
if (ht->used[table_index] > 0 || ht->child_buckets[table_index] > 0) {
for (size_t idx = 0; idx < numBuckets(ht->bucket_exp[table_index]); idx++) {
if (callback && (idx & 65535) == 0) callback(ht);
bucket *b = &ht->tables[table_index][idx];
@@ -1058,6 +1058,7 @@ void hashtableEmpty(hashtable *ht, void(callback)(hashtable *)) {
} while (b != NULL);
}
}
zfree(ht->tables[table_index]);
if (ht->type->trackMemUsage) {
ht->type->trackMemUsage(ht, -sizeof(bucket) * numBuckets(ht->bucket_exp[table_index]));
+5
View File
@@ -28,6 +28,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "intrinsics.h"
+3 -2
View File
@@ -1,7 +1,7 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io_threads.h"
@@ -574,6 +574,7 @@ void trySendPollJobToIOThreads(void) {
static void ioThreadAccept(void *data) {
client *c = (client *)data;
connAccept(c->conn, NULL);
atomic_thread_fence(memory_order_release);
c->io_read_state = CLIENT_COMPLETED_IO;
}
+3 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -195,6 +195,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
engineCtx *engine_ctx,
subsystemType type,
const char *code,
size_t code_len,
size_t timeout,
size_t *out_num_compiled_functions,
robj **err) {
@@ -207,7 +208,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
if (type == VMSE_EVAL) {
lua_State *lua = lua_engine_ctx->eval_lua;
if (luaL_loadbuffer(lua, code, strlen(code), "@user_script")) {
if (luaL_loadbuffer(lua, code, code_len, "@user_script")) {
sds error = sdscatfmt(sdsempty(), "Error compiling script (new function): %s", lua_tostring(lua, -1));
*err = createObject(OBJ_STRING, error);
lua_pop(lua, 1);
+4 -3
View File
@@ -1,8 +1,9 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* This file utilizes prefetching keys and data for multiple commands in a batch,
* to improve performance by amortizing memory access costs across multiple operations.
*/
+131 -33
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/* --------------------------------------------------------------------------
* Modules API documentation information
@@ -193,8 +198,8 @@ typedef struct ValkeyModuleCtx ValkeyModuleCtx;
#define VALKEYMODULE_CTX_NEW_CLIENT (1 << 7) /* Free client object when the \
context is destroyed */
#define VALKEYMODULE_CTX_CHANNELS_POS_REQUEST (1 << 8)
#define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
#define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
#define VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION (1 << 10) /* Context created a keyspace notification event */
/* This represents a key opened with VM_OpenKey(). */
struct ValkeyModuleKey {
@@ -2566,7 +2571,16 @@ void VM_Yield(ValkeyModuleCtx *ctx, int flags, const char *busy_reply) {
* By default, the server will not fire key-space notifications that happened inside
* a key-space notification callback. This flag allows to change this behavior
* and fire nested key-space notifications. Notice: if enabled, the module
* should protected itself from infinite recursion. */
* should protected itself from infinite recursion.
*
* VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION:
* When set, this option allows the module to skip command validation.
* This is useful in scenarios where the module needs to bypass
* command validation for specific operations
* to reduce overhead or handle trusted custom command logic.
* ValkeyModule_Replicate and ValkeyModule_EmitAOF
* are affected by this option, allowing them to operate without
* command validation check. */
void VM_SetModuleOptions(ValkeyModuleCtx *ctx, int options) {
ctx->module->options = options;
}
@@ -3630,8 +3644,10 @@ int VM_Replicate(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ...
int argc = 0, flags = 0, j;
va_list ap;
cmd = lookupCommandByCString((char *)cmdname);
if (!cmd) return VALKEYMODULE_ERR;
if (!ctx->module || !(ctx->module->options & VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION)) {
cmd = lookupCommandByCString((char *)cmdname);
if (!cmd) return VALKEYMODULE_ERR;
}
/* Create the client and dispatch the command. */
va_start(ap, fmt);
@@ -4183,7 +4199,7 @@ static void moduleCloseKey(ValkeyModuleKey *key) {
decrRefCount(key->key);
}
/* Close a key handle. */
/* Close a key handle. The key handle is freed and should not be accessed anymore. */
void VM_CloseKey(ValkeyModuleKey *key) {
if (key == NULL) return;
moduleCloseKey(key);
@@ -7535,15 +7551,17 @@ void VM_EmitAOF(ValkeyModuleIO *io, const char *cmdname, const char *fmt, ...) {
int argc = 0, flags = 0, j;
va_list ap;
cmd = lookupCommandByCString((char *)cmdname);
if (!cmd) {
serverLog(LL_WARNING,
"Fatal: AOF method for module data type '%s' tried to "
"emit unknown command '%s'",
io->type->name, cmdname);
io->error = 1;
errno = EINVAL;
return;
if (!io->ctx || !io->ctx->module || !(io->ctx->module->options & VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION)) {
cmd = lookupCommandByCString((char *)cmdname);
if (!cmd) {
serverLog(LL_WARNING,
"Fatal: AOF method for module data type '%s' tried to "
"emit unknown command '%s'",
io->type->name, cmdname);
io->error = 1;
errno = EINVAL;
return;
}
}
/* Emit the arguments into the AOF in RESP format. */
@@ -7777,6 +7795,8 @@ void unblockClientFromModule(client *c) {
* in that case the privdata argument is disregarded, because we pass the
* reply callback the privdata that is set here while blocking.
*
* For details on return values and error codes, see the comment block for
* VM_BlockClient.
*/
ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
ValkeyModuleCmdFunc reply_callback,
@@ -7789,8 +7809,27 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
void *privdata,
int flags) {
client *c = ctx->client;
if (c->flag.blocked || getClientType(c) != CLIENT_TYPE_NORMAL || c->flag.deny_blocking) {
/* Early return if duplicate block attempt or client is not normal or
* client is set to deny blocking. */
errno = ENOTSUP;
return NULL;
}
if (ctx->flags & (VALKEYMODULE_CTX_TEMP_CLIENT | VALKEYMODULE_CTX_NEW_CLIENT)) {
/* Temporary clients can't be blocked */
errno = EINVAL;
return NULL;
}
int is_keyspace_notification = ctx->flags & (VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION);
int islua = scriptIsRunning();
int ismulti = server.in_exec;
if ((islua || ismulti) && is_keyspace_notification) {
/* Avoid blocking within transactions when context initiated by
* keyspace notification. */
errno = EINVAL;
return NULL;
}
initClientBlockingState(c);
c->bstate->module_blocked_handle = zmalloc(sizeof(ValkeyModuleBlockedClient));
@@ -7846,6 +7885,11 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
c->bstate->timeout = timeout;
blockClient(c, BLOCKED_MODULE);
}
/* Defer response until after being unblocked for a context originated from
* keyspace notification events */
if (is_keyspace_notification) {
initDeferredReplyBuffer(c);
}
}
return bc;
}
@@ -8073,14 +8117,27 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
* free_privdata: called in order to free the private data that is passed
* by ValkeyModule_UnblockClient() call.
*
* Note: ValkeyModule_UnblockClient should be called for every blocked client,
* even if client was killed, timed-out or disconnected. Failing to do so
* will result in memory leaks.
* Notes:
* 1. ValkeyModule_UnblockClient should be called for every blocked client,
* even if client was killed, timed-out or disconnected. Failing to do so
* will result in memory leaks.
* 2. Attempting to block the client on keyspace event notification in versions
* prior to 8.1.1 leads to a crash.
*
* There are some cases where ValkeyModule_BlockClient() cannot be used:
*
* 1. If the client is a Lua script.
* 2. If the client is executing a MULTI block.
* 3. If the client is a temporary module client.
* 4. If the client is already blocked.
*
* In cases 1 and 2, a call to ValkeyModule_BlockClient() will **not** block the
* client, but instead produce a specific error reply. Note that if the
* BlockClient call originated from within a keyspace notification, no error
* reply is generated but nullptr is returned while the errno is set to EINVAL.
*
* In case 3 and 4, a call to ValkeyModule_BlockClient() are no-op, returning
* nullptr. errno is set to EINVAL for case 3 while ENOTSUP for case 4.
*
* In these cases, a call to ValkeyModule_BlockClient() will **not** block the
* client, but instead produce a specific error reply.
@@ -8272,6 +8329,12 @@ int moduleClientIsBlockedOnKeys(client *c) {
* needs to be passed to the client, included but not limited some slow
* to compute reply or some reply obtained via networking.
*
* Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned
* and `errno` is set as follows:
*
* - EINVAL if bc is NULL.
* - ENOTSUP if bc contains `blocked on keys` but its timeout callback is NULL.
*
* Note 1: this function can be called from threads spawned by the module.
*
* Note 2: when we unblock a client that is blocked for keys using the API
@@ -8282,10 +8345,17 @@ int moduleClientIsBlockedOnKeys(client *c) {
* ValkeyModule_BlockClientOnKeys() is accessible from the timeout
* callback via VM_GetBlockedClientPrivateData). */
int VM_UnblockClient(ValkeyModuleBlockedClient *bc, void *privdata) {
if (!bc) {
errno = EINVAL;
return VALKEYMODULE_ERR;
}
if (bc->blocked_on_keys) {
/* In theory the user should always pass the timeout handler as an
* argument, but better to be safe than sorry. */
if (bc->timeout_callback == NULL) return VALKEYMODULE_ERR;
if (bc->timeout_callback == NULL) {
errno = ENOTSUP;
return VALKEYMODULE_ERR;
}
if (bc->unblocked) return VALKEYMODULE_OK;
if (bc->client) moduleBlockedClientTimedOut(bc->client, 1);
}
@@ -8374,11 +8444,17 @@ void moduleHandleBlockedClients(void) {
moduleInvokeFreePrivDataCallback(c, bc);
}
/* It is possible that this blocked client object accumulated
* replies to send to the client in a thread safe context.
* We need to glue such replies to the client output buffer and
* free the temporary client we just used for the replies. */
if (c) AddReplyFromClient(c, bc->reply_client);
if (c) {
/* Replies which were added after the client is blocked by a module
* are accumulated separately. We need to transmit those replies
* to the client. */
commitDeferredReplyBuffer(c, 0);
/* It is possible that this blocked client object accumulated
* replies to send to the client in a thread safe context.
* We need to glue such replies to the client output buffer and
* free the temporary client we just used for the replies. */
AddReplyFromClient(c, bc->reply_client);
}
moduleReleaseTempClient(bc->reply_client);
moduleReleaseTempClient(bc->thread_safe_ctx_client);
@@ -8474,9 +8550,10 @@ void moduleBlockedClientTimedOut(client *c, int from_module) {
moduleFreeContext(&ctx);
if (!from_module)
if (!from_module) {
updateStatsOnUnblock(c, bc->background_duration, 0,
((server.stat_total_error_replies != prev_error_replies) ? ERROR_COMMAND_FAILED : 0));
}
/* For timeout events, we do not want to call the disconnect callback,
* because the blocked client will be automatically disconnected in
@@ -8822,12 +8899,16 @@ int VM_NotifyKeyspaceEvent(ValkeyModuleCtx *ctx, int type, const char *event, Va
return VALKEYMODULE_OK;
}
unsigned long moduleNotifyKeyspaceSubscribersCnt(void) {
return listLength(moduleKeyspaceSubscribers);
}
/* Dispatcher for keyspace notifications to module subscriber functions.
* This gets called only if at least one module requested to be notified on
* keyspace notifications */
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
/* Don't do anything if there aren't any subscribers */
if (listLength(moduleKeyspaceSubscribers) == 0) return;
if (moduleNotifyKeyspaceSubscribersCnt() == 0) return;
/* Ugly hack to handle modules which use write commands from within
* notify_callback, which they should NOT do!
@@ -8862,8 +8943,14 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
if ((sub->event_mask & type) &&
(sub->active == 0 || (sub->module->options & VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS))) {
ValkeyModuleCtx ctx;
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
if (server.executing_client == NULL) {
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
} else {
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_NONE);
ctx.client = server.executing_client;
}
selectDb(ctx.client, dbid);
ctx.flags |= VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION;
/* mark the handler as active to avoid reentrant loops.
* If the subscriber performs an action triggering itself,
@@ -8950,7 +9037,13 @@ void moduleCallClusterReceivers(const char *sender_id,
* was already a registered callback, this will replace the callback function
* with the one provided, otherwise if the callback is set to NULL and there
* is already a callback for this function, the callback is unregistered
* (so this API call is also used in order to delete the receiver). */
* (so this API call is also used in order to delete the receiver).
*
* When a message of this type is received, the registered callback function
* will be invoked with details, including the 40-byte node ID of the sender.
*
* In Valkey 8.1 and later, the node ID is null-terminated. Prior to 8.1, it was
* not null-terminated */
void VM_RegisterClusterMessageReceiver(ValkeyModuleCtx *ctx,
uint8_t type,
ValkeyModuleClusterMessageReceiver callback) {
@@ -9610,8 +9703,7 @@ void revokeClientAuthentication(client *c) {
* is eventually freed we don't rely on the module to still exist. */
moduleNotifyUserChanged(c);
c->user = DefaultUser;
c->flag.authenticated = 0;
clientSetUser(c, DefaultUser, 0);
/* We will write replies to this client later, so we can't close it
* directly even if async. */
if (c == server.current_client) {
@@ -9737,8 +9829,15 @@ ValkeyModuleString *VM_GetModuleUserACLString(ValkeyModuleUser *user) {
* See more information in VM_GetModuleUserFromUserName.
*
* The returned string must be released with ValkeyModule_FreeString() or by
* enabling automatic memory management. */
* enabling automatic memory management.
*
* If the context is not associated with a client connection, NULL is returned
* and errno is set to EINVAL. */
ValkeyModuleString *VM_GetCurrentUserName(ValkeyModuleCtx *ctx) {
if (ctx == NULL || ctx->client == NULL || ctx->client->user == NULL || ctx->client->user->name == NULL) {
errno = EINVAL;
return NULL;
}
return VM_CreateString(ctx, ctx->client->user->name, sdslen(ctx->client->user->name));
}
@@ -9932,8 +10031,7 @@ static int authenticateClientWithUser(ValkeyModuleCtx *ctx,
moduleNotifyUserChanged(ctx->client);
ctx->client->user = user;
ctx->client->flag.authenticated = 1;
clientSetUser(ctx->client, user, 1);
if (clientHasModuleAuthInProgress(ctx->client)) {
ctx->client->flag.module_auth_has_result = 1;
+1
View File
@@ -203,6 +203,7 @@ void moduleAcquireGIL(void);
int moduleTryAcquireGIL(void);
void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
unsigned long moduleNotifyKeyspaceSubscribersCnt(void);
void firePostExecutionUnitJobs(void);
void moduleCallCommandFilters(client *c);
void modulePostExecutionUnitOperations(void);
+143 -60
View File
@@ -71,7 +71,7 @@ static void pauseClientsByClient(mstime_t end, int isPauseClientAll);
int postponeClientRead(client *c);
char *getClientSockname(client *c);
static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter);
static int clientMatchesFilter(client *client, clientFilter client_filter);
static int clientMatchesFilter(client *client, clientFilter *client_filter);
static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data);
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
@@ -132,8 +132,22 @@ void linkClient(client *c) {
static void clientSetDefaultAuth(client *c) {
/* If the default user does not require authentication, the user is
* directly authenticated. */
c->user = DefaultUser;
c->flag.authenticated = (c->user->flags & USER_FLAG_NOPASS) && !(c->user->flags & USER_FLAG_DISABLED);
clientSetUser(c, DefaultUser, (DefaultUser->flags & USER_FLAG_NOPASS) && !(DefaultUser->flags & USER_FLAG_DISABLED));
}
/* Attach the user u to this client.
* Also, mark the client authentication state. In case the client is marked as authenticated,
* it will also set the ever_authenticated flag on the client in order to avoid low level
* limiting of the client output buffer.*/
void clientSetUser(client *c, user *u, int authenticated) {
c->user = u;
c->flag.authenticated = authenticated;
if (authenticated)
c->flag.ever_authenticated = authenticated;
}
static int clientEverAuthenticated(client *c) {
return c->flag.ever_authenticated;
}
int authRequired(client *c) {
@@ -157,8 +171,6 @@ client *createClient(connection *conn) {
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (conn) {
connEnableTcpNoDelay(conn);
if (server.tcpkeepalive) connKeepAlive(conn, server.tcpkeepalive);
connSetReadHandler(conn, readQueryFromClient);
connSetPrivateData(conn, c);
conn->flags |= CONN_FLAG_ALLOW_ACCEPT_OFFLOAD;
@@ -205,8 +217,10 @@ client *createClient(connection *conn) {
c->duration = 0;
clientSetDefaultAuth(c);
c->reply = listCreate();
c->deferred_reply = NULL;
c->deferred_reply_errors = NULL;
c->reply_bytes = 0;
c->deferred_reply_bytes = ULLONG_MAX;
c->obuf_soft_limit_reached_time = 0;
listSetFreeMethod(c->reply, freeClientReplyValue);
listSetDupMethod(c->reply, dupClientReplyValue);
@@ -279,7 +293,6 @@ void putClientInPendingWriteQueue(client *c) {
listLinkNodeHead(server.clients_pending_write, &c->clients_pending_write_node);
}
}
/* This function is called every time we are going to transmit new data
* to the client. The behavior is the following:
*
@@ -328,6 +341,7 @@ int prepareClientToWrite(client *c) {
* it should already be setup to do so (it has already pending data). */
if (!clientHasPendingReplies(c)) putClientInPendingWriteQueue(c);
if (!isDeferredReplyEnabled(c)) c->flag.buffered_reply = 1;
/* Authorize the caller to queue in the output buffer of this client. */
return C_OK;
}
@@ -388,7 +402,8 @@ void deleteCachedResponseClient(client *recording_client) {
VALKEY_NO_SANITIZE("bounds")
size_t _addReplyToBuffer(client *c, const char *s, size_t len) {
size_t available = c->buf_usable_size - c->bufpos;
/* If the debug enforcing to use the reply list is enabled.*/
if (server.debug_client_enforce_reply_list) return 0;
/* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return 0;
@@ -426,14 +441,16 @@ void _addReplyProtoToList(client *c, list *reply_list, const char *s, size_t len
/* Create a new node, make sure it is allocated to at
* least PROTO_REPLY_CHUNK_BYTES */
size_t usable_size;
size_t size = len < PROTO_REPLY_CHUNK_BYTES ? PROTO_REPLY_CHUNK_BYTES : len;
size_t min_reply_size = isDeferredReplyEnabled(c) ? PROTO_REPLY_MIN_BYTES : PROTO_REPLY_CHUNK_BYTES;
size_t size = len < min_reply_size ? min_reply_size : len;
tail = zmalloc_usable(size + sizeof(clientReplyBlock), &usable_size);
/* take over the allocation's internal fragmentation */
tail->size = usable_size - sizeof(clientReplyBlock);
tail->used = len;
memcpy(tail->buf, s, len);
listAddNodeTail(reply_list, tail);
c->reply_bytes += tail->size;
unsigned long long *reply_bytes = (isDeferredReplyEnabled(c)) ? &c->deferred_reply_bytes : &c->reply_bytes;
*reply_bytes += tail->size;
closeClientOnOutputBufferLimitReached(c, 1);
}
@@ -463,7 +480,6 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
}
c->net_output_bytes_curr_cmd += len;
/* We call it here because this function may affect the reply
* buffer offset (see function comment) */
reqresSaveClientReplyOffset(c);
@@ -474,12 +490,17 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
* the SUBSCRIBE command family, which (currently) have a push message instead of a proper reply.
* The check for executing_client also avoids affecting push messages that are part of eviction.
* Check CLIENT_PUSHING first to avoid race conditions, as it's absent in module's fake client. */
if (c->flag.pushing && c == server.current_client && server.executing_client &&
!cmdHasPushAsReply(server.executing_client->cmd)) {
_addReplyProtoToList(c, server.pending_push_messages, s, len);
int defer_push_message = c->flag.pushing && c == server.current_client && server.executing_client &&
!cmdHasPushAsReply(server.executing_client->cmd);
if (defer_push_message == 0 && isDeferredReplyEnabled(c)) {
_addReplyProtoToList(c, c->deferred_reply, s, len);
return;
}
if (defer_push_message) {
_addReplyProtoToList(c, server.pending_push_messages, s, len);
return;
}
size_t reply_len = _addReplyToBuffer(c, s, len);
if (len > reply_len) _addReplyProtoToList(c, c->reply, s + reply_len, len - reply_len);
}
@@ -566,6 +587,7 @@ void afterErrorReply(client *c, const char *s, size_t len, int flags) {
return;
}
commitDeferredReplyBuffer(c, 1);
if (!(flags & ERR_REPLY_FLAG_NO_STATS_UPDATE)) {
/* Increment the global error counter */
server.stat_total_error_replies++;
@@ -842,6 +864,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
size_t len_to_copy = prev->size - prev->used;
if (len_to_copy > length) len_to_copy = length;
memcpy(prev->buf + prev->used, s, len_to_copy);
c->net_output_bytes_curr_cmd += len_to_copy;
prev->used += len_to_copy;
length -= len_to_copy;
if (length == 0) {
@@ -855,6 +878,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
next->used < PROTO_REPLY_CHUNK_BYTES * 4 && c->io_write_state != CLIENT_PENDING_IO) {
memmove(next->buf + length, next->buf, next->used);
memcpy(next->buf, s, length);
c->net_output_bytes_curr_cmd += length;
next->used += length;
listDelNode(c->reply, ln);
} else {
@@ -865,6 +889,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
buf->size = usable_size - sizeof(clientReplyBlock);
buf->used = length;
memcpy(buf->buf, s, length);
c->net_output_bytes_curr_cmd += length;
listNodeValue(ln) = buf;
c->reply_bytes += buf->size;
@@ -1282,6 +1307,49 @@ void addReplySubcommandSyntaxError(client *c) {
sdsfree(cmd);
}
inline int isDeferredReplyEnabled(client *c) {
return c->deferred_reply_bytes != ULLONG_MAX;
}
/* Commands that generate replies before triggering keyspace notifications must
* use a deferred reply buffer. This allows postponing the actual transmission
* of the reply until after the client is unblocked, in case it was blocked by
* a keyspace notification. This is necessary because modules subscribed to
* keyspace notifications can block the client from within the notification
* callback. */
void initDeferredReplyBuffer(client *c) {
if (moduleNotifyKeyspaceSubscribersCnt() == 0) return;
if (c->deferred_reply == NULL) c->deferred_reply = listCreate();
if (!isDeferredReplyEnabled(c)) c->deferred_reply_bytes = 0;
}
static void resetDeferredReplyBuffer(client *c) {
listEmpty(c->deferred_reply);
c->deferred_reply_bytes = ULLONG_MAX;
}
/* Move the client deferred reply buffer into the client reply buffer and put the client
* in the pending write queue. */
void commitDeferredReplyBuffer(client *c, int skip_if_blocked) {
if (skip_if_blocked && c->flag.blocked) return;
if (!isDeferredReplyEnabled(c) || (c->deferred_reply && listLength(c->deferred_reply) == 0)) {
resetDeferredReplyBuffer(c);
return;
}
listJoin(c->reply, c->deferred_reply);
c->reply_bytes += c->deferred_reply_bytes;
resetDeferredReplyBuffer(c);
if (prepareClientToWrite(c) != C_OK) {
return;
}
/* We call it here because this function may affect the reply
* buffer offset (see function comment) */
reqresSaveClientReplyOffset(c);
}
/* Append 'src' client output buffers into 'dst' client output buffers.
* This function clears the output buffers of 'src' */
void AddReplyFromClient(client *dst, client *src) {
@@ -1582,7 +1650,6 @@ void unlinkClient(client *c) {
/* Remove from the list of pending writes if needed. */
if (c->flag.pending_write) {
serverAssert(&c->clients_pending_write_node.next != NULL || &c->clients_pending_write_node.prev != NULL);
if (c->io_write_state == CLIENT_IDLE) {
listUnlinkNode(server.clients_pending_write, &c->clients_pending_write_node);
} else {
@@ -1736,6 +1803,7 @@ void freeClient(client *c) {
c->reply = NULL;
zfree_with_size(c->buf, c->buf_usable_size);
c->buf = NULL;
listRelease(c->deferred_reply);
freeClientArgv(c);
freeClientOriginalArgv(c);
@@ -2578,6 +2646,8 @@ void resetClient(client *c) {
c->slot = -1;
c->flag.executing_command = 0;
c->flag.replication_done = 0;
c->flag.buffered_reply = 0;
c->flag.keyspace_notified = 0;
c->net_output_bytes_curr_cmd = 0;
/* Make sure the duration has been recorded to some command. */
@@ -2588,6 +2658,7 @@ void resetClient(client *c) {
if (c->deferred_reply_errors) listRelease(c->deferred_reply_errors);
c->deferred_reply_errors = NULL;
commitDeferredReplyBuffer(c, 1);
/* We clear the ASKING flag as well if we are not inside a MULTI, and
* if what we just executed is not the ASKING command itself. */
@@ -2764,24 +2835,27 @@ static void setProtocolError(const char *errstr, client *c) {
/* Sample some protocol to given an idea about what was inside. */
char buf[256];
buf[0] = '\0';
if (c->querybuf && sdslen(c->querybuf) - c->qb_pos < PROTO_DUMP_LEN) {
snprintf(buf, sizeof(buf), "Query buffer during protocol error: '%s'", c->querybuf + c->qb_pos);
} else if (c->querybuf) {
snprintf(buf, sizeof(buf), "Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'",
PROTO_DUMP_LEN / 2, c->querybuf + c->qb_pos, sdslen(c->querybuf) - c->qb_pos - PROTO_DUMP_LEN,
PROTO_DUMP_LEN / 2, c->querybuf + sdslen(c->querybuf) - PROTO_DUMP_LEN / 2);
}
if (server.hide_user_data_from_log) {
snprintf(buf, sizeof(buf), "*redacted*");
} else {
if (c->querybuf && sdslen(c->querybuf) - c->qb_pos < PROTO_DUMP_LEN) {
snprintf(buf, sizeof(buf), "'%s'", c->querybuf + c->qb_pos);
} else if (c->querybuf) {
snprintf(buf, sizeof(buf), "'%.*s' (... more %zu bytes ...) '%.*s'",
PROTO_DUMP_LEN / 2, c->querybuf + c->qb_pos, sdslen(c->querybuf) - c->qb_pos - PROTO_DUMP_LEN,
PROTO_DUMP_LEN / 2, c->querybuf + sdslen(c->querybuf) - PROTO_DUMP_LEN / 2);
}
/* Remove non printable chars. */
char *p = buf;
while (*p != '\0') {
if (!isprint(*p)) *p = '.';
p++;
/* Remove non printable chars. */
char *p = buf;
while (*p != '\0') {
if (!isprint(*p)) *p = '.';
p++;
}
}
/* Log all the client and protocol info. */
int loglevel = (c->flag.primary) ? LL_WARNING : LL_VERBOSE;
serverLog(loglevel, "Protocol error (%s) from client: %s. %s", errstr, client, buf);
serverLog(loglevel, "Protocol error (%s) from client: %s. Query buffer: %s", errstr, client, buf);
sdsfree(client);
}
c->flag.close_after_reply = 1;
@@ -2854,30 +2928,30 @@ void processMultibulkBuffer(client *c) {
*
* Calculation: For multi bulk buffer, we accumulate four factors, namely;
*
* 1) multibulklen_slen + 1
* 1) multibulklen_slen + 3
* Cumulative string length (and not the value of) of multibulklen,
* including +1 from RESP first byte.
* 2) bulklen_slen + c->argc
* including the first "*" byte and last "\r\n" 2 bytes from RESP.
* 2) bulklen_slen + 3
* Cumulative string length (and not the value of) of bulklen,
* including +1 from RESP first byte per argument count.
* including +3 from RESP first "$" byte and last "\r\n" 2 bytes per argument count.
* 3) c->argv_len_sum
* Cumulative string length of all argument vectors.
* 4) c->argc * 4 + 2
* Cumulative string length of all white-spaces, for which there exists a total of
* 4 bytes per argument, plus 2 bytes from the leading '\r\n' from multibulklen.
* 4) c->argc * 2
* Cumulative string length of the arguments' white-spaces, for which there exists a total of
* "\r\n" 2 bytes per argument.
*
* For example;
* Command) SET key value
* RESP) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
*
* 1) String length of "*3" is 2, obtained from (multibulklen_slen + 1).
* 2) String length of "$3" "$3" "$5" is 6, obtained from (bulklen_slen + c->argc).
* 1) String length of "*3\r\n" is 4, obtained from (multibulklen_slen + 3).
* 2) String length of "$3\r\n" "$3\r\n" "$5\r\n" is 12, obtained from (bulklen_slen + 3).
* 3) String length of "SET" "key" "value" is 11, obtained from (c->argv_len_sum).
* 4) String length of all white-spaces "\r\n" is 14, obtained from (c->argc * 4 + 2).
* 4) String length of the 3 arguments' white-spaces "\r\n" is 6, obtained from (c->argc * 2).
*
* The 1st component is calculated within the below line.
* */
c->net_input_bytes_curr_cmd += (multibulklen_slen + 1);
c->net_input_bytes_curr_cmd += (multibulklen_slen + 3);
}
serverAssertWithInfo(c, NULL, c->multibulklen > 0);
@@ -2942,9 +3016,8 @@ void processMultibulkBuffer(client *c) {
}
}
c->bulklen = ll;
/* Per-slot network bytes-in calculation, 2nd component.
* c->argc portion is deferred, as it may not have been fully populated at this point. */
c->net_input_bytes_curr_cmd += bulklen_slen;
/* Per-slot network bytes-in calculation, 2nd component. */
c->net_input_bytes_curr_cmd += (bulklen_slen + 3);
}
/* Read bulk argument */
@@ -2982,9 +3055,8 @@ void processMultibulkBuffer(client *c) {
/* We're done when c->multibulk == 0 */
if (c->multibulklen == 0) {
/* Per-slot network bytes-in calculation, 3rd and 4th components.
* Here, the deferred c->argc from 2nd component is added, resulting in c->argc * 5 instead of * 4. */
c->net_input_bytes_curr_cmd += (c->argv_len_sum + (c->argc * 5 + 2));
/* Per-slot network bytes-in calculation, 3rd and 4th components. */
c->net_input_bytes_curr_cmd += (c->argv_len_sum + (c->argc * 2));
c->read_flags |= READ_FLAGS_PARSING_COMPLETED;
}
}
@@ -3484,7 +3556,7 @@ static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide
listRewind(server.clients, &li);
while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln);
if (!clientMatchesFilter(client, *client_filter)) continue;
if (!clientMatchesFilter(client, client_filter)) continue;
o = catClientInfoString(o, client, hide_user_data);
o = sdscatlen(o, "\n", 1);
}
@@ -3687,15 +3759,15 @@ static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter)
return C_OK;
}
static int clientMatchesFilter(client *client, clientFilter client_filter) {
static int clientMatchesFilter(client *client, clientFilter *client_filter) {
/* Check each filter condition and return false if the client does not match. */
if (client_filter.addr && strcmp(getClientPeerId(client), client_filter.addr) != 0) return 0;
if (client_filter.laddr && strcmp(getClientSockname(client), client_filter.laddr) != 0) return 0;
if (client_filter.type != -1 && getClientType(client) != client_filter.type) return 0;
if (client_filter.ids && !intsetFind(client_filter.ids, client->id)) return 0;
if (client_filter.user && client->user != client_filter.user) return 0;
if (client_filter.skipme && client == server.current_client) return 0;
if (client_filter.max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter.max_age) return 0;
if (client_filter->addr && strcmp(getClientPeerId(client), client_filter->addr) != 0) return 0;
if (client_filter->laddr && strcmp(getClientSockname(client), client_filter->laddr) != 0) return 0;
if (client_filter->type != -1 && getClientType(client) != client_filter->type) return 0;
if (client_filter->ids && !intsetFind(client_filter->ids, client->id)) return 0;
if (client_filter->user && client->user != client_filter->user) return 0;
if (client_filter->skipme && client == server.current_client) return 0;
if (client_filter->max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter->max_age) return 0;
/* If all conditions are satisfied, the client matches the filter. */
return 1;
@@ -3888,7 +3960,7 @@ void clientKillCommand(client *c) {
listRewind(server.clients, &li);
while ((ln = listNext(&li)) != NULL) {
client *client = listNodeValue(ln);
if (!clientMatchesFilter(client, client_filter)) continue;
if (!clientMatchesFilter(client, &client_filter)) continue;
/* Kill it. */
if (c == client) {
@@ -4477,10 +4549,10 @@ void rewriteClientCommandVector(client *c, int argc, ...) {
/* Completely replace the client command vector with the provided one. */
void replaceClientCommandVector(client *c, int argc, robj **argv) {
int j;
backupAndUpdateClientArgv(c, argc, argv);
c->argv_len_sum = 0;
for (j = 0; j < c->argc; j++)
c->flag.buffered_reply = 0;
for (int j = 0; j < c->argc; j++)
if (c->argv[j]) c->argv_len_sum += getStringObjectLen(c->argv[j]);
c->cmd = lookupCommandOrOriginal(c->argv, c->argc);
serverAssertWithInfo(c, NULL, c->cmd != NULL);
@@ -4511,6 +4583,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
/* If this is the command name make sure to fix c->cmd. */
if (i == 0) {
c->flag.buffered_reply = 0;
c->cmd = lookupCommandOrOriginal(c->argv, c->argc);
serverAssertWithInfo(c, NULL, c->cmd != NULL);
}
@@ -4534,10 +4607,15 @@ size_t getClientOutputBufferMemoryUsage(client *c) {
repl_node_num = last->id - cur->id + 1;
}
return repl_buf_size + (repl_node_size * repl_node_num);
} else {
size_t list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
return c->reply_bytes + (list_item_size * listLength(c->reply));
}
size_t list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
size_t usage = c->reply_bytes + (list_item_size * listLength(c->reply));
if (isDeferredReplyEnabled(c)) {
usage += c->deferred_reply_bytes +
(list_item_size * listLength(c->deferred_reply));
}
return usage;
}
/* Returns the total client's memory usage.
@@ -4621,6 +4699,11 @@ int checkClientOutputBufferLimits(client *c) {
int soft = 0, hard = 0, class;
unsigned long used_mem = getClientOutputBufferMemoryUsage(c);
/* For unauthenticated clients which were also never authenticated before the output buffer is limited to prevent
* them from abusing it by not reading the replies */
if (used_mem > REPLY_BUFFER_SIZE_UNAUTHENTICATED_CLIENT && authRequired(c) && !clientEverAuthenticated(c))
return 1;
class = getClientType(c);
/* For the purpose of output buffer limiting, primaries are handled
* like normal clients. */
+14 -1
View File
@@ -107,12 +107,25 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
robj *chanobj, *eventobj;
int len = -1;
char buf[24];
client *c = server.executing_client;
debugServerAssert(moduleNotifyKeyspaceSubscribersCnt() == 0 ||
(type & (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_STREAM)) == 0 ||
c == NULL ||
c->cmd == NULL ||
(c->cmd->flags & CMD_WRITE) == 0 ||
c->flag.buffered_reply == 0 ||
c->flag.keyspace_notified == 1 ||
c->id == UINT64_MAX || // AOF client
getClientType(c) != CLIENT_TYPE_NORMAL);
/* If any modules are interested in events, notify the module system now.
* This bypasses the notifications configuration, but the module engine
* will only call event subscribers if the event type matches the types
* they are interested in. */
moduleNotifyKeyspaceEvent(type, event, key, dbid);
if (c) {
c->flag.keyspace_notified = 1;
commitDeferredReplyBuffer(c, 1);
}
/* If notifications for this class of events are off, return ASAP. */
if (!(server.notify_keyspace_events & type)) return;
+17 -9
View File
@@ -1194,7 +1194,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
asize = sizeof(*o) + hashtableMemUsage(ht);
while (hashtableNext(&iter, &next) && samples < sample_size) {
elesize += hashTypeEntryAllocSize(next);
elesize += hashTypeEntryMemUsage(next);
samples++;
}
hashtableResetIterator(&iter);
@@ -1242,28 +1242,36 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
if (s->cgroups) {
raxStart(&ri, s->cgroups);
raxSeek(&ri, "^", NULL, 0);
while (raxNext(&ri)) {
samples = 0;
elesize = 0;
while (samples < sample_size && raxNext(&ri)) {
streamCG *cg = ri.data;
asize += sizeof(*cg);
asize += raxAllocSize(cg->pel);
asize += sizeof(streamNACK) * raxSize(cg->pel);
elesize += sizeof(*cg);
elesize += raxAllocSize(cg->pel);
elesize += sizeof(streamNACK) * raxSize(cg->pel);
/* For each consumer we also need to add the basic data
* structures and the PEL memory usage. */
raxIterator cri;
raxStart(&cri, cg->consumers);
raxSeek(&cri, "^", NULL, 0);
while (raxNext(&cri)) {
size_t inner_samples = 0;
size_t inner_elesize = 0;
while (inner_samples < sample_size && raxNext(&cri)) {
streamConsumer *consumer = cri.data;
asize += sizeof(*consumer);
asize += sdslen(consumer->name);
asize += raxAllocSize(consumer->pel);
inner_elesize += sizeof(*consumer);
inner_elesize += sdslen(consumer->name);
inner_elesize += raxAllocSize(consumer->pel);
/* Don't count NACKs again, they are shared with the
* consumer group PEL. */
inner_samples++;
}
raxStop(&cri);
if (inner_samples) elesize += (double)inner_elesize / inner_samples * raxSize(cg->consumers);
samples++;
}
raxStop(&ri);
if (samples) asize += (double)elesize / samples * raxSize(s->cgroups);
}
} else if (o->type == OBJ_MODULE) {
asize = moduleGetMemUsage(key, o, sample_size, dbid);
+1
View File
@@ -1187,6 +1187,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
rax_free(tofree);
rax->numnodes--;
if (h->iskey || (!h->iscompr && h->size != 1)) break;
if (comprsize + h->size > RAX_NODE_MAX_SIZE) break;
}
debugnode("New node", new);
+5
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "lzf.h" /* LZF compression library */
+5
View File
@@ -7,6 +7,11 @@
* the top-level directory.
* ==========================================================================
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#define VALKEYMODULE_CORE_MODULE
#include "server.h"
+38 -30
View File
@@ -27,7 +27,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "cluster.h"
@@ -46,6 +50,8 @@
#include <sys/stat.h>
#include <ctype.h>
void cleanupTransferResources(void);
void replicationAbortSyncTransfer(void);
void replicationDiscardCachedPrimary(void);
void replicationResurrectCachedPrimary(connection *conn);
void replicationResurrectProvisionalPrimary(void);
@@ -1152,7 +1158,7 @@ void syncCommand(client *c) {
/* Setup the replica as one waiting for BGSAVE to start. The following code
* paths will change the state if we handle the replica differently. */
c->repl_data->repl_state = REPLICA_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay) connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
if (server.repl_disable_tcp_nodelay) anetDisableTcpNoDelay(NULL, c->conn->fd); /* Non critical if it fails. */
c->repl_data->repldbfd = -1;
c->flag.replica = 1;
listAddNodeTail(server.replicas, c);
@@ -1985,7 +1991,7 @@ void replicationCreatePrimaryClientWithHandler(connection *conn, int dbid, Conne
* execution is done. This is the reason why we allow blocking the replication
* connection. */
server.primary->flag.primary = 1;
server.primary->flag.authenticated = 1;
clientSetUser(server.primary, NULL, 1);
/* Allocate a private query buffer for the primary client instead of using the shared query buffer.
* This is done because the primary's query buffer data needs to be preserved for my sub-replicas to use. */
@@ -2653,12 +2659,7 @@ void replicationAbortDualChannelSyncTransfer(void) {
connClose(server.repl_rdb_transfer_s);
server.repl_rdb_transfer_s = NULL;
}
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
if (server.repl_transfer_fd != -1) {
close(server.repl_transfer_fd);
server.repl_transfer_fd = -1;
}
cleanupTransferResources();
server.repl_rdb_channel_state = REPL_DUAL_CHANNEL_STATE_NONE;
server.repl_provisional_primary.read_reploff = 0;
server.repl_provisional_primary.reploff = 0;
@@ -2867,14 +2868,8 @@ error:
connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
}
if (server.repl_rdb_transfer_s) {
connClose(server.repl_rdb_transfer_s);
server.repl_rdb_transfer_s = NULL;
}
if (server.repl_transfer_fd != -1) close(server.repl_transfer_fd);
server.repl_transfer_fd = -1;
server.repl_state = REPL_STATE_CONNECT;
replicationAbortDualChannelSyncTransfer();
server.repl_state = REPL_STATE_CONNECT;
}
/* Replication: Replica side.
@@ -3816,7 +3811,8 @@ void syncWithPrimary(connection *conn) {
server.repl_rdb_transfer_s = connCreate(connTypeOfReplication());
if (connConnect(server.repl_rdb_transfer_s, server.primary_host, server.primary_port, server.bind_source_addr,
dualChannelFullSyncWithPrimary) == C_ERR) {
serverLog(LL_WARNING, "Unable to connect to Primary: %s", connGetLastError(server.repl_transfer_s));
dualChannelServerLog(LL_WARNING, "Unable to connect to Primary: %s",
connGetLastError(server.repl_transfer_s));
connClose(server.repl_rdb_transfer_s);
server.repl_rdb_transfer_s = NULL;
goto error;
@@ -3856,10 +3852,7 @@ error:
connClose(server.repl_rdb_transfer_s);
server.repl_rdb_transfer_s = NULL;
}
if (server.repl_transfer_fd != -1) close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile) zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
replicationAbortSyncTransfer();
server.repl_state = REPL_STATE_CONNECT;
return;
@@ -3886,11 +3879,33 @@ int connectWithPrimary(void) {
return C_OK;
}
/* In disk-based replication, replica will open a temp db file to store the RDB file.
* Before entering the REPL_STATE_TRANSFER or after entering the REPL_STATE_TRANSFER,
* if an error occurs, we need to clean up related resources, such as closing the tmp
* file fd and deleting the temp file.
*
* Noted that repl_transfer_fd and repl_transfer_tmpfile should be set/unset together. */
void cleanupTransferResources(void) {
if (server.repl_transfer_fd == -1) {
serverAssert(server.repl_transfer_tmpfile == NULL);
return;
}
serverAssert(server.repl_transfer_tmpfile != NULL);
close(server.repl_transfer_fd);
bg_unlink(server.repl_transfer_tmpfile);
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
}
/* This function can be called when a non blocking connection is currently
* in progress to undo it.
* Never call this function directly, use cancelReplicationHandshake() instead.
*/
void undoConnectWithPrimary(void) {
if (server.repl_transfer_s == NULL) return;
connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
}
@@ -3899,15 +3914,8 @@ void undoConnectWithPrimary(void) {
* Never call this function directly, use cancelReplicationHandshake() instead.
*/
void replicationAbortSyncTransfer(void) {
serverAssert(server.repl_state == REPL_STATE_TRANSFER);
undoConnectWithPrimary();
if (server.repl_transfer_fd != -1) {
close(server.repl_transfer_fd);
bg_unlink(server.repl_transfer_tmpfile);
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
}
cleanupTransferResources();
}
/* This function aborts a non blocking replication attempt if there is one
@@ -4329,7 +4337,7 @@ void establishPrimaryConnection(void) {
connSetPrivateData(server.primary->conn, server.primary);
server.primary->flag.close_after_reply = 0;
server.primary->flag.close_asap = 0;
server.primary->flag.authenticated = 1;
clientSetUser(server.primary, NULL, 1);
server.primary->last_interaction = server.unixtime;
server.repl_state = REPL_STATE_CONNECTED;
server.repl_down_since = 0;
+24 -10
View File
@@ -1,5 +1,5 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -217,21 +217,35 @@ static void engineTeardownModuleCtx(scriptingEngine *e) {
compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
subsystemType type,
const char *code,
size_t code_len,
size_t timeout,
size_t *out_num_compiled_functions,
robj **err) {
serverAssert(type == VMSE_EVAL || type == VMSE_FUNCTION);
compiledFunction **functions = NULL;
engineSetupModuleCtx(engine, NULL);
if (engine->impl.methods.version == 1) {
functions = engine->impl.methods.compile_code_v1(
engine->module_ctx,
engine->impl.ctx,
type,
code,
timeout,
out_num_compiled_functions,
err);
} else {
/* Assume versions greater than 1 use updated interface. */
functions = engine->impl.methods.compile_code(
engine->module_ctx,
engine->impl.ctx,
type,
code,
code_len,
timeout,
out_num_compiled_functions,
err);
}
compiledFunction **functions = engine->impl.methods.compile_code(
engine->module_ctx,
engine->impl.ctx,
type,
code,
timeout,
out_num_compiled_functions,
err);
engineTeardownModuleCtx(engine);
+1
View File
@@ -55,6 +55,7 @@ ValkeyModule *scriptingEngineGetModule(scriptingEngine *engine);
compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
subsystemType type,
const char *code,
size_t code_len,
size_t timeout,
size_t *out_num_compiled_functions,
robj **err);
+22
View File
@@ -95,6 +95,28 @@ static inline unsigned char sdsType(const_sds s) {
return flags & SDS_TYPE_MASK;
}
/* Returns a user data bit stored in the SDS header by sdsSetAuxBit. The bit
* index is 0-4. Returns 0 or 1. Always returns 0 for SDS_TYPE_5. */
static inline int sdsGetAuxBit(const_sds s, int bit) {
unsigned char flags = s[-1];
return sdsType(s) == SDS_TYPE_5 ? 0 : flags >> (SDS_TYPE_BITS + bit);
}
/* Stores a bit in an unused area in the SDS header, except for SDS_TYPE_5. The
* bit index is 0-4. The value is 0 or 1. The aux bits are lost if the SDS is
* auto-resized. This is only for special uses like immutable SDS embedded in
* other structures. */
static inline void sdsSetAuxBit(sds s, int bit, int value) {
if (sdsType(s) == SDS_TYPE_5) return;
unsigned char flags = s[-1];
if (value) {
flags |= 1 << (SDS_TYPE_BITS + bit);
} else {
flags &= ~(1 << (SDS_TYPE_BITS + bit));
}
s[-1] = (char)flags;
}
static inline size_t sdslen(const_sds s) {
switch (sdsType(s)) {
case SDS_TYPE_5: return SDS_TYPE_5_LEN(s[-1]);
+9 -1
View File
@@ -26,7 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "monotonic.h"
#include "cluster.h"
@@ -2802,6 +2806,7 @@ void initServer(void) {
server.reply_buffer_peak_reset_time = REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME;
server.reply_buffer_resizing_enabled = 1;
server.client_mem_usage_buckets = NULL;
server.debug_client_enforce_reply_list = 0;
resetReplicationBuffer();
/* Make sure the locale is set on startup based on the config file. */
@@ -3724,6 +3729,8 @@ void call(client *c, int flags) {
* re-processing and unblock the client.*/
c->flag.executing_command = 1;
c->flag.buffered_reply = 0;
c->flag.keyspace_notified = 0;
/* Setting the CLIENT_REPROCESSING_COMMAND flag so that during the actual
* processing of the command proc, the client is aware that it is being
* re-processed. */
@@ -4063,6 +4070,7 @@ int processCommand(client *c) {
}
}
c->cmd = c->lastcmd = c->realcmd = cmd;
c->flag.buffered_reply = 0;
sds err;
if (!commandCheckExistence(c, &err)) {
rejectCommandSds(c, err);
+28 -15
View File
@@ -199,7 +199,8 @@ struct hdr_histogram;
#define PROTO_REPLY_MIN_BYTES (1024) /* the lower limit on reply buffer size */
#define REDIS_AUTOSYNC_BYTES (1024 * 1024 * 4) /* Sync file every 4MB. */
#define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */
#define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */
#define REPLY_BUFFER_SIZE_UNAUTHENTICATED_CLIENT 1024 /* 1024 bytes */
/* When configuring the server eventloop, we setup it so that the total number
* of file descriptors we can handle are server.maxclients + RESERVED_FDS +
@@ -1088,6 +1089,7 @@ typedef struct ClientFlags {
uint64_t reprocessing_command : 1; /* The client is re-processing the command. */
uint64_t replication_done : 1; /* Indicate that replication has been done on the client */
uint64_t authenticated : 1; /* Indicate a client has successfully authenticated */
uint64_t ever_authenticated : 1; /* Indicate a client was ever successfully authenticated during it's lifetime */
uint64_t protected_rdb_channel : 1; /* Dual channel replication sync: Protects the RDB client from premature \
* release during full sync. This flag is used to ensure that the RDB client, which \
* references the first replication data block required by the replica, is not \
@@ -1108,7 +1110,11 @@ typedef struct ClientFlags {
* flag, we won't cache the primary in freeClient. */
uint64_t fake : 1; /* This is a fake client without a real connection. */
uint64_t import_source : 1; /* This client is importing data to server and can visit expired key. */
uint64_t reserved : 4; /* Reserved for future use */
uint64_t buffered_reply : 1; /* Indicates the reply for the current command was buffered, either in client::reply
or client::buf. */
uint64_t keyspace_notified : 1; /* Indicates that a keyspace notification was triggered during the execution of the
current command. */
uint64_t reserved : 1; /* Reserved for future use */
} ClientFlags;
typedef struct ClientPubSubData {
@@ -1259,13 +1265,16 @@ typedef struct client {
dictEntry *cur_script; /* Cached pointer to the dictEntry of the script being executed. */
user *user; /* User associated with this connection */
time_t obuf_soft_limit_reached_time;
list *deferred_reply_errors; /* Used for module thread safe contexts. */
robj *name; /* As set by CLIENT SETNAME. */
robj *lib_name; /* The client library name as set by CLIENT SETINFO. */
robj *lib_ver; /* The client library version as set by CLIENT SETINFO. */
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
time_t ctime; /* Client creation time. */
list *deferred_reply_errors; /* Used for module thread safe contexts. */
robj *name; /* As set by CLIENT SETNAME. */
robj *lib_name; /* The client library name as set by CLIENT SETINFO. */
robj *lib_ver; /* The client library version as set by CLIENT SETINFO. */
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
time_t ctime; /* Client creation time. */
list *deferred_reply; /* List of reply objects to be sent to the client, typically after
the client has been unblocked. */
unsigned long long deferred_reply_bytes; /* Total bytes of objects in the blocked client pending list.*/
#ifdef LOG_REQ_RES
clientReqResInfo reqres;
#endif
@@ -1663,7 +1672,7 @@ struct valkeyServer {
int enable_debug_cmd; /* Enable DEBUG commands, see PROTECTED_ACTION_ALLOWED_* */
int enable_module_cmd; /* Enable MODULE commands, see PROTECTED_ACTION_ALLOWED_* */
int enable_debug_assert; /* Enable debug asserts */
int debug_client_enforce_reply_list; /* Force client to always use the reply list */
/* RDB / AOF loading information */
volatile sig_atomic_t loading; /* We are loading data from disk if true */
volatile sig_atomic_t async_loading; /* We are loading data without blocking the db being served */
@@ -2084,6 +2093,7 @@ struct valkeyServer {
unsigned long cluster_blacklist_ttl; /* Duration in seconds that a node is denied re-entry into
* the cluster after it is forgotten with CLUSTER FORGET. */
int cluster_slot_stats_enabled; /* Cluster slot usage statistics tracking enabled. */
mstime_t cluster_mf_timeout; /* Milliseconds to do a manual failover. */
/* Debug config that goes along with cluster_drop_packet_filter. When set, the link is closed on packet drop. */
uint32_t debug_cluster_close_link_on_packet_drop : 1;
/* Debug config to control the random ping. When set, we will disable the random ping in clusterCron. */
@@ -2584,8 +2594,6 @@ void populateCommandLegacyRangeSpec(struct serverCommand *c);
long long ustime(void);
mstime_t mstime(void);
mstime_t commandTimeSnapshot(void);
void getRandomHexChars(char *p, size_t len);
void getRandomBytes(unsigned char *p, size_t len);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
void exitFromChild(int retcode);
long long serverPopcount(void *s, long count);
@@ -2645,6 +2653,8 @@ void resetClientIOState(client *c);
void freeClientOriginalArgv(client *c);
void freeClientArgv(client *c);
void sendReplyToClient(connection *conn);
int isDeferredReplyEnabled(client *c);
void initDeferredReplyBuffer(client *c);
void *addReplyDeferredLen(client *c);
void setDeferredArrayLen(client *c, void *node, long length);
void setDeferredMapLen(client *c, void *node, long length);
@@ -2662,6 +2672,7 @@ void addReplyBool(client *c, int b);
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
void addReplyProto(client *c, const char *s, size_t len);
void AddReplyFromClient(client *c, client *src);
void commitDeferredReplyBuffer(client *c, int skip_if_blocked);
void addReplyBulk(client *c, robj *obj);
void addReplyBulkCString(client *c, const char *s);
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
@@ -2755,6 +2766,7 @@ void initSharedQueryBuf(void);
void freeSharedQueryBuf(void);
client *lookupClientByID(uint64_t id);
int authRequired(client *c);
void clientSetUser(client *c, user *u, int authenticated);
void putClientInPendingWriteQueue(client *c);
client *createCachedResponseClient(int resp);
void deleteCachedResponseClient(client *recording_client);
@@ -2816,7 +2828,7 @@ void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
robj *listTypeDup(robj *o);
void listTypeDelRange(robj *o, long start, long stop);
void popGenericCommand(client *c, int where);
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted);
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int *deleted);
typedef enum {
LIST_CONV_AUTO,
LIST_CONV_GROWING,
@@ -3250,11 +3262,11 @@ robj *setTypeDup(robj *o);
#define HASH_SET_TAKE_VALUE (1 << 1)
#define HASH_SET_COPY 0
typedef struct hashTypeEntry hashTypeEntry;
typedef void hashTypeEntry;
hashTypeEntry *hashTypeCreateEntry(sds field, sds value);
sds hashTypeEntryGetField(const hashTypeEntry *entry);
sds hashTypeEntryGetValue(const hashTypeEntry *entry);
size_t hashTypeEntryAllocSize(hashTypeEntry *entry);
size_t hashTypeEntryMemUsage(hashTypeEntry *entry);
hashTypeEntry *hashTypeEntryDefrag(hashTypeEntry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds));
void dismissHashTypeEntry(hashTypeEntry *entry);
void freeHashTypeEntry(hashTypeEntry *entry);
@@ -3526,6 +3538,7 @@ int isInsideYieldingLongCommand(void);
void processUnblockedClients(void);
void initClientBlockingState(client *c);
void freeClientBlockingState(client *c);
void resetBlockedClientPendingReply(client *c);
void blockClient(client *c, int btype);
void unblockClient(client *c, int queue_for_reprocessing);
void unblockClientOnTimeout(client *c);
+2 -10
View File
@@ -326,6 +326,8 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
return;
}
serverLog(LL_VERBOSE, "Accepted %s:%d", cip, cport);
if (server.tcpkeepalive) anetKeepAlive(NULL, cfd, server.tcpkeepalive);
acceptCommonHandler(connCreateAcceptedSocket(cfd, NULL), flags, cip);
}
}
@@ -462,16 +464,6 @@ int connNonBlock(connection *conn) {
return anetNonBlock(NULL, conn->fd);
}
int connEnableTcpNoDelay(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetEnableTcpNoDelay(NULL, conn->fd);
}
int connDisableTcpNoDelay(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetDisableTcpNoDelay(NULL, conn->fd);
}
int connKeepAlive(connection *conn, int interval) {
if (conn->fd == -1) return C_ERR;
return anetKeepAlive(NULL, conn->fd, interval);
+204 -43
View File
@@ -26,55 +26,200 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include <math.h>
#include <stdbool.h>
/*-----------------------------------------------------------------------------
* Hash Entry API
*----------------------------------------------------------------------------*/
struct hashTypeEntry {
sds value;
char field_offset;
char field_data[];
};
/* The hashTypeEntry pointer is the field sds. We encode the entry layout type
* in the field SDS header. Field type SDS_TYPE_5 doesn't have any spare bits to
* encode this so we use it only for the first layout type.
*
* Entry with embedded value, used for small sizes. The value is stored as
* SDS_TYPE_8. The field can use any SDS type.
*
* +--------------+---------------+
* | field | value |
* | hdr "foo" \0 | hdr8 "bar" \0 |
* +------^-------+---------------+
* |
* |
* entry pointer = field sds
*
* Entry with value pointer, used for larger fields and values. The field is SDS
* type 8 or higher.
*
* +-------+--------------+
* | value | field |
* | ptr | hdr "foo" \0 |
* +-------+------^-------+
* |
* |
* entry pointer = field sds
*/
/* The maximum allocation size we want to use for entries with embedded
* values. */
#define EMBED_VALUE_MAX_ALLOC_SIZE 128
/* SDS aux flag. If set, it indicates that the entry has an embedded value
* pointer located in memory before the embedded field. If unset, the entry
* instead has an embedded value located after the embedded field. */
#define FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR 0
static inline bool entryHasValuePtr(const hashTypeEntry *entry) {
return sdsGetAuxBit(entry, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR);
}
/* Returns the location of a pointer to a separately allocated value. Only for
* an entry without an embedded value. */
static sds *hashTypeEntryGetValueRef(const hashTypeEntry *entry) {
serverAssert(entryHasValuePtr(entry));
char *field_data = sdsAllocPtr(entry);
field_data -= sizeof(sds *);
return (sds *)field_data;
}
/* takes ownership of value, does not take ownership of field */
hashTypeEntry *hashTypeCreateEntry(sds field, sds value) {
size_t field_len = sdslen(field);
char field_sds_type = sdsReqType(field_len);
int field_sds_type = sdsReqType(field_len);
size_t field_size = sdsReqSize(field_len, field_sds_type);
size_t total_size = sizeof(hashTypeEntry) + field_size;
hashTypeEntry *entry = zmalloc(total_size);
entry->value = value;
entry->field_offset = sdsHdrSize(field_sds_type);
sdswrite(entry->field_data, field_size, field_sds_type, field, field_len);
return entry;
size_t value_len = sdslen(value);
size_t value_size = sdsReqSize(value_len, SDS_TYPE_8);
sds embedded_field_sds;
if (field_size + value_size <= EMBED_VALUE_MAX_ALLOC_SIZE) {
/* Embed field and value. Value is fixed to SDS_TYPE_8. Unused
* allocation space is recorded in the embedded value's SDS header.
*
* +--------------+---------------+
* | field | value |
* | hdr "foo" \0 | hdr8 "bar" \0 |
* +--------------+---------------+
*/
size_t min_size = field_size + value_size;
size_t buf_size;
char *buf = zmalloc_usable(min_size, &buf_size);
embedded_field_sds = sdswrite(buf, field_size, field_sds_type, field, field_len);
sdswrite(buf + field_size, buf_size - field_size, SDS_TYPE_8, value, value_len);
/* Field sds aux bits are zero, which we use for this entry encoding. */
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR, 0);
serverAssert(!entryHasValuePtr(embedded_field_sds));
sdsfree(value);
} else {
/* Embed field, but not value. Field must be >= SDS_TYPE_8 to encode to
* indicate this type of entry.
*
* +-------+---------------+
* | value | field |
* | ptr | hdr8 "foo" \0 |
* +-------+---------------+
*/
char field_sds_type = sdsReqType(field_len);
if (field_sds_type == SDS_TYPE_5) field_sds_type = SDS_TYPE_8;
field_size = sdsReqSize(field_len, field_sds_type);
size_t alloc_size = sizeof(sds *) + field_size;
char *buf = zmalloc(alloc_size);
*(sds *)buf = value;
embedded_field_sds = sdswrite(buf + sizeof(sds *), field_size, field_sds_type, field, field_len);
/* Store the entry encoding type in sds aux bits. */
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR, 1);
serverAssert(entryHasValuePtr(embedded_field_sds));
}
return (void *)embedded_field_sds;
}
/* The entry pointer is the field sds, but that's an implementation detail. */
sds hashTypeEntryGetField(const hashTypeEntry *entry) {
const char *field = entry->field_data + entry->field_offset;
return (sds)field;
return (sds)entry;
}
sds hashTypeEntryGetValue(const hashTypeEntry *entry) {
return entry->value;
if (entryHasValuePtr(entry)) {
return *hashTypeEntryGetValueRef(entry);
} else {
/* Skip field content, field null terminator and value sds8 hdr. */
size_t offset = sdslen(entry) + 1 + sdsHdrSize(SDS_TYPE_8);
return (char *)entry + offset;
}
}
/* frees previous value, takes ownership of new value */
static void hashTypeEntryReplaceValue(hashTypeEntry *entry, sds value) {
sdsfree(entry->value);
entry->value = value;
/* Returns the address of the entry allocation. */
static void *hashTypeEntryAllocPtr(hashTypeEntry *entry) {
char *buf = sdsAllocPtr(entry);
if (entryHasValuePtr(entry)) {
buf -= sizeof(sds *);
}
return buf;
}
/* Returns allocation size of hashTypeEntry and data owned by hashTypeEntry,
* even if not embedded in the same allocation. */
size_t hashTypeEntryAllocSize(hashTypeEntry *entry) {
size_t size = zmalloc_usable_size(entry);
size += sdsAllocSize(entry->value);
return size;
/* Frees previous value, takes ownership of new value, returns entry (may be
* reallocated). */
static hashTypeEntry *hashTypeEntryReplaceValue(hashTypeEntry *entry, sds value) {
sds field = (sds)entry;
size_t field_size = sdsHdrSize(sdsType(field)) + sdsalloc(field) + 1;
size_t value_len = sdslen(value);
size_t value_size = sdsReqSize(value_len, SDS_TYPE_8);
if (!entryHasValuePtr(entry)) {
/* Reuse the allocation if the new value fits and leaves no more than
* 25% unused space after replacing the value. */
char *alloc_ptr = sdsAllocPtr(entry);
size_t required_size = field_size + value_size;
size_t alloc_size;
if (required_size <= EMBED_VALUE_MAX_ALLOC_SIZE &&
required_size <= (alloc_size = hashTypeEntryMemUsage(entry)) &&
required_size >= alloc_size * 3 / 4) {
/* It fits in the allocation and leaves max 25% unused space. */
sdswrite(alloc_ptr + field_size, alloc_size - field_size, SDS_TYPE_8, value, value_len);
sdsfree(value);
return entry;
}
hashTypeEntry *new_entry = hashTypeCreateEntry(hashTypeEntryGetField(entry), value);
freeHashTypeEntry(entry);
return new_entry;
} else {
/* The value pointer is located before the embedded field. */
if (field_size + value_size <= EMBED_VALUE_MAX_ALLOC_SIZE) {
/* Convert to entry with embedded value. */
hashTypeEntry *new_entry = hashTypeCreateEntry(field, value);
freeHashTypeEntry(entry);
return new_entry;
} else {
/* Not embedded value. */
sds *value_ref = hashTypeEntryGetValueRef(entry);
sdsfree(*value_ref);
*value_ref = value;
return entry;
}
}
}
/* Returns memory usage of a hashTypeEntry, including all allocations owned by
* the hashTypeEntry. */
size_t hashTypeEntryMemUsage(hashTypeEntry *entry) {
size_t mem = 0;
if (entryHasValuePtr(entry)) {
/* Alloc size is not stored in the embedded field. */
mem = zmalloc_usable_size(hashTypeEntryAllocPtr(entry));
mem += sdsAllocSize(*hashTypeEntryGetValueRef(entry));
} else {
/* Remaining alloc size is encoded in the embedded value SDS header. */
sds field = entry;
sds value = (char *)entry + sdslen(field) + 1 + sdsHdrSize(SDS_TYPE_8);
size_t field_size = sdsHdrSize(sdsType(field)) + sdslen(field) + 1;
size_t value_size = sdsHdrSize(SDS_TYPE_8) + sdsalloc(value) + 1;
mem = field_size + value_size;
}
return mem;
}
/* Defragments a hashtable entry (field-value pair) if needed, using the
@@ -85,25 +230,35 @@ size_t hashTypeEntryAllocSize(hashTypeEntry *entry) {
* If the location of the hashTypeEntry changed we return the new location,
* otherwise we return NULL. */
hashTypeEntry *hashTypeEntryDefrag(hashTypeEntry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds)) {
hashTypeEntry *new_entry = defragfn(entry);
if (new_entry) entry = new_entry;
sds new_value = sdsdefragfn(entry->value);
if (new_value) entry->value = new_value;
return new_entry;
if (entryHasValuePtr(entry)) {
sds *value_ref = hashTypeEntryGetValueRef(entry);
sds new_value = sdsdefragfn(*value_ref);
if (new_value) *value_ref = new_value;
}
char *allocation = hashTypeEntryAllocPtr(entry);
char *new_allocation = defragfn(allocation);
if (new_allocation != NULL) {
/* Return the same offset into the new allocation as the entry's offset
* in the old allocation. */
return new_allocation + ((char *)entry - allocation);
}
return NULL;
}
/* Used for releasing memory to OS to avoid unnecessary CoW. Called when we've
* forked and memory won't be used again. See zmadvise_dontneed() */
void dismissHashTypeEntry(hashTypeEntry *entry) {
/* Only dismiss values memory since the field size usually is small. */
dismissSds(entry->value);
if (entryHasValuePtr(entry)) {
dismissSds(*hashTypeEntryGetValueRef(entry));
}
}
void freeHashTypeEntry(hashTypeEntry *entry) {
sdsfree(entry->value);
zfree(entry);
if (entryHasValuePtr(entry)) {
sdsfree(*hashTypeEntryGetValueRef(entry));
}
zfree(hashTypeEntryAllocPtr(entry));
}
/*-----------------------------------------------------------------------------
@@ -321,7 +476,12 @@ int hashTypeSet(robj *o, sds field, sds value, int flags) {
hashtableInsertAtPosition(ht, entry, &position);
} else {
/* exists: replace value */
hashTypeEntryReplaceValue(existing, v);
void *new_entry = hashTypeEntryReplaceValue(existing, v);
if (new_entry != existing) {
/* It has been reallocated. */
int replaced = hashtableReplaceReallocatedEntry(ht, existing, new_entry);
serverAssert(replaced);
}
update = 1;
}
} else {
@@ -645,10 +805,10 @@ void hsetnxCommand(client *c) {
} else {
hashTypeTryConversion(o, c->argv, 2, 3);
hashTypeSet(o, c->argv[2]->ptr, c->argv[3]->ptr, HASH_SET_COPY);
addReply(c, shared.cone);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
server.dirty++;
addReply(c, shared.cone);
}
}
@@ -666,6 +826,10 @@ void hsetCommand(client *c) {
for (i = 2; i < c->argc; i += 2) created += !hashTypeSet(o, c->argv[i]->ptr, c->argv[i + 1]->ptr, HASH_SET_COPY);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
server.dirty += (c->argc - 2) / 2;
/* HMSET (deprecated) and HSET return value is different. */
char *cmdname = c->argv[0]->ptr;
if (cmdname[1] == 's' || cmdname[1] == 'S') {
@@ -675,9 +839,6 @@ void hsetCommand(client *c) {
/* HMSET */
addReply(c, shared.ok);
}
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
server.dirty += (c->argc - 2) / 2;
}
void hincrbyCommand(client *c) {
@@ -709,10 +870,10 @@ void hincrbyCommand(client *c) {
value += incr;
new = sdsfromlonglong(value);
hashTypeSet(o, c->argv[2]->ptr, new, HASH_SET_TAKE_VALUE);
addReplyLongLong(c, value);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH, "hincrby", c->argv[1], c->db->id);
server.dirty++;
addReplyLongLong(c, value);
}
void hincrbyfloatCommand(client *c) {
@@ -752,10 +913,10 @@ void hincrbyfloatCommand(client *c) {
int len = ld2string(buf, sizeof(buf), value, LD_STR_HUMAN);
new = sdsnewlen(buf, len);
hashTypeSet(o, c->argv[2]->ptr, new, HASH_SET_TAKE_VALUE);
addReplyBulkCBuffer(c, buf, len);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH, "hincrbyfloat", c->argv[1], c->db->id);
server.dirty++;
addReplyBulkCBuffer(c, buf, len);
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
* in order to make sure that differences in float precision or formatting
+18 -17
View File
@@ -480,11 +480,11 @@ void pushGenericCommand(client *c, int where, int xx) {
server.dirty++;
}
addReplyLongLong(c, listTypeLength(lobj));
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c, c->db, c->argv[1]);
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
notifyKeyspaceEvent(NOTIFY_LIST, event, c->argv[1], c->db->id);
addReplyLongLong(c, listTypeLength(lobj));
}
/* LPUSH <key> <element> [<element> ...] */
@@ -608,10 +608,10 @@ void lsetCommand(client *c) {
* already handled the growing case in listTypeTryConversionAppend()
* above, so here we just need to try the conversion for shrinking. */
listTypeTryConversion(o, LIST_CONV_SHRINKING, NULL, NULL);
addReply(c, shared.ok);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST, "lset", c->argv[1], c->db->id);
server.dirty++;
addReply(c, shared.ok);
} else {
addReplyErrorObject(c, shared.outofrangeerr);
}
@@ -628,13 +628,14 @@ void lsetCommand(client *c) {
*
* 'deleted' is an optional output argument to get an indication
* if the key got deleted by this function. */
void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long count, int signal, int *deleted) {
void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long count, int *deleted) {
long llen = listTypeLength(o);
long rangelen = (count > llen) ? llen : count;
long rangestart = (where == LIST_HEAD) ? 0 : -rangelen;
long rangeend = (where == LIST_HEAD) ? rangelen - 1 : -1;
int reverse = (where == LIST_HEAD) ? 0 : 1;
initDeferredReplyBuffer(c);
/* We return key-name just once, and an array of elements */
addReplyArrayLen(c, 2);
addReplyBulk(c, key);
@@ -643,7 +644,8 @@ void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long
/* Pop these elements. */
listTypeDelRange(o, rangestart, rangelen);
/* Maintain the notifications and dirty. */
listElementsRemoved(c, key, where, o, rangelen, signal, deleted);
listElementsRemoved(c, key, where, o, rangelen, deleted);
commitDeferredReplyBuffer(c, 1);
}
/* Extracted from `addListRangeReply()` to reply with a quicklist list.
@@ -726,12 +728,10 @@ void addListRangeReply(client *c, robj *o, long start, long end, int reverse) {
}
/* A housekeeping helper for list elements popping tasks.
*
* If 'signal' is 0, skip calling signalModifiedKey().
*
* 'deleted' is an optional output argument to get an indication
* if the key got deleted by this function. */
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted) {
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int *deleted) {
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
@@ -744,7 +744,7 @@ void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, i
listTypeTryConversion(o, LIST_CONV_SHRINKING, NULL, NULL);
if (deleted) *deleted = 0;
}
if (signal) signalModifiedKey(c, c->db, key);
signalModifiedKey(c, c->db, key);
server.dirty += count;
}
@@ -778,10 +778,10 @@ void popGenericCommand(client *c, int where) {
/* Pop a single element. This is POP's original behavior that replies
* with a bulk string. */
value = listTypePop(o, where);
listElementsRemoved(c, c->argv[1], where, o, 1, NULL);
serverAssert(value != NULL);
addReplyBulk(c, value);
decrRefCount(value);
listElementsRemoved(c, c->argv[1], where, o, 1, 1, NULL);
} else {
/* Pop a range of elements. An addition to the original POP command,
* which replies with a multi-bulk. */
@@ -791,9 +791,11 @@ void popGenericCommand(client *c, int where) {
long rangeend = (where == LIST_HEAD) ? rangelen - 1 : -1;
int reverse = (where == LIST_HEAD) ? 0 : 1;
initDeferredReplyBuffer(c);
addListRangeReply(c, o, rangestart, rangeend, reverse);
listTypeDelRange(o, rangestart, rangelen);
listElementsRemoved(c, c->argv[1], where, o, rangelen, 1, NULL);
listElementsRemoved(c, c->argv[1], where, o, rangelen, NULL);
commitDeferredReplyBuffer(c, 1);
}
}
@@ -823,7 +825,7 @@ void mpopGenericCommand(client *c, robj **keys, int numkeys, int where, long cou
if (llen == 0) continue;
/* Pop a range of elements in a nested arrays way. */
listPopRangeAndReplyWithKey(c, o, key, where, count, 1, NULL);
listPopRangeAndReplyWithKey(c, o, key, where, count, NULL);
/* Replicate it as [LR]POP COUNT. */
robj *count_obj = createStringObjectFromLongLong((count > llen) ? llen : count);
@@ -1116,7 +1118,7 @@ void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
value = listTypePop(sobj, wherefrom);
serverAssert(value); /* assertion for valgrind (avoid NPD) */
lmoveHandlePush(c, c->argv[2], dobj, value, whereto);
listElementsRemoved(c, touchedkey, wherefrom, sobj, 1, 1, NULL);
listElementsRemoved(c, touchedkey, wherefrom, sobj, 1, NULL);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
@@ -1190,7 +1192,7 @@ void blockingPopGenericCommand(client *c, robj **keys, int numkeys, int where, i
if (count != -1) {
/* BLMPOP, non empty list, like a normal [LR]POP with count option.
* The difference here we pop a range of elements in a nested arrays way. */
listPopRangeAndReplyWithKey(c, o, key, where, count, 1, NULL);
listPopRangeAndReplyWithKey(c, o, key, where, count, NULL);
/* Replicate it as [LR]POP COUNT. */
robj *count_obj = createStringObjectFromLongLong((count > llen) ? llen : count);
@@ -1203,12 +1205,11 @@ void blockingPopGenericCommand(client *c, robj **keys, int numkeys, int where, i
robj *value = listTypePop(o, where);
serverAssert(value != NULL);
listElementsRemoved(c, key, where, o, 1, NULL);
addReplyArrayLen(c, 2);
addReplyBulk(c, key);
addReplyBulk(c, value);
decrRefCount(value);
listElementsRemoved(c, key, where, o, 1, 1, NULL);
/* Replicate it as an [LR]POP instead of B[LR]POP. */
rewriteClientCommandVector(c, 2, (where == LIST_HEAD) ? shared.lpop : shared.rpop, key);
return;
+9 -5
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include "hashtable.h"
@@ -898,7 +903,6 @@ void spopWithCountCommand(client *c) {
setTypeRemoveAux(set, str, len, llele, encoding == OBJ_ENCODING_HASHTABLE);
}
}
/* Transfer the old set to the client. */
setTypeIterator *si;
si = setTypeInitIterator(set);
@@ -1382,16 +1386,16 @@ void sinterGenericCommand(client *c,
dstset->ptr = lpShrinkToFit(dstset->ptr);
}
setKey(c, c->db, dstkey, &dstset, 0);
addReplyLongLong(c, setTypeSize(dstset));
notifyKeyspaceEvent(NOTIFY_SET, "sinterstore", dstkey, c->db->id);
server.dirty++;
addReplyLongLong(c, setTypeSize(dstset));
} else {
addReply(c, shared.czero);
if (dbDelete(c->db, dstkey)) {
server.dirty++;
signalModifiedKey(c, c->db, dstkey);
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
}
addReply(c, shared.czero);
decrRefCount(dstset);
}
} else {
@@ -1606,16 +1610,16 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum, robj *dstke
* create this key with the result set inside */
if (setTypeSize(dstset) > 0) {
setKey(c, c->db, dstkey, &dstset, 0);
addReplyLongLong(c, setTypeSize(dstset));
notifyKeyspaceEvent(NOTIFY_SET, op == SET_OP_UNION ? "sunionstore" : "sdiffstore", dstkey, c->db->id);
server.dirty++;
addReplyLongLong(c, setTypeSize(dstset));
} else {
addReply(c, shared.czero);
if (dbDelete(c->db, dstkey)) {
server.dirty++;
signalModifiedKey(c, c->db, dstkey);
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
}
addReply(c, shared.czero);
decrRefCount(dstset);
}
}
+15 -14
View File
@@ -1394,11 +1394,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 {
@@ -1490,6 +1485,11 @@ long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id) {
return s->entries_added;
}
if (!streamIDEqZero(id) && streamCompareID(id, &s->max_deleted_entry_id) < 0) {
/* The ID is before the last tombstone, so the counter is unknown. */
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. */
@@ -1678,10 +1678,11 @@ size_t streamReplyWithRange(client *c,
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 &&
streamCompareID(&group->last_id, &s->first_id) >= 0 &&
!streamRangeHasTombstones(s, &group->last_id, NULL)) {
/* A valid counter and no tombstones in the group's last-delivered-id and the stream's last-generated-id,
* 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. */
@@ -2027,10 +2028,10 @@ void xaddCommand(client *c) {
return;
}
sds replyid = createStreamIDString(&id);
addReplyBulkCBuffer(c, replyid, sdslen(replyid));
notifyKeyspaceEvent(NOTIFY_STREAM, "xadd", c->argv[1], c->db->id);
server.dirty++;
addReplyBulkCBuffer(c, replyid, sdslen(replyid));
/* Trim if needed. */
if (parsed_args.trim_strategy != TRIM_STRATEGY_NONE) {
@@ -2663,9 +2664,9 @@ void xgroupCommand(client *c) {
streamCG *cg = streamCreateCG(s, grpname, sdslen(grpname), &id, entries_read);
if (cg) {
addReply(c, shared.ok);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-create", c->argv[2], c->db->id);
addReply(c, shared.ok);
} else {
addReplyError(c, "-BUSYGROUP Consumer Group name already exists");
}
@@ -2678,16 +2679,16 @@ void xgroupCommand(client *c) {
}
cg->last_id = id;
cg->entries_read = entries_read;
addReply(c, shared.ok);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-setid", c->argv[2], c->db->id);
addReply(c, shared.ok);
} else if (!strcasecmp(opt, "DESTROY") && c->argc == 4) {
if (cg) {
raxRemove(s->cgroups, (unsigned char *)grpname, sdslen(grpname), NULL);
streamFreeCG(cg);
addReply(c, shared.cone);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-destroy", c->argv[2], c->db->id);
addReply(c, shared.cone);
/* We want to unblock any XREADGROUP consumers with -NOGROUP. */
signalKeyAsReady(c->db, c->argv[2], OBJ_STREAM);
} else {
@@ -2780,9 +2781,9 @@ void xsetidCommand(client *c) {
s->last_id = id;
if (entries_added != -1) s->entries_added = entries_added;
if (!streamIDEqZero(&max_xdel_id)) s->max_deleted_entry_id = max_xdel_id;
addReply(c, shared.ok);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM, "xsetid", c->argv[1], c->db->id);
addReply(c, shared.ok);
}
/* XACK <key> <group> <id> <id> ... <id>
+21 -6
View File
@@ -26,6 +26,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "server.h"
#include <math.h> /* isnan(), isinf() */
@@ -101,7 +106,8 @@ void setGenericCommand(client *c,
}
if (flags & OBJ_SET_GET) {
if (getGenericCommand(c) == C_ERR) return;
initDeferredReplyBuffer(c);
if (getGenericCommand(c) == C_ERR) goto cleanup;
}
robj *existing_value = lookupKeyWrite(c->db, key);
@@ -110,27 +116,27 @@ void setGenericCommand(client *c,
/* Handle the IFEQ conditional check */
if (flags & OBJ_SET_IFEQ && found) {
if (!(flags & OBJ_SET_GET) && checkType(c, existing_value, OBJ_STRING)) {
return;
goto cleanup;
}
if (compareStringObjects(existing_value, comparison) != 0) {
if (!(flags & OBJ_SET_GET)) {
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
}
return;
goto cleanup;
}
} else if (flags & OBJ_SET_IFEQ && !found) {
if (!(flags & OBJ_SET_GET)) {
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
}
return;
goto cleanup;
}
if ((flags & OBJ_SET_NX && found) || (flags & OBJ_SET_XX && !found)) {
if (!(flags & OBJ_SET_GET)) {
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
}
return;
goto cleanup;
}
/* If the `milliseconds` have expired, then we don't need to set it into the
@@ -139,7 +145,7 @@ void setGenericCommand(client *c,
if (expire && checkAlreadyExpired(milliseconds)) {
if (found) deleteExpiredKeyFromOverwriteAndPropagate(c, key);
if (!(flags & OBJ_SET_GET)) addReply(c, shared.ok);
return;
goto cleanup;
}
/* When expire is not NULL, we avoid deleting the TTL so it can be updated later instead of being deleted and then
@@ -190,6 +196,9 @@ void setGenericCommand(client *c,
}
replaceClientCommandVector(c, argc, argv);
}
cleanup:
commitDeferredReplyBuffer(c, 1);
}
/*
@@ -437,6 +446,7 @@ void getexCommand(client *c) {
return;
}
initDeferredReplyBuffer(c);
/* We need to do this before we expire the key or delete it */
addReplyBulk(c, o);
@@ -464,9 +474,11 @@ void getexCommand(client *c) {
server.dirty++;
}
}
commitDeferredReplyBuffer(c, 1);
}
void getdelCommand(client *c) {
initDeferredReplyBuffer(c);
if (getGenericCommand(c) == C_ERR) return;
if (dbSyncDelete(c->db, c->argv[1])) {
/* Propagate as DEL command */
@@ -475,9 +487,11 @@ void getdelCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
server.dirty++;
}
commitDeferredReplyBuffer(c, 1);
}
void getsetCommand(client *c) {
initDeferredReplyBuffer(c);
if (getGenericCommand(c) == C_ERR) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
setKey(c, c->db, c->argv[1], &c->argv[2], 0);
@@ -485,6 +499,7 @@ void getsetCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_STRING, "set", c->argv[1], c->db->id);
server.dirty++;
commitDeferredReplyBuffer(c, 1);
/* Propagate as SET command */
rewriteClientCommandArgument(c, 0, shared.set);
}
+43 -33
View File
@@ -27,7 +27,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
/*-----------------------------------------------------------------------------
* Sorted set API
*----------------------------------------------------------------------------*/
@@ -1754,6 +1758,8 @@ static void zaddGenericCommand(client *c, int flags) {
int j, elements, ch = 0;
size_t maxelelen = 0;
int scoreidx = 0;
int reply_err = 0;
/* The following vars are used in order to track what the command actually
* did during the execution, to reply to the client and to trigger the
* notification of keyspace change. */
@@ -1847,18 +1853,26 @@ static void zaddGenericCommand(client *c, int flags) {
ele = c->argv[scoreidx + 1 + j * 2]->ptr;
int retval = zsetAdd(zobj, score, ele, flags, &retflags, &newscore);
if (retval == 0) {
addReplyError(c, nanerr);
goto cleanup;
reply_err = 1;
break;
}
if (retflags & ZADD_OUT_ADDED) added++;
if (retflags & ZADD_OUT_UPDATED) updated++;
if (!(retflags & ZADD_OUT_NOP)) processed++;
score = newscore;
}
server.dirty += (added + updated);
if (!reply_err) {
server.dirty += (added + updated);
}
if (added || updated) {
signalModifiedKey(c, c->db, key);
notifyKeyspaceEvent(NOTIFY_ZSET, incr ? "zincr" : "zadd", key, c->db->id);
}
reply_to_client:
if (incr) { /* ZINCRBY or INCR option. */
if (reply_err) {
addReplyError(c, nanerr);
} else if (incr) { /* ZINCRBY or INCR option. */
if (processed)
addReplyDouble(c, score);
else
@@ -1866,13 +1880,8 @@ reply_to_client:
} else { /* ZADD. */
addReplyLongLong(c, ch ? added + updated : added);
}
cleanup:
zfree(scores);
if (added || updated) {
signalModifiedKey(c, c->db, key);
notifyKeyspaceEvent(NOTIFY_ZSET, incr ? "zincr" : "zadd", key, c->db->id);
}
}
void zaddCommand(client *c) {
@@ -2767,18 +2776,17 @@ static void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIn
if (dstzset->zsl->length) {
zsetConvertToListpackIfNeeded(dstobj, maxelelen, totelelen);
setKey(c, c->db, dstkey, &dstobj, 0);
notifyKeyspaceEvent(NOTIFY_ZSET, (op == SET_OP_UNION) ? "zunionstore" : (op == SET_OP_INTER ? "zinterstore" : "zdiffstore"),
dstkey, c->db->id);
addReplyLongLong(c, zsetLength(dstobj));
notifyKeyspaceEvent(
NOTIFY_ZSET, (op == SET_OP_UNION) ? "zunionstore" : (op == SET_OP_INTER ? "zinterstore" : "zdiffstore"),
dstkey, c->db->id);
server.dirty++;
} else {
addReply(c, shared.czero);
if (dbDelete(c->db, dstkey)) {
signalModifiedKey(c, c->db, dstkey);
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
server.dirty++;
}
addReply(c, shared.czero);
decrRefCount(dstobj);
}
} else if (cardinality_only) {
@@ -2970,16 +2978,16 @@ static void zrangeResultEmitLongLongForStore(zrange_result_handler *handler, lon
static void zrangeResultFinalizeStore(zrange_result_handler *handler, size_t result_count) {
if (result_count) {
setKey(handler->client, handler->client->db, handler->dstkey, &handler->dstobj, 0);
addReplyLongLong(handler->client, result_count);
notifyKeyspaceEvent(NOTIFY_ZSET, "zrangestore", handler->dstkey, handler->client->db->id);
server.dirty++;
addReplyLongLong(handler->client, result_count);
} else {
addReply(handler->client, shared.czero);
if (dbDelete(handler->client->db, handler->dstkey)) {
signalModifiedKey(handler->client, handler->client->db, handler->dstkey);
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", handler->dstkey, handler->client->db->id);
server.dirty++;
}
addReply(handler->client, shared.czero);
decrRefCount(handler->dstobj);
}
}
@@ -3772,6 +3780,24 @@ void zscanCommand(client *c) {
scanGenericCommand(c, o, cursor);
}
void addZpopInitialReply(client *c, int emitkey, int use_nested_array, long rangelen, robj *key) {
if (!use_nested_array && !emitkey) {
/* ZPOPMIN/ZPOPMAX with or without COUNT option in RESP2. */
addReplyArrayLen(c, rangelen * 2);
} else if (use_nested_array && !emitkey) {
/* ZPOPMIN/ZPOPMAX with COUNT option in RESP3. */
addReplyArrayLen(c, rangelen);
} else if (!use_nested_array && emitkey) {
/* BZPOPMIN/BZPOPMAX in RESP2 and RESP3. */
addReplyArrayLen(c, rangelen * 2 + 1);
addReplyBulk(c, key);
} else if (use_nested_array && emitkey) {
/* ZMPOP/BZMPOP in RESP2 and RESP3. */
addReplyArrayLen(c, 2);
addReplyBulk(c, key);
addReplyArrayLen(c, rangelen);
}
}
/* This command implements the generic zpop operation, used by:
* ZPOPMIN, ZPOPMAX, BZPOPMIN, BZPOPMAX and ZMPOP. This function is also used
* inside blocked.c in the unblocking stage of BZPOPMIN, BZPOPMAX and BZMPOP.
@@ -3844,23 +3870,6 @@ void genericZpopCommand(client *c,
long llen = zsetLength(zobj);
long rangelen = (count > llen) ? llen : count;
if (!use_nested_array && !emitkey) {
/* ZPOPMIN/ZPOPMAX with or without COUNT option in RESP2. */
addReplyArrayLen(c, rangelen * 2);
} else if (use_nested_array && !emitkey) {
/* ZPOPMIN/ZPOPMAX with COUNT option in RESP3. */
addReplyArrayLen(c, rangelen);
} else if (!use_nested_array && emitkey) {
/* BZPOPMIN/BZPOPMAX in RESP2 and RESP3. */
addReplyArrayLen(c, rangelen * 2 + 1);
addReplyBulk(c, key);
} else if (use_nested_array && emitkey) {
/* ZMPOP/BZMPOP in RESP2 and RESP3. */
addReplyArrayLen(c, 2);
addReplyBulk(c, key);
addReplyArrayLen(c, rangelen);
}
/* Remove the element. */
do {
if (zobj->encoding == OBJ_ENCODING_LISTPACK) {
@@ -3905,6 +3914,7 @@ void genericZpopCommand(client *c,
if (result_count == 0) { /* Do this only for the first iteration. */
char *events[2] = {"zpopmin", "zpopmax"};
notifyKeyspaceEvent(NOTIFY_ZSET, events[where], key, c->db->id);
addZpopInitialReply(c, emitkey, use_nested_array, rangelen, key);
}
if (use_nested_array) {
+1 -1
View File
@@ -166,7 +166,7 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
return C_ERR;
ftval *= 1000.0; /* seconds => millisec */
if (ftval > LLONG_MAX) {
if (ftval > (long double)LLONG_MAX) {
addReplyError(c, "timeout is out of range");
return C_ERR;
}
+33 -15
View File
@@ -415,12 +415,6 @@ error:
return C_ERR;
}
#ifdef TLS_DEBUGGING
#define TLSCONN_DEBUG(fmt, ...) serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
#else
#define TLSCONN_DEBUG(fmt, ...)
#endif
static ConnectionType CT_TLS;
/* Normal socket connections have a simple events/handler correlation.
@@ -452,6 +446,10 @@ typedef struct tls_connection {
SSL *ssl;
char *ssl_error;
listNode *pending_list_node;
/* Per https://docs.openssl.org/master/man3/SSL_write, after a write call with partially written data,
* we must make subsequent write calls with the same length. We use this field to keep track of
* the previous write length. */
size_t last_failed_write_data_len;
} tls_connection;
static connection *createTLSConnection(int client_side) {
@@ -693,9 +691,6 @@ static void TLSAccept(void *_conn) {
static void tlsHandleEvent(tls_connection *conn, int mask) {
int ret, conn_error;
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d", fd, conn->c.state, mask,
conn->c.read_handler != NULL, conn->c.write_handler != NULL, conn->flags);
switch (conn->c.state) {
case CONN_STATE_CONNECTING:
conn_error = anetGetError(conn->c.fd);
@@ -798,6 +793,8 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
return;
}
serverLog(LL_VERBOSE, "Accepted %s:%d", cip, cport);
if (server.tcpkeepalive) anetKeepAlive(NULL, cfd, server.tcpkeepalive);
acceptCommonHandler(connCreateAcceptedTLS(cfd, &server.tls_auth_clients), flags, cip);
}
}
@@ -910,11 +907,23 @@ static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
/* In case when last write failed due to some internal reason, retry has to provide
* at least the same amount of bytes (https://docs.openssl.org/master/man3/SSL_write).
* If that condition is not met, OpenSSL will return "SSL routines::bad length".
* Currently we only suspect this can happen during primary cron sending '\n'
* indication to the replica, so we silently return from this function without
* impacting the connection state. */
if (data_len < conn->last_failed_write_data_len) {
// TODO: place debugAssert for this case once the known issue described is resolved
return -1;
}
ret = SSL_write(conn->ssl, data, data_len);
conn->last_failed_write_data_len = ret <= 0 ? data_len : 0;
return updateStateAfterSSLIO(conn, ret, 1);
}
static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt) {
tls_connection *conn = (tls_connection *)conn_;
if (iovcnt == 1) return connTLSWrite(conn_, iov[0].iov_base, iov[0].iov_len);
/* Accumulate the amount of bytes of each buffer and check if it exceeds NET_MAX_WRITES_PER_EVENT. */
@@ -924,10 +933,15 @@ static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt)
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) break;
}
/* The amount of all buffers is greater than NET_MAX_WRITES_PER_EVENT,
* which is not worth doing so much memory copying to reduce system calls,
* therefore, invoke connTLSWrite() multiple times to avoid memory copies. */
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) {
/* In case the amount of all buffers is greater than NET_MAX_WRITES_PER_EVENT,
* it might not worth doing so much memory copying to reduce system calls,
* therefore, invoke connTLSWrite() multiple times to avoid memory copies.
* However, in case when last write failed we still have to repeat sending last_failed_write_data_len
* bytes. Because of openssl implementation we cannot repeat sending writes with length smaller than
* the last failed write (https://docs.openssl.org/master/man3/SSL_write) so in case the first io buffer
* does not provide at least the same amount of bytes as previous failed write, we will have to fallback to
* memory copy to a static buffer before calling SSL_write. */
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT && iovcnt > 0 && iov[0].iov_len >= conn->last_failed_write_data_len) {
ssize_t tot_sent = 0;
for (int i = 0; i < iovcnt; i++) {
ssize_t sent = connTLSWrite(conn_, iov[i].iov_base, iov[i].iov_len);
@@ -941,10 +955,14 @@ static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt)
/* The amount of all buffers is less than NET_MAX_WRITES_PER_EVENT,
* which is worth doing more memory copies in exchange for fewer system calls,
* so concatenate these scattered buffers into a contiguous piece of memory
* and send it away by one call to connTLSWrite(). */
* and send it away by one call to connTLSWrite().
* However, code can fallback here in case when last write failed and first
* element of io is buffer not big enough to provide required amount of bytes
* to retry, so iov_bytes_len may exceed NET_MAX_WRITES_PER_EVENT by the amount
* of remaining bytes from last taken io. */
char buf[iov_bytes_len];
size_t offset = 0;
for (int i = 0; i < iovcnt; i++) {
for (int i = 0; i < iovcnt && offset < iov_bytes_len; i++) {
memcpy(buf + offset, iov[i].iov_base, iov[i].iov_len);
offset += iov[i].iov_len;
}
+1 -1
View File
@@ -280,7 +280,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
int using_redirection = 0;
if (c->pubsub_data->client_tracking_redirection) {
client *redir = lookupClientByID(c->pubsub_data->client_tracking_redirection);
if (!redir) {
if (!redir || redir->flag.close_after_reply || redir->flag.close_asap) {
c->flag.tracking_broken_redir = 1;
/* We need to signal to the original connection that we
* are unable to send invalidation messages to the redirected
+75
View File
@@ -0,0 +1,75 @@
#include <time.h>
#include <stdint.h>
#include "test_help.h"
#include "../config.h"
#include "../zmalloc.h"
extern long long popcountScalar(void *s, long count);
#ifdef HAVE_AVX2
extern long long popcountAVX2(void *s, long count);
#endif
static long long bitcount(void *s, long count) {
long long bits = 0;
uint8_t *p = (uint8_t *)s;
for (int x = 0; x < count; x += 1) {
uint8_t val = *(x + p);
while (val) {
bits += val & 1;
val >>= 1;
}
}
return bits;
}
static int test_case(const char *msg, int size) {
size_t bufsize = size > 0 ? size : 1;
uint8_t buf[bufsize];
int fuzzing = 1000;
for (int y = 0; y < fuzzing; y += 1) {
for (int z = 0; z < size; z += 1) {
buf[z] = random() % 256;
}
long long expect = bitcount(buf, size);
long long ret_scalar = popcountScalar(buf, size);
TEST_ASSERT_MESSAGE(msg, expect == ret_scalar);
#ifdef HAVE_AVX2
long long ret_avx2 = popcountAVX2(buf, size);
TEST_ASSERT_MESSAGE(msg, expect == ret_avx2);
#endif
}
return 0;
}
int test_popcount(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
#define TEST_CASE(MSG, SIZE) \
if (test_case("Test failed: " MSG, SIZE)) { \
return 1; \
}
/* The AVX2 version divides the array into the following 3 parts."
* Part A Part B Part C
* +-----------------+--------------+---------+
* | 8 * 32bytes * X | 32bytes * Y | Z bytes |
* +-----------------+--------------+---------+
*/
/* So we test the following cases */
TEST_CASE("Popcount(Part A)", 8 * 32 * 2);
TEST_CASE("Popcount(Part B)", 32 * 2);
TEST_CASE("Popcount(Part C)", 2);
TEST_CASE("Popcount(Part A + Part B)", 8 * 32 * 7 + 32 * 2);
TEST_CASE("Popcount(Part A + Part C)", 8 * 32 * 11 + 7);
TEST_CASE("Popcount(Part A + Part B + Part C)", 8 * 32 * 3 + 3 * 32 + 5);
TEST_CASE("Popcount(Corner case)", 0);
#undef TEST_CASE
return 0;
}
File diff suppressed because one or more lines are too long
+48 -21
View File
@@ -5,6 +5,11 @@ uint64_t hashTestCallback(const void *key) {
return hashtableGenHashFunction((char *)key, strlen((char *)key));
}
uint64_t hashConflictTestCallback(const void *key) {
UNUSED(key);
return 0;
}
int cmpTestCallback(const void *k1, const void *k2) {
return strcmp(k1, k2);
}
@@ -23,6 +28,16 @@ hashtableType KvstoreHashtableTestType = {
.getMetadataSize = kvstoreHashtableMetadataSize,
};
hashtableType KvstoreConflictHashtableTestType = {
.hashFunction = hashConflictTestCallback,
.keyCompare = cmpTestCallback,
.entryDestructor = freeTestCallback,
.rehashingStarted = kvstoreHashtableRehashingStarted,
.rehashingCompleted = kvstoreHashtableRehashingCompleted,
.trackMemUsage = kvstoreHashtableTrackMemUsage,
.getMetadataSize = kvstoreHashtableMetadataSize,
};
char *stringFromInt(int value) {
char buf[32];
int len;
@@ -70,31 +85,43 @@ int test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyHashtable(int argc, char **arg
UNUSED(argv);
UNUSED(flags);
int i;
void *key;
kvstoreIterator *kvs_it;
hashtableType *type[] = {
&KvstoreHashtableTestType,
&KvstoreConflictHashtableTestType,
NULL,
};
int didx = 0;
int curr_slot = 0;
kvstore *kvs1 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);
for (int t = 0; type[t] != NULL; t++) {
hashtableType *testType = type[t];
TEST_PRINT_INFO("Testing %d hashtableType\n", t);
for (i = 0; i < 16; i++) {
TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
int i;
void *key;
kvstoreIterator *kvs_it;
int didx = 0;
int curr_slot = 0;
kvstore *kvs1 = kvstoreCreate(testType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);
for (i = 0; i < 16; i++) {
TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
}
kvs_it = kvstoreIteratorInit(kvs1, HASHTABLE_ITER_SAFE);
while (kvstoreIteratorNext(kvs_it, &key)) {
curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
TEST_ASSERT(kvstoreHashtableDelete(kvs1, curr_slot, key));
}
kvstoreIteratorRelease(kvs_it);
hashtable *ht = kvstoreGetHashtable(kvs1, didx);
TEST_ASSERT(ht != NULL);
TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
TEST_ASSERT(kvstoreSize(kvs1) == 0);
kvstoreRelease(kvs1);
}
kvs_it = kvstoreIteratorInit(kvs1, HASHTABLE_ITER_SAFE);
while (kvstoreIteratorNext(kvs_it, &key)) {
curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
TEST_ASSERT(kvstoreHashtableDelete(kvs1, curr_slot, key));
}
kvstoreIteratorRelease(kvs_it);
hashtable *ht = kvstoreGetHashtable(kvs1, didx);
TEST_ASSERT(ht != NULL);
TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
TEST_ASSERT(kvstoreSize(kvs1) == 0);
kvstoreRelease(kvs1);
return 0;
}
+41 -2
View File
@@ -1,13 +1,18 @@
/*
* Copyright Valkey contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <strings.h>
#include <string.h>
#include <stdio.h>
#include "test_files.h"
#include "test_help.h"
#include "../util.h"
#include "../mt19937-64.h"
#include "../hashtable.h"
#include "../zmalloc.h"
/* We override the default assertion mechanism, so that it prints out info and then dies. */
void _serverAssert(const char *estr, const char *file, int line) {
@@ -32,6 +37,12 @@ int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) {
}
}
/* Check if the test suite has cleaned up all the memory used. */
if (zmalloc_used_memory() > 0) {
printf("[" KRED "%s" KRESET "] Memory leak detected of %zu bytes\n", test->filename, zmalloc_used_memory());
failed_tests++;
}
printf("[" KBLUE "END" KRESET "] - %s: ", test->filename);
printf("%d tests, %d passed, %d failed\n", test_num, test_num - failed_tests, failed_tests);
return !failed_tests;
@@ -40,6 +51,7 @@ int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) {
int main(int argc, char **argv) {
int flags = 0;
char *file = NULL;
char *seed = NULL;
for (int j = 1; j < argc; j++) {
char *arg = argv[j];
if (!strcasecmp(arg, "--accurate"))
@@ -51,13 +63,40 @@ int main(int argc, char **argv) {
file = argv[j + 1];
} else if (!strcasecmp(arg, "--valgrind")) {
flags |= UNIT_TEST_VALGRIND;
} else if (!strcasecmp(arg, "--seed")) {
seed = argv[j + 1];
}
}
if (seed) {
setRandomSeedCString(seed, strlen(seed));
}
char seed_cstr[129];
getRandomSeedCString(seed_cstr, 129);
printf("Tests will run with seed=%s\n", seed_cstr);
unsigned long long genrandseed;
getRandomBytes((void *)&genrandseed, sizeof(genrandseed));
uint8_t hashseed[16];
getRandomBytes(hashseed, sizeof(hashseed));
int numtests = sizeof(unitTestSuite) / sizeof(struct unitTestSuite);
int failed_num = 0, suites_executed = 0;
for (int j = 0; j < numtests; j++) {
if (file && strcasecmp(file, unitTestSuite[j].filename)) continue;
/* We need to explicitly set the seed in the several random numbers
* generator that valkey server uses so that the unit tests reproduce
* the random values in a deterministic way. */
setRandomSeedCString(seed_cstr, strlen(seed_cstr));
init_genrand64(genrandseed);
srandom((unsigned)genrandseed);
hashtableSetHashFunctionSeed(hashseed);
if (!runTestSuite(&unitTestSuite[j], argc, argv, flags)) {
failed_num++;
}
+1
View File
@@ -2294,6 +2294,7 @@ int test_quicklistCompressAndDecomressQuicklistPlainNodeLargeThanUINT32MAX(int a
zfree(node->next);
zfree(node->entry);
zfree(node);
zfree(s);
#endif
return 0;
+52
View File
@@ -1023,3 +1023,55 @@ int test_raxFuzz(int argc, char **argv, int flags) {
}
return !!errors;
}
/* This test verifies that raxRemove correctly handles compression when two keys
* share a common prefix. Upon deletion of one key, rax attempts to recompress
* the structure back to its original form for other key. Historically, there was
* a crash when deleting one key because rax would attempt to recompress the
* structure without checking the 512MB size limit.
*
* This test is disabled by default because it uses a lot of memory. */
int test_raxRecompressHugeKey(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
if (!(flags & UNIT_TEST_LARGE_MEMORY)) return 0;
rax *rt = raxNew();
/* Insert small keys */
char small_key[32];
const char *small_prefix = ",({5oM}";
int i;
for (i = 1; i <= 20; i++) {
snprintf(small_key, sizeof(small_key), "%s%d", small_prefix, i);
size_t keylen = strlen(small_key);
raxInsert(rt, (unsigned char *)small_key, keylen,
(void *)(long)i, NULL);
}
/* Insert large key exceeding compressed node size limit */
size_t max_keylen = ((1 << 29) - 1) + 100; // Compressed node limit + overflow
const char *large_prefix = ",({ABC}";
unsigned char *large_key = zmalloc(max_keylen + strlen(large_prefix));
if (!large_key) {
fprintf(stderr, "Failed to allocate memory for large key\n");
raxFree(rt);
return 1;
}
memcpy(large_key, large_prefix, strlen(large_prefix));
memset(large_key + strlen(large_prefix), '1', max_keylen);
raxInsert(rt, large_key, max_keylen + strlen(large_prefix), NULL, NULL);
/* Remove small keys to trigger recompression crash in raxRemove() */
for (i = 20; i >= 1; i--) {
snprintf(small_key, sizeof(small_key), "%s%d", small_prefix, i);
size_t keylen = strlen(small_key);
raxRemove(rt, (unsigned char *)small_key, keylen, NULL);
}
zfree(large_key);
raxFree(rt);
return 0;
}
+2 -3
View File
@@ -1,10 +1,9 @@
/*
* Copyright Valkey Contributors.
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "../valkey_strtod.h"
#include "errno.h"
#include "math.h"
+54 -22
View File
@@ -51,6 +51,7 @@
#include "sha256.h"
#include "config.h"
#include "zmalloc.h"
#include "serverassert.h"
#include "valkey_strtod.h"
@@ -905,36 +906,67 @@ int version2num(const char *version) {
return v;
}
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[64]; /* 512 bit internal block size. */
static void initializeRandomSeed(void) {
assert(seed_initialized == 0);
/* Initialize a seed and use SHA1 in counter mode, where we hash
* the same seed with a progressive counter. For the goals of this
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom", "r");
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
/* Revert to a weaker seed, and in this case reseed again
* at every call.*/
for (unsigned int j = 0; j < sizeof(seed); j++) {
struct timeval tv;
gettimeofday(&tv, NULL);
pid_t pid = getpid();
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
}
} else {
seed_initialized = 1;
}
if (fp) fclose(fp);
}
/* This function receives a string with 64 bytes encoded as 128 hexadecimal
* digits, and sets the 64 byte random seed. */
void setRandomSeedCString(char *seed_str, size_t len) {
assert(len == sizeof(seed) * 2);
for (size_t i = 0; i < sizeof(seed); i++) {
sscanf(seed_str + (i * 2), "%02hhX", &seed[i]);
}
seed_initialized = 1;
}
/* This function populates a char buffer with 129 bytes with the 64 bytes
* random seed encoded as 128 hexadecimal digits and a null terminator. */
void getRandomSeedCString(char *buff, size_t len) {
assert(len == (sizeof(seed) * 2 + 1));
if (!seed_initialized) {
initializeRandomSeed();
}
for (size_t i = 0; i < sizeof(seed); i++) {
snprintf(buff + (i * 2), 3, "%02hhX", seed[i]);
}
buff[sizeof(seed) * 2] = '\0';
}
/* Get random bytes, attempts to get an initial seed from /dev/urandom and
* the uses a one way hash function in counter mode to generate a random
* stream. However if /dev/urandom is not available, a weaker seed is used.
*
* This function is not thread safe, since the state is global. */
void getRandomBytes(unsigned char *p, size_t len) {
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[64]; /* 512 bit internal block size. */
static uint64_t counter = 0; /* The counter we hash with the seed. */
static uint64_t counter = 0; /* The counter we hash with the seed. */
if (!seed_initialized) {
/* Initialize a seed and use SHA1 in counter mode, where we hash
* the same seed with a progressive counter. For the goals of this
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom", "r");
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
/* Revert to a weaker seed, and in this case reseed again
* at every call.*/
for (unsigned int j = 0; j < sizeof(seed); j++) {
struct timeval tv;
gettimeofday(&tv, NULL);
pid_t pid = getpid();
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
}
} else {
seed_initialized = 1;
}
if (fp) fclose(fp);
initializeRandomSeed();
}
while (len) {
+4
View File
@@ -99,5 +99,9 @@ int snprintf_async_signal_safe(char *to, size_t n, const char *fmt, ...);
#endif
size_t valkey_strlcpy(char *dst, const char *src, size_t dsize);
size_t valkey_strlcat(char *dst, const char *src, size_t dsize);
void getRandomSeedCString(char *buff, size_t len);
void setRandomSeedCString(char *seed_str, size_t len);
void getRandomHexChars(char *p, size_t len);
void getRandomBytes(unsigned char *p, size_t len);
#endif
+7 -5
View File
@@ -244,10 +244,11 @@ static dictType dtype = {
static redisContext *getRedisContext(const char *ip, int port, const char *hostsocket) {
redisContext *ctx = NULL;
redisReply *reply = NULL;
struct timeval tv = {0};
if (hostsocket == NULL)
ctx = redisConnect(ip, port);
ctx = redisConnectWrapper(ip, port, tv, 0);
else
ctx = redisConnectUnix(hostsocket);
ctx = redisConnectUnixWrapper(hostsocket, tv, 0);
if (ctx == NULL || ctx->err) {
fprintf(stderr, "Could not connect to server at ");
char *err = (ctx != NULL ? ctx->errstr : "");
@@ -637,6 +638,7 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
const char *ip = NULL;
int port = 0;
struct timeval tv = {0};
c->cluster_node = NULL;
if (config.hostsocket == NULL || is_cluster_client) {
if (!is_cluster_client) {
@@ -654,9 +656,9 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
port = node->port;
c->cluster_node = node;
}
c->context = redisConnectNonBlock(ip, port);
c->context = redisConnectWrapper(ip, port, tv, 1);
} else {
c->context = redisConnectUnixNonBlock(config.hostsocket);
c->context = redisConnectUnixWrapper(config.hostsocket, tv, 1);
}
if (c->context->err) {
fprintf(stderr, "Could not connect to server at ");
@@ -1340,7 +1342,7 @@ int parseOptions(int argc, char **argv) {
if (lastarg) goto invalid;
config.conn_info.user = sdsnew(argv[++i]);
} else if (!strcmp(argv[i], "-u") && !lastarg) {
parseRedisUri(argv[++i], "redis-benchmark", &config.conn_info, &config.tls);
parseUri(argv[++i], "valkey-benchmark", &config.conn_info, &config.tls);
if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
fprintf(stderr, "Invalid server port.\n");
exit(1);
+14 -7
View File
@@ -27,7 +27,11 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fmacros.h"
#include <stdio.h>
@@ -1631,9 +1635,9 @@ static int cliConnect(int flags) {
/* Do not use hostsocket when we got redirected in cluster mode */
if (config.hostsocket == NULL || (config.cluster_mode && config.cluster_reissue_command)) {
context = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout);
context = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout, 0);
} else {
context = redisConnectUnixWrapper(config.hostsocket, config.connect_timeout);
context = redisConnectUnixWrapper(config.hostsocket, config.connect_timeout, 0);
}
if (!context->err && config.tls) {
@@ -2356,6 +2360,9 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
output_raw = 1;
}
/* In a multi block, commands will return status strings instead of verbatim strings. */
if (config.in_multi) output_raw = 0;
if (!strcasecmp(command, "shutdown")) config.shutdown = 1;
if (!strcasecmp(command, "monitor")) config.monitor_mode = 1;
int is_subscribe =
@@ -2518,7 +2525,7 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
fflush(stdout);
redisFree(c);
c = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout);
c = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout, 0);
if (!c->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
@@ -2601,7 +2608,7 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i], "--user") && !lastarg) {
config.conn_info.user = sdsnew(argv[++i]);
} else if (!strcmp(argv[i], "-u") && !lastarg) {
parseRedisUri(argv[++i], "valkey-cli", &config.conn_info, &config.tls);
parseUri(argv[++i], "valkey-cli", &config.conn_info, &config.tls);
if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
fprintf(stderr, "Invalid server port.\n");
exit(1);
@@ -3949,7 +3956,7 @@ cleanup:
static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context);
node->context = redisConnectWrapper(node->ip, node->port, config.connect_timeout);
node->context = redisConnectWrapper(node->ip, node->port, config.connect_timeout, 0);
if (!node->context->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
@@ -7666,7 +7673,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
char *reply_err = NULL;
redisReply *src_reply = NULL;
// Connect to the source node.
redisContext *src_ctx = redisConnectWrapper(src_ip, src_port, config.connect_timeout);
redisContext *src_ctx = redisConnectWrapper(src_ip, src_port, config.connect_timeout, 0);
if (src_ctx->err) {
success = 0;
fprintf(stderr, "Could not connect to Valkey at %s:%d: %s.\n", src_ip, src_port, src_ctx->errstr);
+25 -4
View File
@@ -325,10 +325,15 @@ typedef uint64_t ValkeyModuleTimerID;
* If enabled, the module is responsible to break endless loop. */
#define VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS (1 << 3)
/* Skipping command validation can improve performance by reducing the overhead associated
* with command checking, especially in high-throughput scenarios where commands
* are already pre-validated or trusted. */
#define VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION (1 << 4)
/* Next option flag, must be updated when adding new module flags above!
* This flag should not be used directly by the module.
* Use ValkeyModule_GetModuleOptionsAll instead. */
#define _VALKEYMODULE_OPTIONS_FLAGS_NEXT (1 << 4)
#define _VALKEYMODULE_OPTIONS_FLAGS_NEXT (1 << 5)
/* Definitions for ValkeyModule_SetCommandInfo. */
@@ -873,6 +878,8 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
*
* - `code`: string pointer to the source code.
*
* - `code_len`: The length of the code string.
*
* - `timeout`: timeout for the library creation (0 for no timeout).
*
* - `out_num_compiled_functions`: out param with the number of objects
@@ -884,6 +891,18 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
* occurred.
*/
typedef ValkeyModuleScriptingEngineCompiledFunction **(*ValkeyModuleScriptingEngineCompileCodeFunc)(
ValkeyModuleCtx *module_ctx,
ValkeyModuleScriptingEngineCtx *engine_ctx,
ValkeyModuleScriptingEngineSubsystemType type,
const char *code,
size_t code_len,
size_t timeout,
size_t *out_num_compiled_functions,
ValkeyModuleString **err);
/* Version one of source code compilation interface. This API does not allow the compiler to
* safely handle binary data. You should use a newer version of the API if possible. */
typedef ValkeyModuleScriptingEngineCompiledFunctionV1 **(*ValkeyModuleScriptingEngineCompileCodeFuncV1)(
ValkeyModuleCtx *module_ctx,
ValkeyModuleScriptingEngineCtx *engine_ctx,
ValkeyModuleScriptingEngineSubsystemType type,
@@ -980,7 +999,7 @@ typedef ValkeyModuleScriptingEngineMemoryInfo (*ValkeyModuleScriptingEngineGetMe
ValkeyModuleScriptingEngineSubsystemType type);
/* Current ABI version for scripting engine modules. */
#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 1UL
#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 2UL
typedef struct ValkeyModuleScriptingEngineMethods {
uint64_t version; /* Version of this structure for ABI compat. */
@@ -988,8 +1007,10 @@ typedef struct ValkeyModuleScriptingEngineMethods {
/* Compile code function callback. When a new script is loaded, this
* callback will be called with the script code, compiles it, and returns a
* list of `ValkeyModuleScriptingEngineCompiledFunc` objects. */
ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
union {
ValkeyModuleScriptingEngineCompileCodeFuncV1 compile_code_v1;
ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
};
/* Function callback to free the memory of a registered engine function. */
ValkeyModuleScriptingEngineFreeFunctionFunc free_function;
+3 -3
View File
@@ -4,13 +4,13 @@
* similar. */
#define SERVER_NAME "valkey"
#define SERVER_TITLE "Valkey"
#define VALKEY_VERSION "255.255.255"
#define VALKEY_VERSION_NUM 0x00ffffff
#define VALKEY_VERSION "8.1.1"
#define VALKEY_VERSION_NUM 0x00080101
/* The release stage is used in order to provide release status information.
* In unstable branch the status is always "dev".
* During release process the status will be set to rc1,rc2...rcN.
* When the version is released the status will be "ga". */
#define VALKEY_RELEASE_STAGE "dev"
#define VALKEY_RELEASE_STAGE "ga"
/* Redis OSS compatibility version, should never
* exceed 7.2.x. */
+2 -1
View File
@@ -101,10 +101,11 @@ static thread_local int thread_index = -1;
#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || defined(__POWERPC__) || defined(__arm__) || \
defined(__arm64__)
static __attribute__((aligned(CACHE_LINE_SIZE))) size_t used_memory_thread_padded[MAX_THREADS_NUM + PADDING_ELEMENT_NUM];
static size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
#else
static __attribute__((aligned(CACHE_LINE_SIZE))) _Atomic size_t used_memory_thread_padded[MAX_THREADS_NUM + PADDING_ELEMENT_NUM];
static _Atomic size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
#endif
static size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
static atomic_int total_active_threads = 0;
/* This is a simple protection. It's used only if some modules create a lot of threads. */
static atomic_size_t used_memory_for_additional_threads = 0;
+3
View File
@@ -215,6 +215,9 @@ proc wait_for_cluster_propagation {} {
wait_for_condition 1000 50 {
[cluster_config_consistent] eq 1
} else {
for {set j 0} {$j < [llength $::servers]} {incr j} {
puts "R $j cluster slots output: [R $j cluster slots]"
}
fail "cluster config did not reach a consistent state"
}
}
+21
View File
@@ -109,6 +109,27 @@ if {!$::valgrind} {
assert_equal "PONG" [r ping]
}
}
# test hide-user-data-from-log
set server_path [tmpdir server5.log]
start_server [list overrides [list dir $server_path crash-memcheck-enabled no hide-user-data-from-log no]] {
test "Config hide-user-data-from-log is off" {
r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
r flush
catch {r debug segfault}
wait_for_log_messages 0 {"*Query buffer: *\$blabla*"} 0 10 1000
}
}
set server_path [tmpdir server6.log]
start_server [list overrides [list dir $server_path crash-memcheck-enabled no hide-user-data-from-log yes]] {
test "Config hide-user-data-from-log is on" {
r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
r flush
catch {r debug segfault}
wait_for_log_messages 0 {"*Query buffer: *redacted*"} 0 10 1000
}
}
}
# test DEBUG ASSERT
+41
View File
@@ -244,3 +244,44 @@ test "Shutting down master waits for replica then aborted" {
}
}
} {} {repl external:skip}
test "Shutting down primary wait for replica after previous block" {
start_server {overrides {save "" repl-backlog-size 1MB}} {
start_server {overrides {save "" repl-backlog-size 1MB}} {
set primary [srv -1 client]
set primary_deferring [valkey_deferring_client -1]
set primary_host [srv -1 host]
set primary_port [srv -1 port]
set primary_pid [srv -1 pid]
set replica [srv 0 client]
set replica_pid [srv 0 pid]
$primary_deferring BLPOP key 0.1
# Config primary and replica.
$replica replicaof $primary_host $primary_port
wait_for_sync $replica
# Pause the replica and write a key on primary.
pause_process $replica_pid
$primary incr k
# Call shutdown.
$primary_deferring shutdown
wait_for_condition 100 100 {
[string match "*connected_clients:2*" [$primary info clients]] && [string match "*blocked_clients:1*" [$primary info clients]]
} else {
set client_info [$primary info clients]
fail "Shutdown did not trigger block. Current INFO CLIENTS output: $client_info"
}
# Wake up replica, causing primary to continue shutting down.
resume_process $replica_pid
$primary_deferring close
# Check for crash.
assert_equal [count_log_message -1 "*BUG REPORT START*"] 0
}
}
} {} {repl external:skip}
+1
View File
@@ -39,6 +39,7 @@ TEST_MODULES = \
datatype2.so \
auth.so \
keyspace_events.so \
block_keyspace_notification.so \
blockedclient.so \
getkeys.so \
getchannels.so \
+2 -6
View File
@@ -187,15 +187,11 @@ void AuthBlock_FreeData(ValkeyModuleCtx *ctx, void *privdata) {
* of blocking module auth.
*/
int blocking_auth_cb(ValkeyModuleCtx *ctx, ValkeyModuleString *username, ValkeyModuleString *password, ValkeyModuleString **err) {
VALKEYMODULE_NOT_USED(username);
VALKEYMODULE_NOT_USED(password);
VALKEYMODULE_NOT_USED(err);
/* Block the client from the Module. */
ValkeyModuleBlockedClient *bc = ValkeyModule_BlockClientOnAuth(ctx, AuthBlock_Reply, AuthBlock_FreeData);
int ctx_flags = ValkeyModule_GetContextFlags(ctx);
if (ctx_flags & VALKEYMODULE_CTX_FLAGS_MULTI || ctx_flags & VALKEYMODULE_CTX_FLAGS_LUA) {
/* Clean up by using ValkeyModule_UnblockClient since we attempted blocking the client. */
ValkeyModule_UnblockClient(bc, NULL);
if (bc == NULL) {
ValkeyModule_ReplyWithError(ctx, "ERR Blocking module command called from transaction");
return VALKEYMODULE_AUTH_HANDLED;
}
ValkeyModule_BlockedClientMeasureTimeStart(bc);
+169
View File
@@ -0,0 +1,169 @@
/* This module is used to test blocking the client during a keyspace event. */
#define _BSD_SOURCE
#define _DEFAULT_SOURCE /* For usleep */
#include "valkeymodule.h"
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#define EVENT_LOG_MAX_SIZE 1024
static pthread_mutex_t event_log_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct KeyspaceEventData {
ValkeyModuleString *key;
ValkeyModuleString *event;
} KeyspaceEventData;
typedef struct KeyspaceEventLog {
KeyspaceEventData *log[EVENT_LOG_MAX_SIZE];
size_t next_index;
} KeyspaceEventLog;
KeyspaceEventLog *event_log = NULL;
int unloaded = 0;
typedef struct BackgroundThreadData {
KeyspaceEventData *event;
ValkeyModuleBlockedClient *bc;
} BackgroundThreadData;
static void *GenericEvent_BackgroundWork(void *arg) {
BackgroundThreadData *data = (BackgroundThreadData *)arg;
// Sleep for 1 second
sleep(1);
pthread_mutex_lock(&event_log_mutex);
if (!unloaded && event_log->next_index < EVENT_LOG_MAX_SIZE) {
event_log->log[event_log->next_index] = data->event;
event_log->next_index++;
}
pthread_mutex_unlock(&event_log_mutex);
if (data->bc) {
ValkeyModule_UnblockClient(data->bc, NULL);
}
ValkeyModule_Free(data);
pthread_exit(NULL);
}
static int KeySpace_NotificationGeneric(ValkeyModuleCtx *ctx, int type,
const char *event,
ValkeyModuleString *key) {
VALKEYMODULE_NOT_USED(ctx);
VALKEYMODULE_NOT_USED(type);
ValkeyModuleString *retained_key = ValkeyModule_HoldString(ctx, key);
ValkeyModuleBlockedClient *bc =
ValkeyModule_BlockClient(ctx, NULL, NULL, NULL, 0);
if (bc == NULL) {
ValkeyModule_Log(ctx, VALKEYMODULE_LOGLEVEL_NOTICE,
"Failed to block for event %s on %s!", event,
ValkeyModule_StringPtrLen(key, NULL));
}
BackgroundThreadData *data =
ValkeyModule_Alloc(sizeof(BackgroundThreadData));
data->bc = bc;
KeyspaceEventData *event_data =
ValkeyModule_Alloc(sizeof(KeyspaceEventData));
event_data->key = retained_key;
event_data->event = ValkeyModule_CreateString(ctx, event, strlen(event));
data->event = event_data;
pthread_t tid;
pthread_create(&tid, NULL, GenericEvent_BackgroundWork, (void *)data);
return VALKEYMODULE_OK;
}
static int cmdGetEvents(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
pthread_mutex_lock(&event_log_mutex);
ValkeyModule_ReplyWithArray(ctx, event_log->next_index);
for (size_t i = 0; i < event_log->next_index; i++) {
ValkeyModule_ReplyWithArray(ctx, 4);
ValkeyModule_ReplyWithStringBuffer(ctx, "event", 5);
ValkeyModule_ReplyWithString(ctx, event_log->log[i]->event);
ValkeyModule_ReplyWithStringBuffer(ctx, "key", 3);
ValkeyModule_ReplyWithString(ctx, event_log->log[i]->key);
}
pthread_mutex_unlock(&event_log_mutex);
return VALKEYMODULE_OK;
}
static int cmdClearEvents(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
pthread_mutex_lock(&event_log_mutex);
for (size_t i = 0; i < event_log->next_index; i++) {
KeyspaceEventData *data = event_log->log[i];
ValkeyModule_FreeString(ctx, data->event);
ValkeyModule_FreeString(ctx, data->key);
ValkeyModule_Free(data);
}
event_log->next_index = 0;
ValkeyModule_ReplyWithSimpleString(ctx, "OK");
pthread_mutex_unlock(&event_log_mutex);
return VALKEYMODULE_OK;
}
/* This function must be present on each Valkey module. It is used in order to
* register the commands into the Valkey server. */
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
if (ValkeyModule_Init(ctx, "testblockingkeyspacenotif", 1,
VALKEYMODULE_APIVER_1) == VALKEYMODULE_ERR) {
return VALKEYMODULE_ERR;
}
event_log = ValkeyModule_Alloc(sizeof(KeyspaceEventLog));
event_log->next_index = 0;
int keySpaceAll = ValkeyModule_GetKeyspaceNotificationFlagsAll();
if (!(keySpaceAll & VALKEYMODULE_NOTIFY_LOADED)) {
// VALKEYMODULE_NOTIFY_LOADED event are not supported we can not start
return VALKEYMODULE_ERR;
}
if (ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_LOADED,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_GENERIC,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_EXPIRED,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_MODULE,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(
ctx, VALKEYMODULE_NOTIFY_KEY_MISS, KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_STRING,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_HASH,
KeySpace_NotificationGeneric) !=
VALKEYMODULE_OK ||
ValkeyModule_CreateCommand(ctx, "b_keyspace.events", cmdGetEvents, "",
0, 0, 0) == VALKEYMODULE_ERR ||
ValkeyModule_CreateCommand(ctx, "b_keyspace.clear", cmdClearEvents, "",
0, 0, 0) == VALKEYMODULE_ERR) {
return VALKEYMODULE_ERR;
}
return VALKEYMODULE_OK;
}
int ValkeyModule_OnUnload(ValkeyModuleCtx *ctx) {
pthread_mutex_lock(&event_log_mutex);
unloaded = 1;
for (size_t i = 0; i < event_log->next_index; i++) {
KeyspaceEventData *data = event_log->log[i];
ValkeyModule_FreeString(ctx, data->event);
ValkeyModule_FreeString(ctx, data->key);
ValkeyModule_Free(data);
}
ValkeyModule_Free(event_log);
pthread_mutex_unlock(&event_log_mutex);
return VALKEYMODULE_OK;
}
+12
View File
@@ -46,6 +46,15 @@ int PingallCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
return ValkeyModule_ReplyWithSimpleString(ctx, "OK");
}
void DingReceiver(ValkeyModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
ValkeyModule_Log(ctx, "notice", "DING (type %d) RECEIVED from %.*s: '%.*s'", type, VALKEYMODULE_NODE_ID_LEN, sender_id, (int)len, payload);
ValkeyModule_SendClusterMessage(ctx, sender_id, MSGTYPE_DONG, "Message Received!", 17);
}
void DongReceiver(ValkeyModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
ValkeyModule_Log(ctx, "notice", "DONG (type %d) RECEIVED from %.*s: '%.*s'", type, VALKEYMODULE_NODE_ID_LEN, sender_id, (int)len, payload);
}
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
@@ -63,5 +72,8 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
if (ValkeyModule_CreateCommand(ctx, "test.cluster_shards", test_cluster_shards, "", 0, 0, 0) == VALKEYMODULE_ERR)
return VALKEYMODULE_ERR;
/* Register our handlers for different message types. */
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DING, DingReceiver);
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DONG, DongReceiver);
return VALKEYMODULE_OK;
}
+3
View File
@@ -703,6 +703,9 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
return VALKEYMODULE_ERR;
}
/* This option tests skip command validation for ValkeyModule_EmitAOF */
ValkeyModule_SetModuleOptions(ctx, VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION);
if (ValkeyModule_CreateCommand(ctx, "mem.alloc", MemAlloc_RedisCommand, "write deny-oom", 1, 1, 1) == VALKEYMODULE_ERR) {
return VALKEYMODULE_ERR;
}
+2
View File
@@ -349,10 +349,12 @@ static ValkeyModuleScriptingEngineCompiledFunction **createHelloLangEngine(Valke
ValkeyModuleScriptingEngineCtx *engine_ctx,
ValkeyModuleScriptingEngineSubsystemType type,
const char *code,
size_t code_len,
size_t timeout,
size_t *out_num_compiled_functions,
ValkeyModuleString **err) {
VALKEYMODULE_NOT_USED(module_ctx);
VALKEYMODULE_NOT_USED(code_len);
VALKEYMODULE_NOT_USED(type);
VALKEYMODULE_NOT_USED(timeout);
VALKEYMODULE_NOT_USED(err);
+7 -4
View File
@@ -249,8 +249,8 @@ int propagateTestSimpleCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
VALKEYMODULE_NOT_USED(argc);
/* Replicate two commands to test MULTI/EXEC wrapping. */
ValkeyModule_Replicate(ctx,"INCR","c","counter-1");
ValkeyModule_Replicate(ctx,"INCR","c","counter-2");
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-1");
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-2");
ValkeyModule_ReplyWithSimpleString(ctx,"OK");
return VALKEYMODULE_OK;
}
@@ -265,8 +265,8 @@ int propagateTestMixedCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, i
reply = ValkeyModule_Call(ctx, "INCR", "c!", "using-call");
ValkeyModule_FreeCallReply(reply);
ValkeyModule_Replicate(ctx,"INCR","c","counter-1");
ValkeyModule_Replicate(ctx,"INCR","c","counter-2");
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-1");
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-2");
reply = ValkeyModule_Call(ctx, "INCR", "c!", "after-call");
ValkeyModule_FreeCallReply(reply);
@@ -347,6 +347,9 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
detached_ctx = ValkeyModule_GetDetachedThreadSafeContext(ctx);
/* This option tests skip command validation for ValkeyModule_Replicate */
ValkeyModule_SetModuleOptions(ctx, VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION);
if (ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_ALL, KeySpace_NotificationGeneric) == VALKEYMODULE_ERR)
return VALKEYMODULE_ERR;
+6 -3
View File
@@ -27,7 +27,7 @@ def build_program():
return 0
# iterate /sys/class/infiniband, find any usable RDMA device, and return IPv4 address
# iterate /sys/class/infiniband, find any usable RDMA device, and return IPv4/IPV6 address
def find_rdma_dev():
# Ex, /sys/class/infiniband/mlx5_0
# Ex, /sys/class/infiniband/rxe_eth0
@@ -39,9 +39,12 @@ def find_rdma_dev():
netdev = ibclass + dev + "/ports/1/gid_attrs/ndevs/0"
with open(netdev) as fp:
addrs = netifaces.ifaddresses(fp.readline().strip("\n"))
if netifaces.AF_INET not in addrs:
if netifaces.AF_INET in addrs:
ipaddr = addrs[netifaces.AF_INET][0]["addr"]
elif netifaces.AF_INET6 in addrs:
ipaddr = addrs[netifaces.AF_INET6][0]["addr"]
else:
continue
ipaddr = addrs[netifaces.AF_INET][0]["addr"]
print("Valkey Over RDMA test prepare " + dev + " <" + ipaddr + "> [OK]")
return ipaddr
except os.error:
+6
View File
@@ -365,3 +365,9 @@ proc ::valkey_cluster::get_slot_from_keys {keys} {
}
return $slot
}
# Check if the server responds with "PONG"
proc check_server_response {server_id} {
# Send a PING command and check if the response is "PONG"
return [expr {[catch {R $server_id PING} result] == 0 && $result eq "PONG"}]
}
+4 -1
View File
@@ -129,6 +129,9 @@ proc wait_for_cluster_propagation {} {
wait_for_condition 1000 50 {
[cluster_config_consistent] eq 1
} else {
for {set j 0} {$j < [llength $::servers]} {incr j} {
puts "R $j cluster slots output: [R $j cluster slots]"
}
fail "cluster config did not reach a consistent state"
}
}
@@ -253,7 +256,7 @@ proc start_cluster {masters replicas options code {slot_allocator continuous_slo
# Configure the starting of multiple servers. Set cluster node timeout
# aggressively since many tests depend on ping/pong messages.
set cluster_options [list overrides [list cluster-enabled yes cluster-ping-interval 100 cluster-node-timeout 3000]]
set cluster_options [list overrides [list cluster-enabled yes cluster-ping-interval 100 cluster-node-timeout 3000 cluster-slot-stats-enabled yes]]
set options [concat $cluster_options $options]
# Cluster mode only supports a single database, so before executing the tests
+6 -2
View File
@@ -124,8 +124,9 @@ proc assert_refcount_morethan {key ref} {
# Wait for the specified condition to be true, with the specified number of
# max retries and delay between retries. Otherwise the 'elsescript' is
# executed.
proc wait_for_condition {maxtries delay e _else_ elsescript} {
# executed. If 'debugscript' is provided, it is executed after failure of
# the confition (before the retry delay).
proc wait_for_condition {maxtries delay e _else_ elsescript {_debug_ ""} {debugscript ""}} {
while {[incr maxtries -1] >= 0} {
set errcode [catch {uplevel 1 [list expr $e]} result]
if {$errcode == 0} {
@@ -133,6 +134,9 @@ proc wait_for_condition {maxtries delay e _else_ elsescript} {
} else {
return -code $errcode $result
}
if {$_debug_ == "debug"} {
uplevel 1 $debugscript
}
after $delay
}
if {$maxtries == -1} {

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