Skip to content

deps sanitizer - #467

Open
sundb wants to merge 244 commits into
unstablefrom
deps-sanitizer
Open

deps sanitizer#467
sundb wants to merge 244 commits into
unstablefrom
deps-sanitizer

Conversation

@sundb

@sundb sundb commented Jun 9, 2026

Copy link
Copy Markdown
Owner

No description provided.

fcostaoliveira and others added 30 commits January 18, 2026 20:14
…#14700)

This PR refines the prefetch strategy by removing ineffective (to close
on the pipeline) dictionary-level prefetching and improving prefetch
usage in IO threads. The goal is to better aligning prefetches with
predictable access patterns.

## Changes

- Removed speculative prefetching from `dictFindLinkInternal()`,
simplifying the dictionary lookup hot path.
- Introduced a two-phase prefetch approach in
`prefetchIOThreadCommands()`:
  - Phase 1: Prefetch client structures and `pending_cmds`
- Phase 2: Add commands to the batch and prefetch follow-up fields
(`reply`, `mem_usage_bucket`)

## Performance

Measured with
`memtier_benchmark-1Mkeys-string-setget2000c-1KiB-pipeline-16`.

| Environment                  | % change |
|-----------------------------|----------|
| oss-standalone               | -0.1%    |
| oss-standalone-02-io-threads | +0.4%    |
| oss-standalone-04-io-threads | +1.6%    |
| oss-standalone-08-io-threads | +2.3%    |
| oss-standalone-12-io-threads | +0.7%    |
| oss-standalone-16-io-threads | +1.9%    |

Overall, this shows an ~2% throughput improvement on IO-threaded
configurations, with no meaningful impact on non-IO-threaded setups.

---------

Co-authored-by: Yuan Wang <wangyuancode@163.com>
* Embed sds element inside skiplist nodes: Changed zset dict to store
zskiplistNode* as keys (with no_value=1) instead of storing sds keys and
double* values, eliminating redundant sds storage and enabling
single-allocation nodes
* Single allocation for skiplist nodes: Each node now contains: fixed
fields + level[] array + embedded sds, reducing memory fragmentation and
allocation overhead. This optimization is based on valkey-io/valkey#1427
* Optimize lookups with dictFindLink: Use dictFindLink in zsetAdd to
avoid double hash table lookup when inserting new elements (find + add
becomes single operation)
* Simplify score updates
This PR is based on valkey-io/valkey#2078

# Reply Copy Avoidance Optimization

This PR introduces an optimization to avoid unnecessary memory copies
when sending replies to clients in Redis.

## Overview

Currently, Redis copies reply data into client output buffers before
sending responses. This PR implements a mechanism to avoid these copies
in certain scenarios, improving performance and reducing memory
overhead.

### Key Changes
* Added capability to reply construction allowing to interleave regular
replies with copy avoid replies in client reply buffers
* Extended write-to-client handlers to support copy avoid replies
* Added copy avoidance of string bulk replies when copy avoidance
indicated by I/O threads
* Copy avoidance is beneficial for performance despite object size only
starting certain number of threads. So it will be enabled only starting
certain number of threads.

**Note**: When copy avoidance disabled content and handling of client
reply buffers remains as before this PR

---------

Signed-off-by: Alexander Shabanov <alexander.shabanov@gmail.com>
Signed-off-by: xbasel <103044017+xbasel@users.noreply.github.com>
Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: Alexander Shabanov <alexander.shabanov@gmail.com>
Co-authored-by: xbasel <103044017+xbasel@users.noreply.github.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: Slavomir Kaslev <slavomir.kaslev@gmail.com>
Co-authored-by: moticless <moticless@github.com>
Co-authored-by: Yuan Wang <yuan.wang@redis.com>
…ng (redis#14662)

## Optimization details

The current `lpDecodeBacklen()` implementation decodes the backlen using
a loop with backward pointer mutation and a branch-heavy termination
condition:

```c
do {
    val |= (uint64_t)(p[0] & 127) << shift;
    if (!(p[0] & 128)) break;
    shift += 7;
    p--;
    if (shift > 28) return UINT64_MAX;
} while(1);
```

While correct, this structure introduces avoidable overhead in hot
paths:

- repeated loop control
- unpredictable branch (if (!(p[0] & 128)) break)
- increased front-end pressure and bad speculation

### Optimization

This PR replaces the loop with a straight-line implementation optimized
for the common case:

- explicit fast paths for 1–2 byte backlen encodings (dominant in
practice)
- unrolled handling up to the maximum 5-byte encoding
- no pointer mutation, no loop, fewer branches
- identical encoding semantics and validation behavior

leading to a 5.3% boost on listpack iterator heavy benchmark on HASH
datatype.
update data type modules to 8.6 RC1
time series v8.5.90
bloom v8.5.90
json v8.5.90
- **TCL test failure**

https://github.com/redis/redis/actions/runs/21121021310/job/60733781853#step:6:5705
```
[err]: Test cluster module notifications when replica restart with RDB during importing
in tests/unit/cluster/atomic-slot-migration.tcl
Expected '{sub: cluster-slot-migration-import-started, source_node_id:28c64b3f462f3c29aa3c96c2ba5dff948dfe315b, destination_node_id:1382a4b4ca86621e39068ee8b25524a44a21bbc1, task_id:4d185a5398be94edac0dd77fff094eb7f5c73ec4, slots:0-100}' to be equal to '{sub: cluster-slot-migration-import-started, source_node_id:28c64b3f462f3c29aa3c96c2ba5dff948dfe315b, destination_node_id:1382a4b4ca86621e39068ee8b25524a44a21bbc1, task_id:4d185a5398be94edac0dd77fff094eb7f5c73ec4, slots:0-100} {sub: cluster-slot-migration-import-completed, source_node_id:28c64b3f462f3c29aa3c96c2ba5dff948dfe315b, destination_node_id:1382a4b4ca86621e39068ee8b25524a44a21bbc1, task_id:4d185a5398be94edac0dd77fff094eb7f5c73ec4, slots:0-100}' (context: type eval line 29 cmd {assert_equal  [list  "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100"  ] [R 4 asm.get_cluster_event_log]} proc ::test)
```
If there is a delay to work to check, the ASM task may complete, so we
will get `started & completed` ASM log instead of only `started` log, it
feels fragile, so delete the check, we will check all logs later.
```
                restart_server -4 true false true save ;# rdb save
---> if there is a delay, the ASM task should complete
                # the asm task info in rdb will fire module event
                assert_equal  [list \
                    "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \
                ] [R 4 asm.get_cluster_event_log]
```
- **Start BGSAVE for slot snapshot ASAP**
Since we consider the migrating client as a replica that wants diskless
replication, so it will wait for repl-diskless-sync-delay` to start a
new fork after the last child exits. But actually slot snapshot can not
be shared with other slaves, so we can start BGSAVE for it immediately.

  also resolve internal ticket RED-177974.
…s#14711)

Add the memory overhead of the hotkeyStats structure to
`used_memory_overhead`, add `hotkeys-` prefix to hotkey keys in INFO and
remove `used_memory` in the hotkeys info section as it's unneeded (too
little memory for us to care about).

Tnx @oranagra for pointing
[this](redis#14680 (comment))
out.
Update Search to 8.6 RC1 version 8.5.90
To verify the pause duration, we need to wait for the client to be
unpause and the command to complete, so add `$rd read` to wait for the
command to finish.

The test failure was caused by $rd still being blocked and not closed in
the previous test, so the next test would get 2 blocked clients instead
of 1 client, causing the test to fail.
…is#14718)

1) Replace fixed sleep with wait_for_condition to avoid flaky test
failures when checking master_current_sync_attempts counter.

2) Similar to redis#14674, use
assert_lessthan_equal instead of assert_lessthan to verify the idle
time.
Fixes prefetch sizing so when the remaining work is smaller than
the effective max batch (2× configured), Redis prefetches it all
at once instead of splitting into an inefficient tiny tail batch.
This change is the last in the series (see redis#14200 and redis#14473) where we
store iterators
on the stack rather than allocating them the heap.
Optimizes ACL evaluation by adding a fast path for fully privileged users.
…edis#14690)

Addresses crash and clarifies errors around container commands.

- Update server.c to handle container commands with no subcommand: emit
"missing subcommand. Try HELP."; keep "unknown subcommand" for invalid
subcommands; for unknown commands, include args preview only when
present
- Add a test module command subcommands.internal_container with a
subcommand for validation
- Add unit test asserting missing subcommand error when calling the
internal container command without arguments
…4723)

There is a failure in CI:
```
*** [err]: Clients are evenly distributed among io threads in tests/unit/introspection.tcl
Expected '2' to be equal to '1' (context: type eval line 3 cmd {assert_equal $cur_clients 1} proc ::start_server)
```

There might be a client used for health checks (to detect if the server
is up)
that has not been freed timely. This can lead to an inaccurate count of
connected clients processed by IO threads. So we wait it to close
completely.
1. CLIENT_IO_CLOSE_ASAP is a flag for c->io_flags, which does not match
c->flags. The flag corresponding to c->flags is CLIENT_CLOSE_ASAP.
2. If we want to asynchronously free a client running on an io-thread,
we should check its c->io_flags to determine if CLIENT_IO_CLOSE_ASAP has
already been added. If it hasn't been added before, then
CLIENT_IO_CLOSE_ASAP should be added.
This allows users to specify exactly what per slot statistics are to be
collected -- CPU, network traffic and/or memory used.

The config accepts multiple values as a space-separated list:
  - cpu: Track CPU usage per slot (cpu-usec metric)
  - net: Track network bytes per slot (network-bytes-in, network-bytes-out metrics)
  - mem: Track memory usage per slot (memory-bytes metric)
  - yes: Enable all tracking (equivalent to "cpu net mem")
  - no: Disable all tracking (default)

Note: Memory tracking (mem) can ONLY be enabled at startup. If you try to enable
memory tracking via CONFIG SET when it wasn't enabled at startup, the command will
fail. However, you can disable memory tracking at runtime by removing the 'mem' flag.
Once disabled, memory tracking cannot be re-enabled without restarting the server.
# Problem

While introducing Async IO
threads(redis#13695) primary and replica
clients were left to be handled inside main thread due to data race and
synchronization issues. This PR solves this issue with the additional
hope it increases performance of replication.

# Overview

## Moving the clients to IO threads

Since clients first participate in a handshake and an RDB replication
phases it was decided they are moved to IO-thread after RDB replication
is done. For primary client this was trivial as the master client is
created only after RDB sync (+ some additional checks one can see in
`isClientMustHandledByMainThread`). Replica clients though are moved to
IO threads immediately after connection (as are all clients) so
currently in `unstable` replication happens while this client is in
IO-thread. In this PR it was moved to main thread after receiving the
first `REPLCONF` message from the replica, but it is a bit hacky and we
can remove it. I didn't find issues between the two versions.

## Primary client (replica node)

We have few issues here:
- during `serverCron` a `replicationCron` is ran which periodically
sends `REPLCONF ACK` message to the master, also checks for timed-out
master. In order to prevent data races we utilize`IOThreadClientsCron`.
The client is periodically sent to main thread and during
`processClientsFromIOThread` it's checked if it needs to run the
replication cron behaviour.

- data races with main thread - specifically `lastinteraction` and
`read_reploff` members of the primary client that are written to in
`readQueryFromClient` could be accessed at the same time from main
thread during execution of `INFO REPLICATION`(`genRedisInfoString`). To
solve this the members were duplicated so if the client is in IO-thread
it writes to the duplicates and they are synced with the original
variables each time the client is send to main thread ( that means `INFO
REPLICATION` could potentially return stale values).

- During `freeClient` the primary client is fetched to main thread but
when caching it(`replicationCacheMaster`) the thread id will remain the
id of the IO thread it was from. This creates problems when resurrecting
the master client. Here the call to `unbindClientFromIOThreadEventLoop`
in `freeClient` was rewritten to call `keepClientInMainThread` which
automatically fixes the problem.

- During `exitScriptTimedoutMode` the master is queued for reprocessing
(specifically process any pending commands ASAP after it's unblocked).
We do that by putting it in the `server.unblocked_clients` list, which
are processed in the next `beforeSleep` cycle in main thread. Since this
will create a contention between main and IO thread, we just skip this
queueing in `unblocked_clients` and just queue the client to main thread
- the `processClientsFromIOThread` will process the pending commands
just as main would have.

## Replica clients (primary node)

We move the client after RDB replication is done and after replication
backlog is fed with its first message.
We do that so that the client's reference to the first replication
backlog node is initialized before it's read from IO-thread, hence no
contention with main thread on it.

### Shared replication buffer

Currently in unstable the replication buffer is shared amongst clients.
This is done via clients holding references to the nodes inside the
buffer. A node from the buffer can be trimmed once each replica client
has read it and send its contents. The reference is
`client->ref_repl_buf_node`. The replication buffer is written to by
main thread in `feedReplicationBuffer` and the refcounting is intrusive
- it's inside the replication-buffer nodes themselves.

Since the replica client changes the refcount (decreases the refcount of
the node it has just read, and increases the refcount of the next node
it starts to read) during `writeToClient` we have a data race with main
thread when it feeds the replication buffer. Moreover, main thread also
updates the `used` size of the node - how much it has written to it,
compared to its capacity which the replica client relies on to know how
much to read. Obviously replica being in IO-thread creates another data
race here. To mitigate these issues a few new variables were added to
the client's struct:

- `io_curr_repl_node` - starting node this replica is reading from
inside IO-thread
- `io_bound_repl_node` - the last node in the replication buffer the
replica sees before being send to IO-thread.

These values are only allowed to be updated in main thread. The client
keeps track of how much it has read into the buffer via the old
`ref_repl_buf_node`. Generally while in IO-thread the replica client
will now keep refcount of the `io_curr_repl_node` until it's processed
all the nodes up to `io_bound_repl_node` - at that point its returned to
main thread which can safely update the refcounts.
The `io_bound_repl_node` reference is there so the replica knows when to
stop reading from the repl buffer - imagine that replica reads from the
last node of the replication buffer while main thread feeds data to it -
we will create a data race on the `used` value
(`_writeToClientSlave`(IO-thread) vs `feedReplicationBuffer`(main)).
That's why this value is updated just before the replica is being send
to IO thread.
*NOTE*, this means that when replicas are handled by IO threads they
will hold more than one node at a time (i.e `io_curr_repl_node` up to
`io_bound_repl_node`) meaning trimming will happen a bit less
frequently. Tests show no significant problems with that.
(tnx to @ShooterIT for the `io_curr_repl_node` and `io_bound_repl_node`
mechanism as my initial implementation had similar semantics but was way
less clear)

Example of how this works:

* Replication buffer state at time N:
   | node 0| ... | node M, used_size K |
* replica caches `io_curr_repl_node`=0, `io_bound_repl_node`=M and
`io_bound_block_pos`=K
* replica moves to IO thread and processes all the data it sees
* Replication buffer state at time N + 1:
| node 0| ... | node M, used_size Full | |node M + 1| |node M + 2,
used_size L|, where Full > M
* replica moves to main thread at time N + 1, at this point following
happens
   - refcount to node 0 (io_curr_repl_node) is decreased
- `ref_repl_buf_node` becomes node M(io_bound_repl_node) (we still have
size-K bytes to process from there)
- refcount to node M is increased (now all nodes from 0 up to M-1
including can be trimmed unless some other replica holds reference to
them)
- And just before the replica is send back to IO thread the following
are updated:
   - `io_bound_repl_node` ref becomes node M+2
   - `io_bound_block_pos` becomes L

Note that replica client is only moved to main if it has processed all
the data it knows about (i.e up to `io_bound_repl_node` +
`io_bound_block_pos`)

### Replica clients kept in main as much as possible

During implementation an issue arose - how fast is the replica client
able to get knowledge about new data from the replication buffer and how
fast can it trim it. In order for that to happen ASAP whenever a replica
is moved to main it remains there until the replication buffer is fed
new data. At that point its put in the pending write queue and special
cased in handleClientsWithPendingWrites so that its send to IO thread
ASAP to write the new data to replica. Also since each time the replica
writes its whole repl data it knows about that means after it's send to
main thread `processClientsFromIOThread` is able to immediately update
the refcounts and trim whatever it can.

### ACK messages from primary

Slave clients need to periodically read `REPLCONF ACK` messages from
client. Since replica can remain in main thread indefinitely if no DB
change occurs, a new atomic `pending_read` was added during
`readQueryFromClient`. If a replica client has a pending read it's
returned back to IO-thread in order to process the read even if there is
no pending repl data to write.

### Replicas during shutdown

During shutdown the main thread pauses write actions and periodically
checks if all replicas have reached the same replication offset as the
primary node. During `finishShutdown` that may or may not be the case.
Either way a client data may be read from the replicas and even we may
try to write any pending data to them inside `flushSlavesOutputBuffers`.
In order to prevent races all the replicas from IO threads are moved to
main via `fetchClientFromIOThread`. A cancel of the shutdown should be
ok, since the mechanism employed by `handleClientsWithPendingWrites`
should return the client back to IO thread when needed.

## Notes

While adding new tests timing issues with Tsan tests were found and
fixed.

Also there is a data race issue caught by Tsan on the `last_error`
member of the `client` struct. It happens when both IO-thread and main
thread make a syscall using a `client` instance - this can happen only
for primary and replica clients since their data can be accessed by
commands send from other clients. Specific example is the `INFO
REPLICATION` command.
Although other such races were fixed, as described above, this once is
insignificant and it was decided to be ignored in `tsan.sup`.

---------

Co-authored-by: Yuan Wang <wangyuancode@163.com>
Co-authored-by: Yuan Wang <yuan.wang@redis.com>
…reshold-based (redis#14692)

This PR optimizes peak memory tracking by moving from **per-command
checks** to a **threshold-based mechanism** in `zmalloc`.

Instead of updating peak memory on every command, peak tracking is now
triggered only when a thread's memory delta exceeds **100KB**. This
reduces runtime overhead while keeping peak memory accuracy acceptable.

## Implementation Details

- Peak memory is tracked atomically in `zmalloc` when a thread's memory
delta exceeds 100KB
- Thread-safe peak updates using CAS
- Peak tracking considers both:
  - current used memory
  - zmalloc-reported peak memory

## Performance Results (ARM AArch64)

All performance numbers were obtained on an **AWS m8g.metal (ARM
AArch64)** instance.

The database was pre-populated with **1M keys**, each holding a **1KB
value**.
Benchmarks were executed using memtier with a **10 SET : 90 GET ratio**
and **pipeline = 10** ([full benchmark spec.
here](https://github.com/redis/redis-benchmarks-specification/blob/main/redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-10.yml)).

| Environment | Baseline `redis/redis` unstable (median ± std.dev) |
Comparison `paulorsousa/redis`
`f05a4bd273cb4d63ff03d33e6207837b6e51de86` (median) | % change (higher
better) | Note |

|------------------------------|----------------------------------------------------|----------------------------------------------------------------------------------:|--------------------------|-----------------------|
| oss-standalone | 802,830 ± 0.2% (7 datapoints) | 796,660 | -0.8% | No
change |
| oss-standalone-02-io-threads | 982,698 ± 0.6% (7 datapoints) | 980,520
| -0.2% | No change |
| oss-standalone-04-io-threads | 2,573,244 ± 1.9% (7 datapoints) |
2,630,931 | +2.2% | Potential improvement |
| oss-standalone-08-io-threads | 2,343,609 ± 1.6% (7 datapoints) |
2,455,630 | +4.8% | Improvement |
Add key allocation sizes histograms based on previous memory accounting work
in redis#14363 and redis#14451.

The histograms are exposed via `INFO keysizes` and use logarithmic (power-of-2) bins,
similar to current key sizes/length histogram implementation in the following fields:

    db0_distrib_lists_sizes:1=...,2=...,4=...
    db0_distrib_sets_sizes:1=...,2=...,4=...
    db0_distrib_hashes_sizes:1=...,2=...,4=...
    db0_distrib_zsets_sizes:1=...,2=...,4=...

To avoid confusion with existing distrib_strings_sizes histograms which are based on
string lengths we don't report allocation sizes histograms for strings.

So far per key and per slot memory accounting code has been relying type specific functions
(hashTypeAllocSize(), listTypeAllocSize(), zsetAllocSize(), etc) for computing data structure
allocation sizes since it's faster and we only need to track size deltas and not the complete
allocation size along with the kvobj and key length overhead. In order to keep the allocation
sizes histogram consistent, memory accounting code has been switched to use kvobjAllocSize()
instead which does return the total allocation size.

Note that the feature is enabled with `key-bytes-stats` or `cluster-slot-stats` config in redis
config file on startup.
…dis#14725)

## Summary
Adds missing IDMP configuration parameters to redis.conf, previously
ommitted in redis#14615

## Changes
- Added `stream-idmp-duration` configuration parameter with
documentation
- Added `stream-idmp-maxsize` configuration parameter with documentation
- Both parameters were already implemented in the code (src/config.c,
src/server.h) but were missing from redis.conf

## Configuration Parameters

### stream-idmp-duration
- **Purpose**: Duration (in seconds) to remember IDMP identifiers for
duplicate detection
- **Range**: 1 to 86400 seconds (1 second to 24 hours)
- **Default**: 100 seconds
- **Modifiable**: Yes, via CONFIG SET at runtime

### stream-idmp-maxsize
- **Purpose**: Maximum number of IDMP identifiers to track per producer
per stream
- **Range**: 1 to 10000 entries
- **Default**: 100 entries
- **Modifiable**: Yes, via CONFIG SET at runtime
…ock is available (redis#14713)

This PR reduces per-command `ustime()` syscalls in `call()` by reusing
cached time and batching wall-clock updates when HW monotonic time is
available.

### What changed
- Pass `server.ustime` to `enterExecutionUnit()` instead of calling
`ustime()`.
- Use HW monotonic clock to measure duration and accumulate it across
commands.
- Refresh cached time with `ustime()` only when accumulated duration >
**10µs** or after **25 commands**.
- Fallback to direct `ustime()` when HW monotonic clock isn’t available.

### Impact
- `ustime` CPU: **4.58% → 0.25%**, which leads to ~4% boost on max QPS

### Notes
- Time drift is bounded (≤10µs or 25 commands).
- No behavior change on non-HW-monotonic systems.

---------

Co-authored-by: Yuan Wang <yuan.wang@redis.com>
…redis#14727)

### Summary

Adds `expired_keys_active` and `expired_subkeys_active` counters to
track keys and hash fields expired by the active expiration cycle,
distinguishing them from lazy expirations.
These new metrics are exposed in INFO stats output.

### Motivation

Currently, Redis tracks the total number of expired keys (expired_keys)
and expired hash fields (expired_subkeys), but there's no way to
differentiate between expirations triggered by active expire and lazy
expire.

---------

Co-authored-by: Moti Cohen <moti.cohen@redis.com>
in redis#14440, we remove the refcount
check in
[tryDeferFreeClientObject](redis@235e688#diff-252bce0cc340542712f0c1adf62e9035ea47a4a064321fbf40ec3dd4b814aaf2R1509),
it is ok in 8.4 version, since after command execution, the refcount of
a kvobject always is 1.
but in redis#14608 (8.6 RC1) we change this assumption, increment refcount
when a client refer a kvobject in reply, so now if the refcount of
kvobject is more than 1, we may let the io thread call `decrRefCount`,
there is data race, maybe it causes memory leak.
Adds startup-time security warnings when the default user permits
unauthenticated access, with behavior dependent on protected-mode and
bind settings.
Warnings are skipped in Sentinel mode since it intentionally
disables protected-mode by design.

- No password + no protected-mode + no bind: warn about accepting
  connections from any IP/interface
- No password + no protected-mode: warn about accepting connections
  from any IP on configured interface
- No password + protected-mode enabled: warn about accepting
  connections from local clients
…ter (redis#14742)

Some hotkeys cpu metrics display time in milliseconds others in
microseconds.

Change the metrics showing time of command executions to all use
microseconds and use the `-us` postfix to show that.

Also, disable the `SLOTS` param for `HOTKEYS START` if we are not in
cluster mode.
…is#14739)

Optimizes handling of clients with referenced replies by embedding the
`pending_ref_reply_node` list node in `client` and avoiding
per-operation node alloc/free.

there is an improvement: ~2% on 4 and 16 io-threads. ~1% on 8 io-threads
This pull request vectorizes the 8-bit quantization vector-search path
in a similar was as the non-quantization path.
The assembly intrinsics are a bit more complicated than in the
non-quantization path, since we are operating on 8-bit integers and we
need to worry about preventing overflow. Thus, after loading the 8-bit
integers, they are extended into 16-bits before multiplying and
accumulating into 32-bit integers.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
## Overview
This PR optimizes Redis Streams consumer group performance by replacing
the `pel_by_time` rax tree with a doubly-linked list, delivering
significant performance improvements for NACK updates and XREADGROUP
CLAIM operations while also reducing memory usage.

## The Problem
Consumer groups maintain a time-ordered index of pending entries using a
radix tree (`pel_by_time`). Every time a pending entry is reclaimed or
delivered, we need to update its delivery time, which currently
requires:

```c
raxRemovePelByTime(group->pel_by_time, old_time, &id);  // O(k) where k=key length
nack->delivery_time = current_time;
raxInsertPelByTime(group->pel_by_time, current_time, &id); // O(k) where k=key length
```

## The Key Insight

**99% of delivery_time updates set the value to the current time** —
which means they're appending to the tail of a time-ordered structure.

We're using a radix tree (O(k) operations where k is key length, plus
tree traversal overhead) for what is essentially an append-only workload
(should be O(1)).

## The Solution

Replace the rax tree with a doubly-linked list embedded directly in each
`streamNACK`:

```c
typedef struct streamNACK {
    mstime_t delivery_time;
    uint64_t delivery_count;
    streamConsumer *consumer;
    listNode *cgroup_ref_node;
    streamID id;                    // NEW
    struct streamNACK *pel_prev;    // NEW
    struct streamNACK *pel_next;    // NEW
} streamNACK;
```

Now updating a NACK becomes:
```c
pelListUpdate(group, nack, current_time);  // O(1): unlink + append
```

## Why This Works

**Typical case (99%):** Delivery time = current time
- Unlink from current position: O(1) — just update 2-4 pointers
- Append to tail: O(1) — update tail pointer and link

**Edge case (1%):** XCLAIM with explicit past IDLE time
- Still handled correctly by `pelListInsertSorted()` which scans
backward from tail
- Rare enough that O(N) worst case doesn't matter

## Memory Reduction

The linked list approach uses less memory than the rax tree:

**What we add:**
- 3 new fields in `streamNACK`: `id` (16 bytes) + `pel_prev` (8 bytes) +
`pel_next` (8 bytes) = 32 bytes per entry

**What we remove:**
- Entire `pel_by_time` rax tree with its node overhead (~40-50 bytes per
entry)

**Net result:** Lower memory footprint per pending entry, plus better
cache locality from eliminating the separate tree structure.

## Performance Impact

### Theoretical Analysis

| Operation | Before | After |
|-----------|--------|-------|
| NACK update | O(k) × 2 + tree overhead | O(1) |
| CLAIM iteration | O(k) per entry + traversal | O(1) per entry |

*k = key length (32 bytes: timestamp + stream ID)*

For a consumer group with 10,000 pending entries claiming 100 oldest:
- **Before:** Tree traversal + key comparisons for each operation
- **After:** Simple pointer updates

**Key Findings:**
- **28% higher throughput** for XREADGROUP with CLAIM
- **22% lower average latency** (0.195ms → 0.152ms)
- **21% lower P99 latency** (0.212ms → 0.168ms)
- XADD performance unchanged (69K ops/sec both implementations)
oshadmi and others added 26 commits May 20, 2026 15:45
Updates the bundled RediSearch module version used by the Redis 8.8
branch from `v8.7.91` to `v8.8.0`.
## Summary

Bumps `MODULE_VERSION` for RedisBloom, RedisJSON, and RedisTimeSeries
from `v8.7.91` to `v8.8.0`.

| Module          | From    | To     |
|-----------------|---------|--------|
| RedisBloom      | v8.7.91 | v8.8.0 |
| RedisJSON       | v8.7.91 | v8.8.0 |
| RedisTimeSeries | v8.7.91 | v8.8.0 |

## Module changes (v8.7.91 → v8.8.0)

### RedisBloom
([v8.7.91...v8.8.0](RedisBloom/RedisBloom@v8.7.91...v8.8.0))
- MOD-15418 — fix load rdb mem leak
([redis#1007](RedisBloom/RedisBloom#1007))
- Revert "fix redis version"
([redis#1010](RedisBloom/RedisBloom#1010))
- bump version to v8.8.0

### RedisJSON
([v8.7.91...v8.8.0](RedisJSON/RedisJSON@v8.7.91...v8.8.0))
- Revert "fix redis version"
([redis#1597](RedisJSON/RedisJSON#1597))
- bump version v8.8.0

### RedisTimeSeries
([v8.7.91...v8.8.0](RedisTimeSeries/RedisTimeSeries@v8.7.91...v8.8.0))
- MOD-14439 — Detect cluster topology changes during a multi-shard
command and return an appropriate error
([redis#1930](RedisTimeSeries/RedisTimeSeries#1930))
- Revert Docker and CI redis-ref from 8.8 back to unstable
([redis#2033](RedisTimeSeries/RedisTimeSeries#2033))
- bump version v8.8.0

## Test plan
- [ ] CI passes for all three modules at the new pinned version
- [ ] \`make all\` builds RedisBloom, RedisJSON, RedisTimeSeries cleanly
against \`unstable\`
- [ ] Module tests run under \`make test\`

🤖 Generated with [Claude Code](https://claude.com/claude-code)



Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This is a follow-up to
[redis#14938](redis#14938), which
upgraded GitHub Actions to newer stable versions for the upcoming
Node.js 20 deprecation on GitHub Actions runners.

That PR missed two remaining action updates in `daily.yml`:

- `cross-platform-actions/action`
- `py-actions/py-dependency-install`

### Why replace `py-actions/py-dependency-install`

`py-actions/py-dependency-install` is no longer an actively maintained
dependency installation action, so keeping it in CI increases
maintenance and supply-chain risk over time.

The replacement uses GitHub's official `actions/setup-python` action,
which is actively maintained and supports built-in `pip` dependency
caching. Installing dependencies with `python -m pip install -r
./utils/req-res-validator/requirements.txt` also makes the workflow
behavior explicit and easier to debug.
Add new module API `RM_GetClusterNodeSlotRanges` that allows modules
to query slot ranges for any cluster node by its node ID, not just the
local node:

```c
RedisModuleSlotRangeArray *RM_GetClusterNodeSlotRanges(RedisModuleCtx *ctx, const char *nodeid)
```

---------

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
Co-authored-by: Yuan Wang <yuan.wang@redis.com>
This is based on
[valkey-io/valkey#849](valkey-io/valkey#849)

Introduce `concurrency` and `group` keywords into workflows execpt from
`redis_docs_sync` (which is triggered on release event only) and
`daily`.

https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency

With this, only one workflow can run in a group at any time.

---------

Co-authored-by: Yury-Fridlyand <yury.fridlyand@improving.com>
## Problem

The `redis-cli` reverse-search test for the no-result case can be flaky
in slower CI environments.

`read_cli` may return too early when CLI output is fragmented or
delayed. It currently gives up after only 5 consecutive empty reads,
with a 10ms delay between reads, which can make the test assert before
the expected `(empty array)` output is printed.

## Changes

Increase the `read_cli` consecutive empty-read threshold from `5` to
`100`.

This keeps the existing read behavior unchanged when data is available,
but allows the helper to wait longer for delayed/fragmented CLI output
before giving up.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
…15110)

## Problem

`strtod()` handles some `nan(n-char-sequence)` inputs differently across
libc implementations. For example, `nan(ab!c)` and `nan(ab c)` may be
accepted on some platforms but rejected on others.

The existing test treated these inputs as fixed invalid cases, which can
fail on platforms whose libc accepts them.

## Changes

- Move libc-dependent `nan(...)` cases out of the fixed invalid test
list.
- Add a helper to verify `fast_float_strtod()` matches the platform
`strtod()` behavior for these cases, including value, `endptr`, and
success/failure status.
- Keep the existing parser behavior unchanged.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
)

`stat_io_reads_processed[]` and `stat_io_writes_processed[]` were
per-IO-thread arrays inside `struct redisServer` that suffer from false
sharing. This PR moves the two stat counters into the IOThread struct,
which is already `__attribute__((aligned(CACHE_LINE_SIZE)))`. Each IO
thread's counters now sit on a separate cache line, eliminating the
cross-thread contention.
- Added io_reads_processed and io_writes_processed fields to IOThread struct
- Removed stat_io_reads_processed[] and stat_io_writes_processed[] from struct redisServer
- Made IOThreads[] non-static with extern declaration in server.h
### Issue

This refines the fix from redis#15119.

The previous change increased the generic `read_cli` empty-read retry
threshold from 5 to 100 to reduce flakiness in the redis-cli reverse
search no-result test. While effective, that made every interactive CLI
read potentially wait longer.

### Changes

This restores the original generic `read_cli` behavior and uses targeted
pattern-based waiting only where needed.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
### Issue

CLUSTER SLOT-STATS network-bytes-in, in-line buffer processing can fail
because the test sends an inline SET through a deferring client and
immediately checks slot stats from another client.

$rd flush only guarantees the request was written to the socket; it does
not guarantee Redis has processed the command and updated
network-bytes-in.

### Change

Wait for the inline SET reply before reading CLUSTER SLOT-STATS.
redis#14844)

## Summary

In the reply copy-avoidance path (bulkStrRef), the RESP bulk-string
prefix `$<len>\r\n` was formatted eagerly on the main thread inside
`_addBulkStrRefToBufferOrList()`. This PR defers that formatting to
write time via a new idempotent helper `formatBulkStrRefPrefix()`, so
the work happens on the IO thread for clients served by IO threads.

---------

Co-authored-by: Yuan Wang <yuan.wang@redis.com>
## Issue

The vector set Python tests intentionally use two clients: 
- the default client (`self.redis`) for the existing RESP2-oriented test
expectations
- `self.redis3` for RESP3-specific coverage.

However, the default client did not explicitly set a protocol, so it
depended on redis-py's default behavior. With newer redis-py versions,
RESP3 is now the default
protocol(redis/redis-py#4052). In particular,
vector set replies such as `VSIM ... WITHSCORES` may be parsed into
map/dict-like structures instead of the RESP2 flat-array shape assumed
by existing tests.

## Changes

Explicitly create the default primary and replica Redis clients with
`protocol=2`.
`self.redis3` is left unchanged and continues to use `protocol=3` for
RESP3-specific test coverage.
…edis#15284)

asmSyncWithSource() built the error with sdscatfmt() and a "%.40s"
specifier. sdscatfmt() is not printf: it parses only the single byte
after '%' and ignores width/precision, so "%.40s" emits a literal '.',
consumes no argument, and task->source is never printed. The message
rendered as "Source node .40s was not found", dropping the node name.

task->source is a CLUSTER_NAMELEN (40) byte, non-NUL-terminated buffer
(filled via memcpy and always read with an explicit length elsewhere),
so simply switching to sdscatfmt's "%s" would strlen() past the buffer.
Use sdscatprintf(), which honors the "%.40s" precision and bounds the
read to 40 bytes -- matching the sibling error paths in this function
that already use sdscatprintf().
…edis#15285)

Fixes: test faulure in
https://github.com/redis/redis/actions/runs/26698983853/job/78688240089

While investigating the failure, I added temporary debug output:

```tcl
puts "cpu_time_array=$cpu_time_array"
puts "num_returned_cpu=$num_returned_cpu"
puts "res=$res"
```

The failing SAMPLE 1000 run produced:

```
cpu_time_array=key_010 3 key_000 3 key_015 2 key_005 2 key_013 2 key_056 2 key_054 2 key_046 1 key_044 1 key_049 1
num_returned_cpu=10
res=5
```

The test becomes statistically unstable at SAMPLE 1000. With only ~50
sampled operations out of 50k requests, the accumulated CPU times
collapse into a very narrow range (observed: 1–3 µs), causing frequent
ties between hot and cold keys near the Top-K cutoff. The resulting
failures are driven by sampling variance rather than HOTKEYS
correctness.

Since the test already covers sampling behavior with ratios 1, 100, and
500, removing the 1000 case preserves coverage while eliminating a
configuration that is too sparse to produce stable, reproducible
assertions.
Partial fix redis#11085

## Bug

`parseRedisUri()` called `percentDecode(curr, 0)` for URIs of the form
`redis://:password@host` and stored the resulting **empty-but-non-NULL**
sds into `connInfo->user`.
`cliAuth()` only checks `user == NULL` when deciding between legacy
`AUTH <pass>` and ACL `AUTH <user> <pass>`, so the empty username was
sent as `AUTH "" <password>`, which the server rejects with `WRONGPASS`.

## Fix

In parseRedisUri(), treat explicitly empty username or password
components as NULL rather than empty SDS strings. This allows cliAuth()
to fall back to legacy single-argument AUTH when the username is empty,
and to skip AUTH entirely when the password is empty.

Before replacing URI-parsed credentials, existing connInfo->user and
connInfo->auth values are freed to avoid leaks and preserve the expected
"later arguments override earlier ones" behavior.

As a related cleanup, -a/--pass and --user now also free previously
assigned values before reassignment, fixing the same leak pattern when
those options are specified multiple times.
### Issue
The module datatype defrag test sends 20k commands through a deferred
client before reading any replies. On slower CI environments this can
cause replies to accumulate and fill TCP/socket buffers, leading to
flaky `I/O error reading reply` failures.

### Change

Fix by batching deferred writes and reply drains, following the same
approach used in redis#14886.
Updates the in-source documentation for RedisModule_GetClusterNodeInfo
so module authors size caller buffers correctly.
…edis#15252)

Add `raxFindLink()` + `raxInsertAt()`: a two-step commit API that
lets a caller walk the rax once to find a key, then commit an insert
at the recorded position without re-walking. Today's
"lookup then insert" pattern (`raxFind` + `raxInsert` on miss) walks
the tree twice; this change collapses the worst case to a single
walk.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
… validation (redis#15187)

## Summary

Add overflow checks for attacker-controlled `uint32_t` length fields in
`clusterProcessPacket()` before adding them to `explen` (`uint32_t`).
Without these checks, crafted PUBLISH/PUBLISHSHARD and MODULE cluster
bus messages can wrap `explen` to a small value, bypassing the `totlen
!= explen` validation and causing heap out-of-bounds reads or OOM aborts
in the processing path.

## Problem

In `clusterProcessPacket()`, the expected packet length for PUBLISH and
MODULE messages is computed by summing struct overhead with
variable-length fields read from the packet header via `ntohl()`:

```c
// PUBLISH (line 2841-2846)
explen += sizeof(clusterMsgDataPublish) - 8 +
    ntohl(hdr->data.publish.msg.channel_len) +
    ntohl(hdr->data.publish.msg.message_len);

// MODULE (line 2855-2858)
explen += sizeof(clusterMsgModule) - 3 + ntohl(hdr->data.module.msg.len);
```

Both `channel_len + message_len` (PUBLISH) and `len` (MODULE) are
`uint32_t` values from the network packet. Their addition to `explen`
can overflow `uint32_t`, wrapping to a small value that matches
`totlen`, passing the size validation at line 2864.

**Example (PUBLISH):** With `channel_len = 0x80000000` and `message_len
= 0x80000000`, their sum overflows to `0`, making `explen` equal to just
the base struct size. An attacker sets `totlen` to match. The validation
passes, and the processing path at line 3273 calls
`createStringObject()` with the original 2GB lengths, reading far past
the received buffer.

**Attack vector:** Reachable via the cluster bus port (default: data
port + 10000), which does not require authentication by default.

## Fix

Extract `ntohl` values into local variables and check each addition
against `UINT32_MAX` before performing it. Reject the packet with a
warning log if overflow is detected. The computed `explen` is identical
to the original for all non-overflowing (legitimate) inputs.

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: debing.sun <debing.sun@redis.com>
**Summary**

The stream RDB/RESTORE loader read the first element of each stream
listpack node (the "valid entries" count) and immediately decoded it as
an integer, but under shallow validation (`sanitize-dump-payload no`)
only the listpack header had been checked — never the first entry
itself. A crafted payload whose first entry declares an oversized string
encoding (e.g. `LP_ENCODING_32BIT_STR` claiming `0x7FFFFFFF` bytes)
caused `lpGetIntegerValue()` to read encoding-dependent bytes past the
end of the listpack, triggering an out-of-bounds read / crash.

**Changes**

1. **First-entry validation before integer decode (`rdb.c`)**
In `rdbLoadObject`, the stream-loading path now obtains the first
element via `lpValidateFirst()` instead of `lpFirst()`, and then
validates that entry with `lpValidateNext()` before
`lpGetIntegerValue()` decodes it. If the entry is malformed, the load is
rejected cleanly via `rdbReportCorruptRDB("Stream listpack integrity
check failed.")` with proper cleanup (`sdsfree(nodekey)`,
`decrRefCount(o)`, `zfree(lp)`) and an early `NULL` return, matching how
other corrupt-payload cases are handled. This closes the gap where only
the listpack header — not its first entry — was checked under
`sanitize-dump-payload no`.

2. **Test (`corrupt-dump.tcl`)**
A new test (`corrupt payload: stream listpack entry with corrupt
encoding crashes lpFirst`) exercises the rejection path. It disables
payload sanitization and checksum validation, then issues a `RESTORE`
with a hand-built stream payload whose listpack has a valid header but a
first entry encoded as `LP_ENCODING_32BIT_STR` (`0xF0`) declaring a
`0x7FFFFFFF`-byte string that runs past the end of the listpack. The
test asserts the command fails with `*Bad data format*`, verifies the
`*Stream listpack integrity check failed*` warning is logged, and
confirms the server survives (`r ping`).
…edis#15226)

## Summary
- Refreshes the per-OS **Build from source** sections in `README.md` to
align with the OSes Redis currently tests against.
- Drops Ubuntu 20.04 (Focal) and the macOS 15 "Support and instructions
will be provided at a later date" placeholder.
- Adds new sections for Ubuntu 26.04 (Resolute), AlmaLinux/Rocky 10.1+,
and Alpine 3.23+.
- Renames Debian 11/12 → 12/13 (Bookworm/Trixie) and AlmaLinux/Rocky 9.5
→ 9.7+, and bumps the tested Docker images in each section.
- Updates the macOS heading to cover macOS 14 (Sonoma), 15 (Sequoia),
and 26 (Tahoe), and notes that the instructions apply to both Intel and
Apple Silicon (ARM) Macs. Adds `LTO=0` to the build step, bumps the Rust
pin from 1.80.1 → 1.94.0, and removes GNU libtool from the front of
`PATH` so `RediSearch` builds cleanly — see "Upstream issues surfaced
during validation" below.

To keep maintenance manageable, versions of the same OS family with
identical steps are grouped (Debian 12+13 in one section;
AlmaLinux/Rocky 8.10, 9.7+, 10.1+ each kept as a single section).

## Upstream issues surfaced during validation
The notes inside the new and updated sections document workarounds (and
remaining gaps) discovered while validating these instructions
end-to-end with `BUILD_WITH_MODULES=yes` against the `unstable` HEAD.
They look like issues in the `RediSearch` module's build/Rust
integration rather than Redis core, so they're called out here so
reviewers can decide whether to fix them at the source or keep the
workarounds in the README:

- **Ubuntu 26.04**: clang/LLVM 21 + CMake 4.x from `apt` are not
compatible with the modules build. The section pins CMake to 3.31.6 via
`pip3` in a venv and adds `lld`, `llvm`, and `libcrypt-dev`. With those,
all four modules build cleanly.
- **AlmaLinux/Rocky 10.1+**: The system clang (LLVM 20) lags the Rust
toolchain (LLVM 21) installed via `INSTALL_RUST_TOOLCHAIN=yes`, which
trips `RediSearch`'s cross-language LTO check. This still needs an
upstream fix in `RediSearch` and is the only remaining gap preventing
`redisearch.so` from building on AlmaLinux/Rocky 10.
- **Alpine 3.23**: the section builds `redis-server`, `redisbloom.so`,
`rejson.so`, and `redistimeseries.so`. `redisearch.so` does not build on
Alpine 3.23 because `RediSearch` source uses Rust 1.94 stabilized
features (e.g. `Box::new_zeroed_slice`) while Alpine 3.23 ships Rust
1.91; expected to build once Alpine bumps Rust to ≥ 1.94 (1.95 is
already available on `alpine:edge`). The section deliberately omits
`INSTALL_RUST_TOOLCHAIN=yes` and uses Alpine's packaged
dynamically-linked `rust`/`cargo`, because the official rust-lang.org
musl toolchain is fully statically linked and prevents `bindgen` from
`dlopen`-ing `libclang.so` for `RedisJSON`.
- **macOS 14/15/26**: `RediSearch`'s modules build has three
macOS-specific issues. The section now contains the workarounds; the
underlying issues should probably be fixed in `RediSearch`:
1. `LTO=1` by default but `RediSearch`'s build script aborts on
non-Linux with `Error: LTO is only supported on Linux`. The section sets
`LTO=0`.
2. `RediSearch` source uses Rust edition 2024 and 1.94-stabilized
features (same root cause as the Alpine 3.23 finding above). The
section's Rust pin was bumped from `1.80.1` to `1.94.0`; older Rust
fails with `feature edition2024 is required`.
3. `RediSearch`'s CMake calls `libtool -static` (BSD libtool syntax) to
bundle a unified static archive. The section's `PATH` no longer prepends
`$HOMEBREW_PREFIX/opt/libtool/libexec/gnubin`, so Apple's
`/usr/bin/libtool` wins for that step.

## Test plan
Validated end-to-end via Docker against `unstable` HEAD:
- [x] `BUILD_WITH_MODULES=yes` on `ubuntu:26.04` — produces
`redis-server` + all four module `.so`s (`redisbloom`, `redisearch`,
`rejson`, `redistimeseries`).
- [x] `BUILD_WITH_MODULES=yes` on `almalinux:10.1` — produces
`redis-server` + three module `.so`s (`redisbloom`, `rejson`,
`redistimeseries`); `redisearch.so` blocked on the upstream issue noted
above.
- [x] `BUILD_WITH_MODULES=yes` on `alpine:3.23` — produces
`redis-server` + three module `.so`s (`redisbloom`, `rejson`,
`redistimeseries`); `redisearch.so` blocked on Alpine's Rust version
lagging the `RediSearch` source's Rust 1.94 requirement.
- [x] Smoke-test (dependency install) on `ubuntu:26.04`,
`debian:trixie`, `almalinux:9.7`, `almalinux:10.1`, `alpine:3.23` — all
package install commands succeed.
- [x] `BUILD_WITH_MODULES=yes` on macOS 26 (Tahoe), arm64 (mac2.metal
EC2) — produces `redis-server` + all four module `.so`s. Validation
surfaced the three macOS issues now documented above; with the section's
new `LTO=0`, Rust 1.94 pin, and PATH adjustment, the build is end-to-end
clean. macOS 14/15 not separately re-validated (package list and step
structure unchanged across the three versions).

🤖 Generated with [Claude Code](https://claude.com/claude-code)


---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…edis#15108)

### Problem


On Ubuntu 26.04, running `make test` shows these GCC warnings:
```shell
resp_parser.c: In function ‘parseBulk’:
resp_parser.c:44:15: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   44 |     char *p = strchr(proto+1,'\r');
      |               ^~~~~~
resp_parser.c: In function ‘parseSimpleString’:
resp_parser.c:63:15: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   63 |     char *p = strchr(proto+1,'\r');
      |               ^~~~~~
resp_parser.c: In function ‘parseError’:
resp_parser.c:71:15: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   71 |     char *p = strchr(proto+1,'\r');
      |               ^~~~~~
resp_parser.c: In function ‘parseLong’:
resp_parser.c:79:15: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   79 |     char *p = strchr(proto+1,'\r');
      |               ^~~~~~
```

### Changed

It updates local pointer types from `char *` to `const char *` without
changing runtime behavior.
## Problem

On Linux LoongArch64, the SIGSEGV/SIGBUS signal handler in `src/debug.c`
never prints the `Crashed running the instruction at: ...` line, and the
dumped register block reads Dumping of registers not supported for this
OS/arch. The crash report is significantly less useful than on other
supported architectures.

This also causes the "Test module crash when info crashes with a
segfault" case in `tests/unit/moduleapi/crash.tcl` to fail on
loongarch64-linux.

## Cause

In `getAndSetMcontextEip` (`src/debug.c`), the Linux `#if … #elif …`
chain handles `x86/x86_64/ia64/riscv/arm/aarch64` but has no LoongArch64
branch, so it falls through to `NOT_SUPPORTED()` and returns `NULL`.
`logRegisters` has the same gap and hits its own `NOT_SUPPORTED()` for
LoongArch64.

## Fix

Add LoongArch branches to both functions:

- `getAndSetMcontextEip`: read/write `uc->uc_mcontext.__pc`.
- `logRegisters`: dump `r1..r31` and `pc`, then feed `__gregs[3]` (sp)
to `logStackContent`.

Register names follow the [LoongArch Procedure Call
Standard](https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc);
field names follow the glibc `<sys/ucontext.h>` for loongarch64-linux.
`r0` (hardwired zero) and `r21` (reserved, non-allocatable) are
intentionally omitted.
…s#15263)

## Summary

The cluster bus PING/PONG/MEET packet parser validated extension padding
and total length but never checked that string-carrying extensions are
properly null-terminated, allowing a crafted packet to trigger
out-of-bounds reads when the payload is later consumed as a C string.

1. **Null-termination check for hostname and human-nodename extensions
(`cluster_legacy.c`)**

Added a check inside the existing extension-validation loop in
`clusterProcessPacket`: for `CLUSTERMSG_EXT_TYPE_HOSTNAME` and
`CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME` extension types, it verifies that
the data portion is non-empty (`datalen > 0`) and that the last byte is
`'\0'`. Packets failing this check are rejected with a warning log and
an early return, the same way other malformed-extension cases are
handled.

2. **Test (`hostnames.tcl`)**

A new test exercises the rejection path by constructing a raw
cluster-bus PING packet with a 32-byte hostname extension that contains
no `'\0'`, sending it directly to a node's bus port, and verifying the
packet is dropped (warning logged, hostname not updated in `CLUSTER
NODES`). Two helper procs (`build_cluster_bus_ping` and
`build_hostname_extension`) build the binary packet from scratch in Tcl,
allowing fine-grained control over extension contents without needing a
modified Redis sender.
…is#14704)

Avoid zmalloc_size() in kvobjAllocSize() and use approximation instead.

Since for ongoing key allocation histograms work (redis#14695) we need to
call
kvobjAllocSize() more often on hot paths, using zmalloc_size() would
cause
unnecessary performance overhead.
@sundb sundb changed the title test deps sanitizer Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.