Commit graph

13179 commits

Author SHA1 Message Date
lovelypiska
4edccfa5fb
Merge 646a7d98d3 into 1aa21f97d2 2026-07-14 16:29:23 +00:00
Slavomir Kaslev
1aa21f97d2
Fix parallel builds with make -j (#15459)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
After #15253 parallel builds started to warn:

```
  ~/..../redis$ make -j
  ==> Building main Redis (src/)
  make[1]: warning: jobserver unavailable: using -j1.  Add '+' to parent make rule.
  make[1]: Entering directory '~/..../redis/src'
      CC Makefile.dep
  make[1]: warning: jobserver unavailable: using -j1.  Add '+' to parent make rule.
  ....
```

and use only a single process when building, ignoring the `-j` flag.

Fix this by invoking build scripts with `+` prefix to let `make` know we
spawn sub-make, so it keeps the jobserver pipe fd open for the script.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a
summary for commit ca8c7c13ee. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-07-14 13:02:08 +03:00
Mincho Paskalev
aa301bfd38
Fix IO thread busy looping for repl clients (#15329)
Fix for issue https://github.com/redis/redis/issues/15311 

Issue is present after https://github.com/redis/redis/pull/14335. As
described there - a replica client may enter a busy loop inside IO
thread during idle period after a period of high traffic. Issue happens
when the client still has a write handler attached but writing is
disabled. This can happen in the following scenario:
- `processClientsFromMainThread` installs a write handler for the client
after `writeToClient` did not succeed in writing all of the reply
- `IOThreadClientsCron` sends the client to main thread (while the write
handler is still installed)
- The client is then send back to IO thread where
`processClientsFromMainThread` is able to finish writing all of the
reply data during `writeToClient(c, 0)` call, which does not uninstall
the write handler.
- If the traffic stops at this moment the write handler will
continuously be called by the epoll loop, thus creating a busy loop
inside IO thread.

Generally all clients are prone to having the same problem but I presume
it was exhibited now, since replica clients always have big replies when
traffic is high, whereas normal clients tend not to have sustained
outgoing big traffic (replies are mostly small - OK, some value, etc..)

Fix uninstalls the write handler each time a client is send to main
thread.
2026-07-14 12:23:34 +03:00
Ozan Tezcan
9e5614d837
Feed replicas mid command-stream under heavy write load (#15447)
Some checks failed
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Send pending data to replicas once ~1MB has built up, instead of waiting
for beforeSleep, so a busy loop keeps feeding replicas as it goes rather
than in one burst at the end.

Normally we write to replicas only once per event loop, at beforeSleep.
If one loop produces 10MB, the replica socket stays empty during the
loop, then at beforeSleep we try to send all 10MB at once. The socket
buffer only fits ~4MB, so 4MB is sent and 6MB waits, with a write
handler installed for the rest. But that handler runs about once per
loop and only sends another ~4MB, while every loop adds 10MB more, so
the buffer keeps growing. It eventually hits the replica
client-output-buffer-limit, the master drops the replica, and it has to
full sync. Sending every ~1MB during the loop feeds the replica as data
is produced, so the buffer stays small and the replica keeps up.

If io threads are enabled, this is not necessary. When on, replicas are
written from a separate thread in parallel, so they keep up on their own
even under load.

This scenario could be tested with a command like: 

```
memtier_benchmark --ratio 1:0 --data-size 500 --pipeline 50 --threads 8 --clients 50
```
2026-07-13 18:40:39 +03:00
Tom Gabsow
8897348030
Add RedisModuleEvent_ClusterTopologyChange server event (#15350)
## Summary

Adds a new module server event,
**`RedisModuleEvent_ClusterTopologyChange`**, so modules can be notified
when the OSS cluster topology changes instead of polling or relying on
operators to call an undocumented per-module refresh command on every
node.

### Motivation

Data-structure modules that fan out to other shards by slot range —
RedisTimeSeries (via LibMR: `TS.MGET`/`TS.MRANGE`/`TS.QUERYINDEX`),
RediSearch, etc. — keep their own cached view of the cluster. Today they
have no hook to learn that the topology changed, so:

- RedisTimeSeries requires operators to run the undocumented internal
`TIMESERIES.REFRESHCLUSTER` on **every primary** after cluster creation
and after **every** reshard.
- RediSearch periodically re-reads topology, which is wasteful and
laggy.

This is poor operator/developer experience and a recurring source of
broken keyless multi-shard commands after resharding/failover. See
community reports referenced in RED-148990 (e.g. RedisTimeSeries issues
#1348, #1683, #1215) and redis/redis#15286.

### What this does

Fires `RedisModuleEvent_ClusterTopologyChange` in cluster mode. The
event has a **single subevent (always `0`)**; the reasons that triggered
it are carried as a bitmask in the event data, so a module can react
selectively. The data pointer is a
`RedisModuleClusterTopologyChangeInfo` whose `change_flags` field is any
combination of:

| Flag | When |
|---|---|
| `REDISMODULE_CLUSTER_TOPOLOGY_CHANGE_FLAG_SLOT` | Slot ownership
changed (reshard, atomic slot migration, `ADD`/`DELSLOTS[RANGE]`). |
| `REDISMODULE_CLUSTER_TOPOLOGY_CHANGE_FLAG_ROLE` | A node changed its
primary/replica role (e.g. failover, replica re-pointing to a new
primary). |
| `REDISMODULE_CLUSTER_TOPOLOGY_CHANGE_FLAG_STATE` | The cluster OK/FAIL
state changed (this also covers the cluster first becoming ready at
startup). |
| `REDISMODULE_CLUSTER_TOPOLOGY_CHANGE_FLAG_NODE` | A node joined or
left the cluster, or an existing node's address (ip/port) changed. |

More than one bit may be set when several changes are coalesced into one
(debounced) notification. Beyond the change reasons, the module reads
whatever else it needs about the new topology via the cluster info APIs
(`CLUSTER SLOTS`, `RedisModule_GetClusterNodesList` /
`RedisModule_GetClusterNodeInfo` / `RedisModule_GetClusterSize`, …) — no
topology snapshot is passed in the event data.

### Design / debouncing

Every mutation site only records a pending reason: it calls
`clusterNotifyTopologyChange(flag)`, which ORs the bit into
`clusterState.topology_change_flags` and requests
`CLUSTER_TODO_FIRE_TOPOLOGY_CHANGE`. The notify calls are wired into:

- slot mutators — `clusterAddSlot` / `clusterDelSlot` (`SLOT`)
- role flips — `clusterSetNodeAsMaster` / `clusterSetMaster`, and the
gossip-driven demotion in `clusterProcessPacket` (`ROLE`)
- membership / address — `clusterAddNode` / `clusterDelNode` /
`nodeUpdateAddressIfNeeded` (`NODE`)
- the OK/FAIL transition in `clusterUpdateState` (`STATE`)

The event is actually fired from the **tail of `clusterBeforeSleep()`**
(via `clusterFireTopologyChangeIfNeeded`), which runs at most once per
event-loop iteration. So a reshuffle touching thousands of slots is
coalesced into a **single** event whose `change_flags` reflects every
reason that contributed. The cluster first becoming ready needs no
special case — it is delivered through the `STATE` (and, at startup,
`SLOT`/`NODE`) reasons on the first `FAIL -> OK` transition.

Wiring lives in:
- `src/redismodule.h` — event id 20, the
`RedisModuleClusterTopologyChangeInfo` struct, the `..._FLAG_*` bits,
the (single) subevent define.
- `src/module.c` — version array entry, `IsSubEventSupported`,
`moduleFireServerEvent` dispatch, doc comment.
- `src/cluster_legacy.{c,h}` — the `CLUSTER_TODO_FIRE_TOPOLOGY_CHANGE`
todo, the `clusterState.topology_change_flags` field, and the
`clusterNotifyTopologyChange` / `clusterFireTopologyChangeIfNeeded`
helpers.

### Testing

- New test module `tests/modules/cluster_topology.c` subscribes to the
event and counts, per reason bit, how many notifications carried it; the
counters are exposed via `cluster_topology.stats`.
- New `tests/unit/cluster/cluster-topology-change.tcl` asserts the event
fires (with the expected reason bit) on:
- **startup** — cluster becoming ready (`STATE`), and nodes being
discovered (`NODE`);
  - a **node address change** (`NODE`);
  - a **slot ownership change** via `CLUSTER DELSLOTSRANGE` (`SLOT`);
- an **atomic slot migration** — both losing and gaining owner observe
`SLOT`;
  - the cluster turning **FAIL** (`STATE`);
- a **failover** — every node, not just the promoted one, observes
`ROLE`;
  - a **node removal** (`NODE`).
- `redis-server` boots clean (the `moduleEventVersions` length assert
passes) and the existing `moduleapi/hooks` suite is unaffected.

### Consumer

The RedisTimeSeries side that subscribes to this event and drops the
manual `TIMESERIES.REFRESHCLUSTER` requirement is a companion change
(LibMR + RedisTimeSeries). Verified end-to-end against this branch: a
3-node OSS cluster auto-refreshes its topology on formation and on
reshard with no manual command.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: Ozan Tezcan <ozan.tezcan@redis.com>
Co-authored-by: Yuan Wang <wangyuancode@163.com>
Co-authored-by: Yuan Wang <yuan.wang@redis.com>
2026-07-13 18:06:20 +03:00
JongsuPark
3071bf6d7d
Fix unit mismatch that disabled the FAST expire cycle stale trigger (#15412)
Fixes #15410

`stat_expired_stale_perc` is a fraction in [0, 1]
(`total_expired/total_sampled`, multiplied by 100 only when printed in
INFO), but the FAST-cycle gate compared it directly against
`config_cycle_acceptable_stale`, which is a percentage in [1, 10]:

```c
if (!timelimit_exit &&
    server.stat_expired_stale_perc < config_cycle_acceptable_stale)
    return;
```

A value in [0, 1] is (almost) always below a threshold in [1, 10], so
the branch always returned early: FAST cycles could only run after the
previous SLOW cycle hit its time cap. The "percentage of estimated stale
keys is too high" trigger has been dead code since it was introduced in
6.0.

Scale the fraction to percent in the comparison, matching the per-DB
repeat condition in the same function, which already compares in percent
units (`data.expired * 100 / data.sampled >
config_cycle_acceptable_stale`).
2026-07-13 22:38:38 +08:00
himanshu-2l
117681f6a6
Add total command argument count to SLOWLOG GET reply (#15347)
## What

`SLOWLOG GET` entries gain a 7th element: the total argument count of
the
logged command (including the command name), captured before truncation.

## Why

When a command has more arguments than `slowlog-max-argc` (default 32),
the
logged argument list is truncated and the last shown element is replaced
with
`"... (N more arguments)"`. The exact count can only be recovered by
parsing
that human-readable string, which is brittle. Monitoring tools and
client
libraries want the exact count for complexity analysis of variadic O(N)
commands.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-13 22:25:08 +08:00
Vismay
7662d3ff5d
Fix server crash on VRANDMEMBER with LLONG_MIN count (#15392)
## The problem

`VRANDMEMBER key <count>` crashes the server when `count` is `LLONG_MIN`
(`-9223372036854775808`) and the set is non-empty. It is remotely
triggerable, so it is a DoS.

A negative count means "return `abs(count)` elements, allowing
duplicates". The code computes the magnitude as:

```c
long long abs_count = (count < 0) ? -count : count;
```

For `count == LLONG_MIN`, `-count` is undefined behavior and the true
magnitude (`2^63`) is not representable as a positive `long long`, so
`abs_count` stays negative. That negative value is passed to
`RedisModule_ReplyWithArray()`, and Redis aborts on `serverAssert(length
>= 0)` in `addReplyAggregateLen()`.

## The fix

Reject `count == LLONG_MIN` with an out-of-range error before computing
the magnitude. This matches core `SRANDMEMBER`, which validates its
count with `getRangeLongFromObjectOrReply(c, c->argv[2], -LONG_MAX,
LONG_MAX, ...)` and therefore already excludes `LONG_MIN`.

Other large in-range negative counts are unchanged (they still return
that many elements with duplicates, exactly as before and as core
`SRANDMEMBER` does); only the unrepresentable `LLONG_MIN` is rejected.

Fixes #15384
2026-07-13 17:22:42 +08:00
sacad
c5ac971438
Fix signed overflow in BITFIELD #offset parsing (#15433)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
`BITFIELD` and `BITFIELD_RO` support `#<offset>` syntax, where the
parsed offset is multiplied by the bitfield width before use. For very
large offsets, that multiplication could overflow `long long` before the
existing range checks ran.

This PR fixes #15389 by rejecting `#<offset>` values that would overflow
before applying the width multiplier, returning the existing `ERR bit
offset is not an integer or out of range` error instead.
2026-07-10 21:52:36 +08:00
Vitah Lin
c0c886a8cf
Skip unready fds in select event backend (#15446)
## Issue

After `select()` returns, the select backend loops over all registered
fds to find the ones that are ready. It already skips fds that are not
registered, but it did not skip registered fds that were not ready in
the current `select()` result.

Those fds have a computed mask of `AE_NONE`. They do not trigger any
read/write callback, but adding them to `eventLoop->fired` still makes
`aeProcessEvents()` count them as processed events.

## Change

Skip fds whose computed mask is `AE_NONE` before adding them to
`eventLoop->fired`.

This makes the select backend only report fds that are actually ready.
2026-07-10 16:46:11 +08:00
Filipe Oliveira (Redis)
81ac97cbed
redis-cli: add configurable latency percentiles to --latency modes (#15352)
## Summary

`redis-cli --latency` / `--latency-history` previously reported only
**min**, **max** and **avg**. This PR adds a `--latency-percentiles
<p1,p2,...>` option to also report arbitrary latency percentiles (e.g.
`50,99,99.9,99.99`) in **all four** output formats (standard, CSV, raw,
JSON).

```
$ redis-cli --latency --latency-percentiles 50,99,99.9
min: 0.031, max: 0.336, avg: 0.103 (812 samples), p50: 0.089, p99: 0.315, p99.9: 0.336

$ redis-cli --json --latency --latency-percentiles 50,99,99.9
{"min": 0.031, "max": 0.336, "avg": 0.103, "count": 812, "percentiles": {"50": 0.089, "99": 0.315, "99.9": 0.336}}
```

## HDR histogram

Percentiles are accumulated in an **HDR histogram** — the same library
already used by redis-cli's `--vset-recall` mode and by
`redis-benchmark` for latency percentiles. This keeps memory bounded and
recording O(1) (no growing buffer, no per-tick sort).

In `--latency-history`, the histogram is `hdr_reset()` at every window
boundary, in lockstep with the `min`/`max`/`avg` accumulators, so a
reported percentile is always the percentile **of the current window**.
The histogram is `hdr_init(1us, 3s, 3 sig figs)`; out-of-range samples
are clamped like in redis-benchmark.

## Sub-millisecond resolution

To make percentiles meaningful, the per-PING round-trip is now measured
with **microsecond** resolution (`ustime()`) instead of whole
milliseconds, and all latency figures are reported in **milliseconds
with 3-decimal precision**.

## ⚠️ Behavior change

This also changes the output of plain `--latency`/`--latency-history`
(without the new flag): `min`/`max`/`avg` switch from integer
milliseconds (`0`, `1`) to fractional milliseconds (`0.030`, `1.233`)
across standard/CSV/raw/JSON. The `count` field and the column/field
layout for the no-percentiles case are unchanged. Tooling that parsed
the old integer columns will now see decimals.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-10 14:59:58 +08:00
Mincho Paskalev
b48e4abe40
Add LMOVEM/BLMOVEM commands (#15405)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
2026-07-09 07:55:40 +03:00
Filipe Oliveira (Redis)
42bd63b3c1
Optimize wide HSET/HMSET on a fresh hash with a single batched listpack append (#15345)
## What

Building a freshly-created listpack-encoded hash with a multi-field
`HSET`/`HMSET` is **O(n²)**: `hsetCommand` calls `hashTypeSet` once per
field, and each call runs a full `lpFind` over the listpack — which
keeps growing as earlier fields of the *same* command are appended. For
a wide HSET this is ~n²/2 field comparisons per command.

This adds a fast path that builds such a hash with a **single
`lpBatchAppend`** — the same batched-append primitive `hashTypeSet`
already uses — making the build **O(n)**.

## Mechanism

When `HSET`/`HMSET` targets an **empty** `OBJ_ENCODING_LISTPACK` hash
with **≥5 fields**, `hashTypeBuildFreshListpack()` does a **single
pass** over the arguments: a transient `dict` (`sdsReplyDictType`,
borrowing the `argv` sds) maps each field to its **slot index** in a
`(field,value)` array. The first occurrence of a field appends a new
slot (preserving argument order); a repeated field reuses that slot and
overwrites its value (**last-wins**). The array is then written with one
`lpBatchAppend` — no per-field `lpFind`/`lpReplace`, no per-field
reallocs.

The result is **byte-identical to the per-field path**, so `HGETALL`
field order (insertion order) is unchanged — a CI test asserts this
directly (`HGETALL` order + `DEBUG DIGEST-VALUE` equal between the fast
path and a field-by-field build). The `(field,value)` array is on the
**stack** for ≤128 fields and **heap-allocated** above that, so there is
no fixed cap on how wide a build the fast path handles.

## Scope (deliberately narrow)

The fast path applies **only** to an empty listpack hash with **≥5
fields** and no field TTLs. The ≥5 gate comes from an A/B sweep of the
fast path vs the per-field loop on a fresh listpack hash: the transient
dedup dict only pays for itself from ~5 fields up (below that the
per-field loop is cheaper), so narrow HSET/HMSET keeps the existing
path. Every other case — `HSET` into a non-empty hash (updates/appends),
narrow (<5 field) `HSET`, hash-field-TTL (`OBJ_ENCODING_LISTPACK_EX`),
and hashtable encoding — takes the **unchanged** per-field path, so
existing-hash updates are byte-for-byte unchanged.

`hashTypeTryConversion` already runs **before** the dispatch, so a build
that would exceed `hash-max-listpack-value` /
`hash-max-listpack-entries` (default 512) / `lpSafeToAdd` is already a
hashtable and never reaches this path — no per-field length check and no
post-build conversion needed, and `numfields` here is bounded by
`hash-max-listpack-entries`.
2026-07-09 12:05:04 +08:00
Tal Bar Yakar
4dd58caa7c
RED-191626 , RED-191854 - modules modernization (#15253)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Summary

Redis core and the external modules (Search, JSON, Bloom, TimeSeries)
are currently built and released through separate flows, forcing anyone
assembling a full distribution to juggle multiple repos, refs, build
commands, and config files.

This PR collapses that into one Redis-owned workflow:

```sh
make modules-update    # clone/refresh modules from modules/modules.yaml
make bootstrap         # install per-module build/test deps
make build             # build Redis core + selected modules
make run               # start redis-server with modules loaded
make test all          # run module test suites
make tarball TAG=...   # build a reproducible source bundle
```

## What This Adds

**1. Manifest-driven modules** — `modules/modules.yaml` is the single
source of truth for bundled modules. Each entry pins `repo`/`ref` and
defines its build artifact and runtime `loadmodule` path. Refs resolve
dynamically (tag → branch → SHA, else fail).

Current pins: RedisBloom `v8.9.81`, RediSearch `v8.9.82`, RedisJSON
`v8.9.81`, RedisTimeSeries `v8.9.82`.

**2. Central manifest parser** — `scripts/lib/manifest.sh` is the only
YAML reader, usable both as a shell library and a CLI helper (via
`modules/manifest.mk`).

**3. Per-module stub Makefiles removed** — The `modules/redis*/Makefile`
stubs are deleted; their data moves to `modules.yaml` and shared
behavior to `modules/common.mk`.

## Make Workflow

The root `Makefile` is now a thin dispatch layer over scripts in
`scripts/`:

| Target | Script | Purpose |
|---|---|---|
| `make build [args]` | `scripts/build.sh` | Build Redis core plus
selected modules |
| `make bootstrap [args]` | `scripts/bootstrap.sh` | Install per-module
build/test prerequisites |
| `make setup [args]` | `scripts/setup.sh` | Run module update plus
bootstrap |
| `make run [args]` | `scripts/run.sh` | Start Redis with selected
modules loaded |
| `make test [args]` | `scripts/test.sh` | Run Redis or module tests |
| `make clean [args]` | `scripts/clean.sh` | Clean Redis and/or module
outputs |
| `make deploy [args]` | `scripts/deploy.sh` | Install Redis and module
artifacts |
| `make modules-update [args]` | `scripts/modules-update.sh` |
Clone/refresh module sources from the manifest |
| `make modules-shallow [args]` | `scripts/modules-shallow.sh` |
Re-clone selected modules with shallow history |
| `make sync-redis-conf [args]` | `scripts/sync-redis-conf.sh` |
Regenerate managed module config |
| `make tarball TAG=...` | `scripts/tarball.sh` | Create a reproducible
Redis + modules source tarball |

All accept module selectors:

```sh
make build all              # core + all modules
make build redisjson        # core + one module
make run none               # core only, no modules
make test redisearch TEST='some:test:name'
```

## Runtime Configuration

Removes the hand-maintained `redis-full.conf` (which could drift from
upstream). Config is now generated: `sync-redis-conf.sh` produces a
managed Modules block appended to verbatim `redis.conf`, with
`loadmodule` lines (active when built or `ASSUME_BUILT=1`) plus each
module's `module.conf`. The generated file is git-ignored.

## Docker & Release

- **`docker/Dockerfile.noble`** — Ubuntu 24.04 build env for core +
modules (amd64/arm64).
- **`scripts/tarball.sh`** — reproducible source bundle: archives core
at `TAG`, clones modules at pinned refs, strips `.git`/build artifacts,
generates config, and produces a deterministic gzip.
`utils/releasetools/01_create_tarball.sh` now delegates to it.
- **`post-release-automation.yml`** — builds the bundle on published
releases, uploads it as artifact + release asset, and reports SHA256.

## Docs

`modules/MODULES.md` (operator guide), `modules/README.md`, and
`README.md` cover the manifest schema, Make targets, tarball flow,
Docker env, and troubleshooting.

## Compatibility Notes

- `BUILD_WITH_MODULES=yes` is replaced by `make build
[redis|core|none|all|<module>...]`.
- `redis-full.conf` is now generated and git-ignored.
- Module clones live under `modules/<name>/src/` (git-ignored).
- `vector-sets` is omitted — it already lives in-tree.
- RediSearch builds with `LTO=1 REDISEARCH_GENERATE_HEADERS=0
INLINE_LSE_ATOMICS=0`.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-08 22:55:45 +08:00
hristostaykov-del
3869512c92
Fix ACL key-name leak in BCAST client-side caching invalidations (#15371)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Issue

BCAST client-side caching sent an invalidation for every key matching a
registered prefix, with no ACL check. A client subscribed with a broad
(or empty) prefix could therefore learn the *names* of keys it has no
permission to read — an information leak across ACL boundaries.

A naive filter is not enough on its own: pending BCAST invalidations
accumulate per-prefix and are only flushed in `beforeSleep`, filtered by
each subscriber's *current* ACL key permissions. If a client re-AUTHs,
or a user's ACL is rewritten in place (`ACL SETUSER` / `ACL LOAD`)
between accumulation and flush, `beforeSleep` would re-filter the
already-accumulated keys by the new (possibly stricter) permissions and
silently drop invalidations the client was entitled to under its old
identity.

## Change

Filter each invalidated key via `ACLUserCheckKeyPerm()` against the
subscriber's user (`CMD_KEY_ACCESS`) before sending. Replies are cached
per distinct user pointer so the ACL check stays O(U*K) rather than
O(C*K) (U = distinct users, C = clients, K = keys). NOLOOP clients keep
their per-client ad-hoc reply path.

Deliver pending invalidations under the old identity *before* it
changes:

- `clientSetUser()`: new helper that flushes the client's subscribed
  prefixes before `c->user` changes; wired into all auth paths and
  `clientSetDefaultAuth()`.
- `ACL SETUSER`: flush prefixes that have a subscriber of the modified
  user before overwriting it in place.
- `ACL LOAD`: whole-table flush before mutating users (`DefaultUser` is
  overwritten in place, and many users may change at once).
- `deauthenticateAndCloseClient()`: disable tracking before resetting
the
  user, so the dying client can't receive invalidations under the
  default user.

`trackingBroadcastInvalidationMessages()` now takes a user filter (NULL
flushes all prefixes); the per-prefix send logic is extracted into a
shared helper reused by the new flush paths.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-08 12:10:22 +08:00
Tom Gabsow
6bf6224c3d
Revert "Add FORK_CHILD_PRE/CANCELLED subevents for pre-fork module coordination (#15327)" (#15432)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Reverts #15327 ("Add FORK_CHILD_PRE/CANCELLED subevents for pre-fork
module coordination").

## Why revert

The `FORK_CHILD_PRE` / `FORK_CHILD_CANCELLED` subevents were added so a
multi-threaded module could quiesce its background threads before
`fork()` (a thread holding a lock — e.g. the allocator lock — at fork
time can deadlock the child). On reflection, this doesn't earn its place
as a permanent module API:

- **It doesn't actually solve the problem it targets.** As implemented,
the event only fires synchronously and returns "ready"; a module cannot
*refuse* or *delay* a fork, and there is no retry/handshake. A robust
solution would need a real async fork handshake (modules ack/negotiate,
server forks only on full ack) — a much larger design that isn't agreed
on. As a half-measure, the hook can give a false sense of fork-safety.
- **The deadlock it was chasing is sanitizer-specific, not a production
bug.** The observed hang is `fork()` child re-entering the
allocator/stdio lock (via logging — `serverLog` → `fprintf` → `malloc`)
while another thread held it at fork time. Under a normal allocator
(jemalloc/glibc) this is neutralized by the allocator's own
`pthread_atfork`; it only wedges under AddressSanitizer, whose allocator
has no fork-safe atfork. That's better handled in the sanitizer/test
environment than by a permanent public module API.
- **No consumer.** The intended module-side user is not going ahead with
the pre-fork drain approach, so this would ship as an unused API — and
an API, once public, has to be supported indefinitely.

## Scope

- Clean revert of the squash commit `750f6e7` (single parent); applies
with no conflicts on current `unstable`.
- Restores `redisFork()` to its prior form (removes the re-entrancy
guard, the `FORK_CHILD_PRE`/`CANCELLED` firing, and the test-only `debug
DEBUG fork-fail` hook).
- `REDISMODULE_SUBEVENT_FORK_CHILD_*` enum returns to `BORN=0`,
`DIED=1`, `_NEXT=2`.
- Verified: no remaining references to the removed symbols
(`FORK_CHILD_PRE`, `FORK_CHILD_CANCELLED`, `debug_fork_fail`) anywhere
in `src/` or `tests/`.
2026-07-07 12:38:31 +03:00
h.o.t. neglected
57a00274b3
Fix crash on CONFIG SET of TLS options in non-TLS builds (#15409)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
When Redis is built without `OpenSSL/TLS`, `tls-port`,
`tls-replication`, and `tls-cluster` remain runtime-modifiable via
`CONFIG SET`. Their config apply callbacks call
`connTypeConfigure(connectionTypeTls(), ...)`, but in a non-TLS build
`connectionTypeTls()` returns `NULL` and `connTypeConfigure()`
dereferences it unconditionally. This PR fixes #15404 by checking
returned pointer in `applyTlsCfg()` and `applyTLSPort()`, the two caller
functions of `connectionTypeTls()`.
2026-07-07 09:48:48 +08:00
Ozan Tezcan
8fa70c9877
Optimize lpSeek() by validating entries only when necessary (#15376)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
lpSeek() walked the listpack with lpNext()/lpPrev() which run
lpAssertValidEntry() on every hop. Apply the same optimization
lpFindCbInternal() already uses: step with lpSkip()/lpSkipPrev() and
fully validate an entry only when reading it could go past the
allocation; otherwise a cheap range check is enough. The returned entry
is always validated, and out-of-range landings still abort, so memory
safety is unchanged.

Added lpPrevN() and lpNextN() which is called by lpSeek() internally.
2026-07-06 17:06:25 +03:00
Ozan Tezcan
5037e19c61
Improve RESTORE REPLACE performance for new keys (#15397)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
For a new key, RESTORE REPLACE did three dict lookups (find, delete,
add). It now reuses the link from the first lookup for the insert and
skips the delete when no key is present, so it does a single lookup. The
impact is more visible while the dict is rehashing: three lookups would
trigger three incremental rehash steps instead of one. A similar
optimization could be applied to the non-REPLACE path, but RESTORE
REPLACE is the hotter path (e.g. atomic slot migration) and it would
require more changes.
2026-07-06 07:57:44 +03:00
Omer Shadmi
aa5409f1c2
MOD-16645: Update RediSearch module to v8.9.82 (#15425)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Updates modules/redisearch/Makefile to bundle RediSearch v8.9.82 on
Redis 8.10.

RediSearch tag v8.9.82 points at
RediSearch/RediSearch@7db8310a06, which
includes the MOD-16514 bootstrap hardening backport merged in
RediSearch#10340.

Validation:
- Verified the RediSearch tag exists and peels to
7db8310a0609c7bfaab911ee00cd2e9e65430496.
- Verified the tag is reachable via
https://github.com/redisearch/redisearch.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single Makefile version pin with no local logic changes; risk is
limited to upstream RediSearch behavior in the new tag.
> 
> **Overview**
> **Bumps the bundled RediSearch module** from **v8.9.81** to
**v8.9.82** by updating `MODULE_VERSION` in
`modules/redisearch/Makefile` for Redis 8.10 builds.
> 
> The new tag pulls in upstream **MOD-16514 bootstrap hardening**
(RediSearch#10340); no other build flags or paths change in this repo.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
2d1aef9326. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-07-05 13:30:20 +03:00
h.o.t. neglected
95f42acde0
Fix LSET out-of-range 64-bit index truncation (#15407)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
`lsetCommand()` parses the index as a `long`, but
`listTypeReplaceAtIndex()` took an `int`. On 64-bit builds this narrowed
out-of-range indexes to valid ones (e.g. `4294967296` -> `0`,
`4294967298` -> `2`), silently overwriting existing elements instead of
returning `ERR index out of range`. This PR fixes #15402 by changing
`listTypeReplaceAtIndex()` to take a `long` index.
2026-07-03 22:48:43 +08:00
Vitah Lin
5b22a09918
Reject duplicate CONFIG SET aliases (#15322)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
## Issue

`CONFIG SET` rejected duplicate options only when both arguments
resolved to the same `standardConfig` pointer. Alias configs are
registered as separate `standardConfig` copies, so a command could set
the same logical config twice by using both the primary name and its
alias.

```sh
CONFIG SET busy-reply-threshold 100 lua-time-limit 200
```

## Change

Add helpers to compare configs by logical identity. Duplicate detection
now keeps the existing pointer fast path and compares canonical primary
names when aliases are involved.

Add coverage for `busy-reply-threshold` and its `lua-time-limit` alias
in the duplicate `CONFIG SET` test.

## Test

Passed:

```sh
./runtest --single unit/introspection --only "CONFIG SET duplicate configs"
```

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-02 09:59:27 +08:00
Mehmet Tokgöz
2c9c04c1f8
Fix SET mutually exclusive NX/XX and IF* options (#15291)
Follow https://github.com/redis/redis/pull/14435

The conditions `NX`, `XX`, `IFEQ`, `IFNE`, `IFDEQ` and `IFDNE` belong to
a single mutually-exclusive group. The `IF*` parse branches enforced
this against the whole group (`cond_mut_excl`), but the `NX`/`XX`
branches only checked each other.

This made rejection depend on argument order. `SET k v NX IFEQ x` was
rejected (the `IFEQ` branch saw `NX` already set), but `SET k v IFEQ x
NX` was accepted (the NX branch ignored the `IFEQ` flag).

Affected command examples (all silently accepted before the fix):
  - SET k v IFEQ hello NX
  - SET k v IFEQ hello XX
  - SET k v IFNE world NX
  - SET k v IFDEQ 0123456789abcdef NX

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-02 09:21:35 +08:00
Vitah Lin
ec26d01fba
Fix overflowing memory unit conversions (#15390)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Issue

`memtoull()` can overflow when multiplying a parsed number by its memory
unit. For example, a very large `kb` value can wrap around to a small
number:

```sh
127.0.0.1:6379> CONFIG SET maxmemory 18014398509481985kb
OK
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "1024"
```

The same parser is also used by other memory configs, such as
`client-output-buffer-limit`.

## Change

Clamp overflowing unit conversions in `memtoull()` to `ULLONG_MAX`,
consistent with how oversized numeric input is already handled.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-07-01 11:53:06 +08:00
Yuan Wang
47b39347b5
Support atomic slot migration in redis-cli (#15338)
We introduced atomic slot migration in 8.4, but have not supported it in
redis-cli yet.
`redis-cli --cluster reshard` and `rebalance` now move slots with
server-side atomic slot migration (ASM) via CLUSTER MIGRATION IMPORT
when every cluster node reports Redis ≥ 8.4.0; otherwise behavior stays
on per-slot MIGRATE.

The default timeout of atomic slot migration is **60 minutes**
2026-07-01 11:12:57 +08:00
Omer Shadmi
e5fb6863db
MOD-16508: Update RediSearch module to v8.9.81 (#15370)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
Updates the bundled RediSearch module version used by Redis module
builds from `v8.9.80` to `v8.9.81`.

This picks up the tagged RediSearch 8.9.81 release for the 8.10 MS2
milestone.

Validation:
- Checked that `v8.9.81` exists in `RediSearch/RediSearch` and resolves
to `ccfdde4eb4444049f5965c4aa69a97d9a23a8cf2`.
- Checked the diff is limited to `modules/redisearch/Makefile`.
- Ran `git diff --check`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single-version Makefile change with no logic or security surface;
routine upstream module pickup.
> 
> **Overview**
> Bumps the **RediSearch** bundle pin from **`v8.9.80`** to
**`v8.9.81`** via `MODULE_VERSION` in `modules/redisearch/Makefile`, so
Redis module builds pull the **8.9.81** tagged upstream release for the
8.10 MS2 milestone.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
70e1f61178. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-06-30 13:46:53 +03:00
Vismay
0e618fafdf
Fix NULL deref in usUntilEarliestTimer when all time events are deleted (#15391)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Fixes #13992

## The problem

`usUntilEarliestTimer()` in `src/ae.c` scans the time event list for the
nearest non-deleted timer. It already guards the empty-list case (`if
(te == NULL) return -1;`), but when the list is non-empty yet every
event is marked `AE_DELETED_EVENT_ID`, the loop finishes with `earliest
== NULL` and the subsequent `earliest->when` access is a NULL pointer
dereference.

## The fix

Add `if (earliest == NULL) return -1;` after the scan, mirroring the
existing empty-list guard.

`-1` is the correct value here rather than `0`: the caller in
`aeProcessEvents()` treats a negative result as "no timer to wait for"
and leaves the poll timeout unset (blocking on I/O), whereas `0` would
make it poll with a zero timeout and busy-loop until the deleted events
are reclaimed.

Note: Harmless in practice — Redis always has the self-rearming
serverCron timer, so earliest is never NULL. This is defensive hardening
for the generic ae.c loop rather than a real crash fix.
2026-06-30 08:41:44 +08:00
Vitah Lin
5b5c32663b
Fix XREAD MAXSIZE test under force-resp3 (#15378)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Issue
The `XREAD MAXSIZE budget is per-command inside MULTI/EXEC` test compares the
entry count of a standalone `XREAD` reply against the same `XREAD` issued inside
a `MULTI`/`EXEC`. Under `--force-resp3` the test client's RESP3→RESP2
`transform_response_if_needed` dispatcher keys only on the command actually
sent, so it transforms the standalone `XREAD` reply but leaves the `EXEC` reply
and its nested sub-replies untouched. The standalone reply therefore arrives in
RESP2 array-of-pairs shape (`{{stream {entries...}}}`) while the EXEC-nested
`XREAD` reply stays in RESP3 map shape (`{stream {entries...}}`), causing
`xread_total_entries` to count `22` versus `2` and the assertion to fail only
under `--force-resp3`, even though both replies are functionally correct.

## Change
Normalize the nested `XREAD` reply from `EXEC` when running with
`--force-resp3`, so the test compares the same RESP2-style structure for
both the standalone and transactional `XREAD` replies.
2026-06-29 11:27:41 +03:00
Ozan Tezcan
f5e8137e1e
Cancel ASM tasks on RM_ResetDataset and RM_RdbLoad (#15377)
Both RM_ResetDataset and RM_RdbLoad flush the whole dataset but bypassed
the flush command path, so in-progress ASM tasks were left running. As a
result, after the task completed some deleted keys could re-appear,
while others could be dropped that should have been kept.

Move the cancellation into emptyData() so every full keyspace flush
(RM_ResetDataset, RM_RdbLoad, FLUSHALL, FLUSHDB) cancels the ASM task.
2026-06-29 10:29:19 +03:00
Christian Aistleitner
d28dd44c49
Switch from the archived redis-docs to the new doc repo (#15383)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
`redis-docs` got archived in March 2026, but we still referenced it in
some parts. We migrate them to the new `docs` repo.

This makes it easier for contributors to find where things are.
2026-06-29 14:01:08 +08:00
Ozan Tezcan
fe5d218646
Fix TSAN warning in hashTypeLength (#15382)
TSan complains because `server.allow_access_expired` is accessed from a
BIO thread: populateDeltaHistograms() reaches hashTypeLength() via
getObjectLength() with subtractExpiredFields==0.

Fix it by reading that variable only when necessary, i.e. only when
subtractExpiredFields is set.


```
*** [err]: Sanitizer error: ==================
WARNING: ThreadSanitizer: data race (pid=77380)
  Write of size 4 at 0x55e70ee77980 by main thread (mutexes: write M0):
    #0 moduleNotifyKeyUnlink /home/runner/work/redis/redis/src/module.c:13193 (redis-server+0x33246d) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #1 dbSetValue /home/runner/work/redis/redis/src/db.c:623 (redis-server+0x158aa4) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #2 setKeyByLink /home/runner/work/redis/redis/src/db.c:777 (redis-server+0x159406) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #3 setGenericCommand /home/runner/work/redis/redis/src/t_string.c:181 (redis-server+0x1a1ce2) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #4 setCommand /home/runner/work/redis/redis/src/t_string.c:443 (redis-server+0x1a22d8) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #5 call /home/runner/work/redis/redis/src/server.c:4035 (redis-server+0xe719f) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #6 processCommand /home/runner/work/redis/redis/src/server.c:4808 (redis-server+0xea119) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #7 processCommandAndResetClient /home/runner/work/redis/redis/src/networking.c:3497 (redis-server+0x13e076) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #8 processInputBuffer /home/runner/work/redis/redis/src/networking.c:3789 (redis-server+0x13e076)
    #9 processPendingCommandAndInputBuffer /home/runner/work/redis/redis/src/networking.c:3541 (redis-server+0x13fadf) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #10 processClientsFromIOThread /home/runner/work/redis/redis/src/iothread.c:619 (redis-server+0xc1cfa) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #11 handleClientsFromIOThread /home/runner/work/redis/redis/src/iothread.c:680 (redis-server+0xc1e59) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #12 aeProcessEvents /home/runner/work/redis/redis/src/ae.c:435 (redis-server+0xa861d) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #13 aeProcessEvents /home/runner/work/redis/redis/src/ae.c:360 (redis-server+0xa861d)
    #14 aeMain /home/runner/work/redis/redis/src/ae.c:495 (redis-server+0xa861d)
    #15 main /home/runner/work/redis/redis/src/server.c:8327 (redis-server+0x8b4c4) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)

  Previous read of size 4 at 0x55e70ee77980 by thread T3:
    #0 hashTypeLength /home/runner/work/redis/redis/src/t_hash.c:2299 (redis-server+0x1eb6b5) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #1 getObjectLength /home/runner/work/redis/redis/src/object.c:1009 (redis-server+0x151d69) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #2 populateDeltaHistograms /home/runner/work/redis/redis/src/lazyfree.c:33 (redis-server+0x2fd505) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    #3 kvsLazyfreeFree /home/runner/work/redis/redis/src/lazyfree.c:63 (redis-server+0x2fd505)
    #4 bioProcessBackgroundJobs /home/runner/work/redis/redis/src/bio.c:340 (redis-server+0x2b07cb) (BuildId: 56f4df0f306eff979d7ac5c7c665610cf8c07edb)
    ```
2026-06-29 08:56:14 +03:00
debing.sun
477aaf79c5
Add MIGRATE_MODULE_PROPAGATE_END event to propagate commands at the end of ASM (#15373)
## Problem

The existing
`REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_MODULE_PROPAGATE`
event only allows modules to inject commands **before** the slot
snapshot is sent. There is no mechanism to inject commands **after** the
incremental replication stream, i.e., at the very end of the migration
right before the `STREAM-EOF` is delivered to the destination.

This means modules have no way to perform post-migration bookkeeping on
the destination that must happen **after** all migrated data has been
applied but **before** the destination takes ownership of the slot.

## Solution

Add a new subevent
`REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_MODULE_PROPAGATE_END`
that fires in the main process during the handoff phase (`ASM_HANDOFF`),
after writes to the slot are paused and the migration stream is
complete, but before the `STREAM-EOF` is sent.

---------

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
2026-06-29 11:57:18 +08:00
Sergei Georgiev
08b465e4f4
Add MAXCOUNT and MAXSIZE options to XREAD and XREADGROUP (#15282)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
### Overview

This PR adds two new options, `MAXCOUNT` and `MAXSIZE`, to the `XREAD`
and `XREADGROUP` stream commands. They cap the **cumulative** reply
across all streams named in a single command: `MAXCOUNT` bounds the
total number of entries returned, and `MAXSIZE` bounds the total reply
size in bytes. Unlike the existing `COUNT` option — which limits entries
on a per-stream basis — these new options apply a global budget for the
whole command, giving clients a reliable way to bound a multi-stream
read.

### Problem Statement

`XREAD`/`XREADGROUP` accept multiple streams in one call, and `COUNT`
only limits entries **per stream**. A read over N streams with `COUNT C`
can therefore return up to `N * C` entries, and the total reply size is
effectively unbounded — a single command can return a very large payload
depending on how many streams match and how large each entry is.

For clients that need to bound the work and memory of a single round
trip (e.g. fan-in consumers reading many streams, or latency-sensitive
paths), there is no way today to say "give me at most K entries total"
or "at most B bytes total" across all streams in one command. Working
around this requires issuing per-stream reads and aggregating
client-side, costing extra round trips.

### Solution

Introduces two cumulative caps, applied across all streams in the
command:

- `XREAD [COUNT count] [MAXCOUNT maxcount] [MAXSIZE maxsize] [BLOCK
milliseconds] STREAMS key [key ...] id [id ...]`
- `XREADGROUP GROUP group consumer [COUNT count] [MAXCOUNT maxcount]
[MAXSIZE maxsize] [BLOCK milliseconds] [CLAIM min-idle-time] [NOACK]
STREAMS key [key ...] id [id ...]`

Semantics:

- **`MAXCOUNT`** caps the total number of entries returned across all
streams. It must be a positive integer, and must be `>= COUNT` when both
are given (since it is a cumulative cap over a per-stream limit). When
`COUNT` is omitted, `MAXCOUNT` alone bounds the total.
- **`MAXSIZE`** caps the total reply size in bytes (tracked via
`c->net_output_bytes_curr_cmd`). It must be a positive integer.
- **At least one entry is always returned.** The budget is never
enforced until at least one entry has been emitted across the whole
reply, so a single message larger than `MAXSIZE` is still returned
rather than yielding an empty reply.
- Both caps work for new (`>`) reads and for history/PEL reads in
`XREADGROUP`. For `MAXSIZE`, the limit is checked *before* delivering
the next PEL/new entry, so a skipped entry is neither sent nor added to
the consumer's PEL.

**Implementation details:**

`MAXCOUNT` is enforced in `xreadCommand()`: a running `total_entries`
counter shrinks each stream's effective `count` to the remaining budget
and stops scanning further streams once the cap is reached. `MAXSIZE` is
enforced inside the emit loops of `streamReplyWithRange()` and
`streamReplyWithRangeFromConsumerPEL()`, guarded by `(emitted_before +
arraylen) > 0` to preserve the "always serve one entry" exception. All
existing callers (`XRANGE`, `XCLAIM`, `XAUTOCLAIM`, `XINFO`, and the
consumer-PEL path) are migrated to the new struct form.

### Examples

Given three streams, each with 100 entries:

**`MAXCOUNT` caps the cumulative entry count (vs. per-stream `COUNT`):**

```
> XREAD COUNT 50 STREAMS s1 s2 s3 0 0 0
# returns 150 entries total (50 per stream)

> XREAD COUNT 50 MAXCOUNT 80 STREAMS s1 s2 s3 0 0 0
# returns 80 entries total — capped across all streams
```

**`MAXCOUNT` without `COUNT`** — bounds the total directly, filling from
the first stream onward:

```
> XREAD MAXCOUNT 7 STREAMS s1 s2 s3 0 0 0
# returns 7 entries total, all from s1
```

**`MAXSIZE` bounds the reply size in bytes:**

```
> XREAD MAXSIZE 200 STREAMS s1 s2 s3 0 0 0
# returns fewer entries than an unbounded read, but always >= 1
```

**`MAXSIZE` always returns a single oversized message:**

```
> XADD bigstream 1-1 f <5000-byte value>
> XREAD MAXSIZE 50 STREAMS bigstream 0
1) 1) "bigstream"
   2) 1) 1) "1-1"
         2) 1) "f"
            2) "<5000-byte value>"
# the single entry exceeds MAXSIZE but is still returned
```

**Both together — whichever bound triggers first wins:**

```
> XREAD MAXCOUNT 5 MAXSIZE 100000 STREAMS s1 s2 s3 0 0 0
# returns 5 entries (MAXCOUNT is the tighter bound)
```

`XREADGROUP` behaves identically for both new (`>`) reads and
history/PEL reads, and the caps are honored after a blocking client is
unblocked.

### Backward Compatibility

This is a fully additive change. `MAXCOUNT` and `MAXSIZE` are new
optional tokens recognized only within `XREAD`/`XREADGROUP`; when
omitted, behavior is exactly as before. No existing command, argument,
default, or reply format is modified, and no new command is introduced.
2026-06-26 11:00:07 +03:00
Tal Bar Yakar
8431843316
Update RedisTimeSeries module to v8.9.81 (#15372)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Summary
Bumps the pinned `MODULE_VERSION` for **RedisTimeSeries** from `v8.9.80`
to `v8.9.81`. RedisBloom and RedisJSON are unchanged.

| Module | From | To |
| --- | --- | --- |
| RedisTimeSeries | v8.9.80 | v8.9.81 |

> **Note:** the `v8.9.80...8.9.81` range is **diverged** — 10 new
commits on `8.9.81`, and `8.9.81` is 2 commits *behind* `v8.9.80`
(release-branch-only version/branch-cut commits, not forward-ported).
The ancestry diff contains 10 commits and **all 10 are genuinely new** —
`git cherry` marks every one `+`, so **0 are backports** already in
`v8.9.80`. One in-range note: **`TS.BGET` was renamed to `TS.READ`**
([#2081](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2081))
with a reworked blocking syntax, so the net-new command surface is
`TS.READ`, not `TS.BGET`.

## Module changes ([v8.9.80 →
v8.9.81](https://github.com/RedisTimeSeries/RedisTimeSeries/compare/v8.9.80...8.9.81))
* Use dedicated redis_ref field in ramp.yml for CI git ref
([#2059](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2059))
* pass VG=1 flag to the build as well
([#2060](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2060))
* MOD-16160 — wake parked TS.BGET clients on key deletion
([#2062](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2062))
* CI: bump GitHub Actions to Node24-compatible versions
([#2035](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2035))
* MOD-15749 — Short-form CLUSTERSET: RAMP capability + LibMR
error-return
([#2065](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2065))
* CI: speed up Event CI (lean PR gate) and shard slow nightly jobs
([#2068](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2068))
* MOD-6409 — TS.INFO memoryUsage (and MEMORY USAGE, via the mem_usage
callback)
([#2067](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2067))
* MOD-16266 — fix ci hang
([#2066](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2066))
* MOD-15565 — mrange aggregation execution in inner shards
([#2074](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2074))
* MOD-16505 — change TS.BGET command to TS.READ
([#2081](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2081))

**0 commits excluded — all 10 are genuinely new**
The `v8.9.80...8.9.81` ancestry diff contains exactly these 10 commits.
`git cherry -v v8.9.80 8.9.81` marks every one `+` (no patch-equivalent
change already in `v8.9.80`), so none are backports — unlike the `v8.8.0
→ v8.9.80` bump, there is nothing to exclude here.

## Test plan
* [ ]  CI passes for RedisTimeSeries at the new pinned version
* [ ]  `make all` builds RedisTimeSeries cleanly against `unstable`
* [ ]  Module tests run under `make test`
2026-06-25 21:35:34 +03:00
redis-release-automation-app[bot]
1885269582
Update RedisBloom to version 8.9.80 (#15368)
## Module Update

This PR updates **RedisBloom** to version **8.9.80**.

## Changes

- Updated `modules/redisbloom/Makefile` to set `MODULE_VERSION =
v8.9.80`

## Release Information

- **Module**: RedisBloom
- **Version**: 8.9.80
- **Tag Date**: 2026-06-24
- **Makefile Path**: `modules/redisbloom/Makefile`

## Verification

- [ ] Version updated correctly in Makefile
- [ ] Module builds successfully
- [ ] Tests pass
- [ ] No breaking changes

---

*This PR was automatically created by the Redis Module Release
Orchestrator.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single version constant change with no application logic; risk is
limited to upstream RedisBloom 8.9.80 behavior or compatibility in
deployments that use this module.
> 
> **Overview**
> Bumps the **RedisBloom** build pin from **v8.8.0** to **v8.9.80** by
updating `MODULE_VERSION` in `modules/redisbloom/Makefile`.
> 
> That variable drives the upstream clone in `modules/common.mk`, so
builds will fetch and compile the **8.9.80** tag from
`redisbloom/redisbloom` instead of the previous release.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0ebf5d7323. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-25 20:47:22 +03:00
redis-release-automation-app[bot]
604531d5f4
Update RedisJSON to version 8.9.80 (#15363)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
## Module Update

This PR updates **RedisJSON** to version **8.9.80**.

## Changes

- Updated `modules/redisjson/Makefile` to set `MODULE_VERSION = v8.9.80`

## Release Information

- **Module**: RedisJSON
- **Version**: 8.9.80
- **Tag Date**: 2026-06-24
- **Makefile Path**: `modules/redisjson/Makefile`

## Verification

- [ ] Version updated correctly in Makefile
- [ ] Module builds successfully
- [ ] Tests pass
- [ ] No breaking changes

---

*This PR was automatically created by the Redis Module Release
Orchestrator.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single Makefile version bump with no application or security logic
changes in this repository; risk is limited to upstream RedisJSON 8.9.80
behavior at build/runtime.
> 
> **Overview**
> Bumps the **RedisJSON** build pin from **v8.8.0** to **v8.9.80** by
updating `MODULE_VERSION` in `modules/redisjson/Makefile`.
> 
> That version is used by the shared module build (`common.mk`) to check
out the matching **redisjson/redisjson** tag when fetching sources and
producing `rejson.so`; no other files or build steps change in this PR.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9bc0964af0. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 14:54:14 +03:00
hristostaykov-del
e4b81f6df2
Implement sdiffcard command (#15278)
Add `SDIFFCARD` command — a cardinality-only variant of `SDIFF` that
returns the number of elements in the set difference without sending the
actual elements of the result.

### Motivation

Similar to how `SINTERCARD` and `SUNIONCARD` provide cardinality queries
for intersection and union, `SDIFFCARD` fills the gap for set
difference. This is useful when clients only need to know the size of
the difference rather than the full set of elements.

### Implementation

- Reuses `sunionDiffGenericCommand` with `cardinality_only=1` and
`SET_OP_DIFF`, consistent with how `SUNIONCARD` and `SINTERCARD` work.
- For diff algorithm 1, skips `setTypeAddAux` entirely and just
increments a counter, with early termination when `LIMIT` is reached.
- For diff algorithm 2, positive-`LIMIT` cardinality-only requests use a
dedicated 2b path
that materializes the subtrahends, scans the first set, and stops at
`LIMIT`; unlimited
(`LIMIT 0` or omitted) requests use the normal algorithm-2 cardinality
path and compute
the full difference cardinality.
- Adds a dedicated `sdiffcardGetKeys` key-extraction function following
the same pattern as `sintercardGetKeys` / `sunioncardGetKeys`.
- `LIMIT 0` means no limit. A positive `LIMIT` caps the returned
cardinality, allowing early exit.

### Syntax
SDIFFCARD numkeys key [key ...] [LIMIT limit]

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-06-24 13:24:27 +03:00
Vitah Lin
62d787a033
Fix module numeric config crash on missing config (#15357)
## Issue

`RedisModule_ConfigSetNumeric()` could crash when called with a
non-existent config name. `moduleSetNumericConfig()` did not check
whether `getMutableConfig()` returned `NULL` before dereferencing
`config->type`, unlike the existing bool config setter path.



## Change

Add the missing `NULL` check in `moduleSetNumericConfig()`, matching the
existing handling in `moduleSetBoolConfig()`.

Add TCL coverage for `configaccess.setnumeric nonexistent_config 1` to
verify the module API returns an error instead of crashing.

## Test
Passed:

```sh
make
./runtest --single unit/moduleapi/configaccess
```
2026-06-24 13:00:44 +03:00
hristostaykov-del
1d226e24e2
Fix NULL deref in ACL LOAD with internal (user-less) connections (#15356)
## Problem
`ACLLoadFromFile()` iterates `server.clients` to rebind users and drop
pub/sub clients whose channel ACLs changed. Before this fix the loop
only
skipped `CLIENT_MASTER` clients before dereferencing `c->user->name`.

Internal cluster connections (`AUTH "internal connection" <secret>`) set
`c->user = NULL` and `CLIENT_INTERNAL`, but **not** `CLIENT_MASTER`. So
if
an internal connection is open when `ACL LOAD` runs, the loop
dereferences
a NULL `c->user` and crashes the node.

## Fix
Guard on `c->user == NULL` instead of `CLIENT_MASTER`. This is a
superset:
master clients already carry `user = NULL`, so their handling is
unchanged,
while internal connections are now also skipped. Skipping internal
connections is also semantically correct — they run unrestricted (no ACL
user) and have no channel permissions to re-resolve.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2026-06-24 12:17:54 +03:00
Vitah Lin
4e59b8c00e
Use the path argument instead of server.configfile in rewriteConfig() (#15362)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
`rewriteConfig()` accepts a path and reads the existing config from that
path, but the writeback path used `server.configfile`.

Since current callers all pass `server.configfile`, this does not affect
the normal CONFIG REWRITE path, but using the passed-in path keeps the
function behavior consistent with its interface and comments.
2026-06-23 22:25:59 +08:00
Moti Cohen
d397d25179
rax: leaf-inlining for fixed-length-key rax trees (#15256)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Add a fixed-length-key rax mode that stores values directly in the leaf
parent's child slots, avoiding the final leaf raxNode allocation.

Introduce raxNewEx(metaSize, alloc_size, keyFixedLen) and keep the
existing representation unchanged for variable-length trees.

Update lookup, insert, remove, free, compression, and iterator paths to
handle virtual leaves, including value updates through
raxIteratorSetData().

Switch in-tree fixed-size rax users to the new mode. This reduces memory
for dense fixed-length trees, with throughput changes within benchmark
noise.
2026-06-23 12:04:52 +03:00
h.o.t. neglected
d2d3390d0c
Trim excess SDS allocation in inline command parsing (#15259)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
This PR is based on valkey-io/valkey#1213.

sdssplitargs() builds each argument one byte at a time via sdscatlen(),
which uses greedy doubling allocation. This leaves significant unused
space in each SDS string — for a 600-byte value, ~400 bytes are wasted.

Add sdsRemoveFreeSpace() after sdssplitargs() in processInlineBuffer()
to trim each argument before wrapping it in an robj.

Measured with 1M SET commands with 600-byte values piped via `redis-cli
--pipe` in inline format:
- Before: 1,021 MB vs 655 MB for RESP (55.9% overhead)
- After: 655 MB, matching RESP baseline

---------

Co-authored-by: muelstefamzn <muelstef@amazon.com>
2026-06-22 10:15:31 +08:00
debing.sun
7fb02029f5
Fix mem_clients_normal accounting drift when discarding a cached master (#15309)
Found by oranagra 

## The problem

`freeClient()` reverses the client's contribution to
`stat_clients_type_memory` (exposed as `mem_clients_normal`) only when
`c->conn` is set:

```c
if (c->conn)
    server.stat_clients_type_memory[c->last_memory_type] -= c->last_memory_usage;
```

A replica caches its master on disconnect to attempt a partial resync.
replicationCacheMaster() detaches the connection (unlinkClient) before
caching, so by the time the cached master is freed by
replicationDiscardCachedMaster(), `c->conn` is already NULL. The
subtraction is skipped, so the cached master's last accounted size is
never removed from mem_clients_normal.

## Fix

Include the cached master in the subtraction by also reversing the
contribution when the client being freed is `server.cached_master` (it
is still set at the point `replicationDiscardCachedMaster()` calls
`freeClient()`, and only cleared right after).
2026-06-22 09:49:57 +08:00
Joan Fontanals
44a6a9f5ef
[RED-197766] - allow modules to register PostNotificationJobsPerKey (#15242)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Reply-schemas linter / reply-schemas-linter (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
## What this PR does

Adds a new module API, `RedisModule_AddPostNotificationJobForKey`,
alongside
the existing `RedisModule_AddPostNotificationJob`. Both schedule work to
run
after a keyspace-notification (KSN) handler returns (so the handler
stays free
of write side-effects), but the new API binds each job to a specific key
and
differs from the existing one in three ways:

| | `AddPostNotificationJob` (existing) | `AddPostNotificationJobForKey`
(new) |
|---|---|---|
| **Callback** | `(ctx, pd)` | `(ctx, key, pd)` — receives the bound
key. |
| **Firing schedule** | Once, at the end of the outermost execution unit
(`firePostExecutionUnitJobs`). | At the tail of **every `call()`** — so
each MULTI/EXEC and EVAL/FCALL sub-command is observed in turn —
**plus** during AOF replay after each replayed command, **plus** the
end-of-execution-unit drain (for standalone commands). |
| **When allowed** | Refused while `server.loading` or on a read-only
replica. | Permitted during AOF replay and on replicas (see below).
Refused if called **outside a KSN handler**. |

Motivation: RED-197766. A module needs to react to each step of an
`HSET` + `HEXPIRE` sequence on the same hash inside a `MULTI/EXEC` block
—
the existing API can't deliver that because it only fires once at the
end of
EXEC.

## Design: one queue, distinguished by the bound key

Per-key jobs share the existing post-notification queue
(`modulePostExecUnitJobs`) rather than living in a separate one. A
queued job
(`RedisModulePostExecUnitJob`) carries an owned `key` reference and a
callback
union:

- **Regular job** (`RM_AddPostNotificationJob`): `key == NULL`,
`cb.callback`
is used. Fires once at the end of the outermost execution unit and may
write
  to the keyspace.
- **Per-key job** (`RM_AddPostNotificationJobForKey`): `key != NULL`,
  `cb.key_callback` is used and receives the bound key. Fires between
sub-commands and during AOF replay, and may **not** write to the
keyspace.

`key != NULL` is the sole discriminator — no separate type or flag.

### Firing sites

`firePerKeyJobsBetweenSubcommands()` drains only the per-key jobs (those
with
`key != NULL`), leaving regular jobs for the end-of-unit drain. It is
invoked
at three places, all off the universal `afterCommand` hot path so
standalone
commands that never register a per-key job pay nothing:

1. `execCommand()` (`multi.c`) — after each MULTI/EXEC sub-command's
`call()`,
   so per-key effects are observable between sibling sub-commands.
2. `scriptCall()` (`script.c`) — after each EVAL/FCALL sub-command's
`call()`.
3. `loadSingleAppendOnlyFile()` (`aof.c`) — after each replayed single
command,
plus a final `firePostExecutionUnitJobs()` at the `cleanup:` path so a
partially-applied command can't leave stragglers. EXEC's sub-commands go
through `call()` during replay and are covered by the per-`call()`
drain.

Per-key jobs registered by a standalone command (no MULTI/EXEC, no
script) are
drained by the existing end-of-execution-unit
`firePostExecutionUnitJobs()`,
which walks the single queue in submission order.

These drain sites are gated by an inline hint
`server.fire_keyed_jobs_between_subcommands`, armed when a per-key job
is
enqueued and cleared after a drain, so the no-jobs case is a single
well-predicted branch on a hot `server` field rather than a cross-TU
call.

## AOF replay & replica support

Unlike the regular API, `RM_AddPostNotificationJobForKey` does **not**
mirror
the `server.loading || repl_slave_ro` guard. The canonical consumer
attaches
**module key metadata** (`RM_SetKeyMeta` / `RM_GetKeyMeta`) to keys it
observes
via KSN. That metadata is neither stored in the AOF nor replicated; it
must be
rebuilt by re-running the same callback over the same KSN stream on
every
instance that applies the command — master, replica, and a process
replaying
its own AOF at startup. The per-key API is therefore permitted in all
those
contexts; the only registration guard is that it must be called from
inside a
KSN dispatch.

**RDB load is intentionally outside this pattern.** RDB load decodes
keys
directly without running commands, so no KSN fires and per-key callbacks
do not
run. A test (`perkey-rdb`) pins this boundary.

## Runtime contract enforcement

A per-key callback MUST only touch non-replicated, non-AOF-persisted
state
(canonically: module key metadata). Writing back into the keyspace would
amplify the AOF on replay and diverge a replica from its master. This is
now
**enforced at runtime**, not merely documented —
`server.firing_keyed_post_notif_jobs`
is set for the duration of each per-key callback, and while it is set:

- `RM_Call(...)` is refused: returns `NULL` with `errno == EINVAL`, or a
  `-ERR` call-reply when `CALL_REPLIES_AS_ERRORS` is requested.
- `RM_NotifyKeyspaceEvent` / `RM_NotifyKeyspaceEventWithSubkeys` are
refused
(return `REDISMODULE_ERR`) — a nested notification could enqueue further
  per-key jobs mid-drain.

Each refusal is logged once per drain (`LL_WARNING`, deduplicated via
`keyedPostNotifRMCallWarned` / `keyedPostNotifNotifyWarned`). The
canonical
`RM_SetKeyMeta` path is unaffected (metadata does not propagate).

## Implementation summary

- New per-key callback type `RedisModulePostNotifyJobPerKeyFunc`
  `(ctx, key, pd)`. The existing `RedisModulePostNotificationJobFunc` is
renamed to `RedisModulePostNotifyJobFunc` with a backward-compatible
typedef
  alias so existing module sources keep compiling.
- `RedisModulePostExecUnitJob` gains an owned `key` field and a callback
union.
- `firePerKeyJobsBetweenSubcommands()` drains per-key jobs;
`firePostExecutionUnitJobs()` drains the whole queue at end of unit. A
shared
`executePostExecUnitJob()` helper runs and frees a single job (and
decrefs
  the owned key for per-key jobs).
- New `server` fields:
- `firing_keyed_post_notif_jobs` — set while a per-key callback runs;
the
no-write guard backing the `RM_Call` / `RM_NotifyKeyspaceEvent`
refusals.
- `fire_keyed_jobs_between_subcommands` — fast-path hint gating the
explicit
    drains in `multi.c` / `script.c` / `aof.c`.
- `in_keyspace_notification` — counter, incremented around the dispatch
loop
    in `moduleNotifyKeyspaceEvent`. Defines the scope from which
`RM_AddPostNotificationJobForKey` may be called (nested notifications
nest
cleanly). Calling the API outside this scope returns `REDISMODULE_ERR`.
- `moduleUnregisterPostNotificationJobs()` drops any queued jobs
belonging to a
  module being unloaded (wired into `moduleUnregisterCleanup`).
- Because each KSN dispatch is single-key by construction, multi-key
commands
like `MSET`/`MGET` work transparently: they emit one
`notifyKeyspaceEvent`
  per key, so the handler can register one per-key job per affected key.

## Testing

A single test module, `pkmeta`
(`tests/modules/postnotifications_perkey_metadata.c`), uses the
canonical
pattern: a KSN handler enqueues a per-key job, and the job attaches
module key
metadata via `RM_SetKeyMeta`. Metadata is neither AOF-persisted nor
replicated,
so its presence after a reload / propagation is direct evidence the
callback
re-ran on that instance. Module-internal counters (fire count, fire log,
blocked-`RM_Call` count, blocked-notification count), kept out of the
keyspace
on purpose, carry the load-bearing assertions.

All tests live in one suite,
`tests/unit/moduleapi/postnotifications_perkey.tcl`:

| Group | Tests | What they pin |
|---|---|---|
| `perkey-aof` | single command rebuilds metadata via AOF reload;
MULTI/EXEC fires once per sub-command; HSET+HEXPIRE in MULTI/EXEC fires
twice (each run for both `debug loadaof` and full restart) | One drain
per replayed command/sub-command during AOF replay; the original
RED-197766 scenario end-to-end. |
| `perkey-rdb` | RDB-only restart does NOT rebuild metadata | Pins that
RDB load is outside the firing pattern. |
| `perkey-aof-replica` | AOF replay on a replica at startup rebuilds
metadata | The carve-out removing the `repl_slave_ro` guard. |
| `perkey-repl` | replica builds metadata from a propagated single
command; replica fires per sub-command for propagated MULTI/EXEC | Both
sides run the per-key job locally; no metadata crosses the replication
stream. |
| `perkey-misuse` | registration refused outside a KSN handler | The
`in_keyspace_notification` scope guard. |
| `perkey-order` | fires once per key in submission order across
MULTI/EXEC; fires between sub-commands inside MULTI/EXEC; fires between
commands inside a script (EVAL); multi-key command fires one job per key
| Ordering and per-sub-command granularity for MULTI/EXEC, scripts, and
multi-key commands. |
| `perkey-contract` | RM_Call refused; refusal repeats per firing and
never writes; RM_NotifyKeyspaceEvent refused; notification refusal
repeats per firing | Runtime enforcement of the no-write contract. |
| `perkey-expire` | lazy expire on read fires the per-key job; active
expire (cron) fires it without a read | Expire-path coverage. |
| `perkey-ptr-safety` | SMOVE first metadata attach must not UAF the
source set | Owned-key lifetime safety. |

The suite is registered in `runtest-moduleapi` and the module in
`tests/modules/Makefile`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches module notification timing, MULTI/EXEC, scripting, and AOF
replay—areas where ordering bugs could affect replication consistency,
though keyspace writes from callbacks are blocked.
> 
> **Overview**
> Adds **`RedisModule_AddPostNotificationJobForKey`**, a sibling to
`AddPostNotificationJob` that binds deferred work to a specific key from
a keyspace-notification handler. Per-key jobs share the existing
post-notification queue (`key != NULL`); regular jobs still drain only
at the end of the execution unit, while per-key jobs also run after each
sub-command in **MULTI/EXEC**, **EVAL/FCALL**, and **single-command AOF
replay** (gated by `fire_keyed_jobs_between_subcommands` so the common
path stays cheap).
> 
> Runtime contract: per-key callbacks may update local module state
(e.g. **`RM_SetKeyMeta`**) but **`RM_Call`** and
**`RM_NotifyKeyspaceEvent`** are refused while they run; registration
outside KSN dispatch fails via **`in_keyspace_notification`**. Unlike
the regular API, per-key registration is allowed during AOF replay and
on replicas so metadata can be rebuilt from the same KSN stream. Module
unload now drops queued jobs for that module; AOF load **`cleanup`**
flushes remaining regular jobs.
> 
> New **`pkmeta`** test module and **`postnotifications_perkey`** suite
cover ordering, AOF/replica rebuild, contract violations, and
expire/evict edge cases.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
022ad9d078. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Filipe Oliveira (Redis) <filipe@redis.com>
Co-authored-by: Moti Cohen <moticless@gmail.com>
2026-06-17 15:21:58 +02:00
Tom Gabsow
750f6e762c
Add FORK_CHILD_PRE/CANCELLED subevents for pre-fork module coordination (#15327)
Adds two `RedisModuleEvent_ForkChild` subevents so a multi-threaded
module can bring its background threads to a safe point before `fork()`:

- `REDISMODULE_SUBEVENT_FORK_CHILD_PRE` — fired synchronously on the
main thread just before `fork()`. The handler runs synchronously, so
returning from it acknowledges the module is ready to fork.
- `REDISMODULE_SUBEVENT_FORK_CHILD_CANCELLED` — fired if a fork preceded
by `_PRE` did not happen (`fork()` failed), so the module can resume. On
success the module resumes on the existing `FORK_CHILD_BORN`.

`redisFork()` fires `_PRE` before `fork()` and `_CANCELLED` on `fork()`
failure.

## Why
Between `fork()` and `exec()` only async-signal-safe functions are safe
to call. A multi-threaded module (search, timeseries/LibMR, gears…)
whose background thread holds a lock — e.g. the allocator (malloc arena)
lock — at the instant of `fork()` leaves the child holding that lock
with no owning thread, so the child **deadlocks** the first time it
takes it (e.g. during the RDB save it forked for). `_PRE` lets the
module quiesce its threads to a safe, lock-free point first.

Existing `FORK_CHILD` subscribers are unaffected — they ignore the new
subevents (default case); no existing subevent value or the event id
changes.


Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
2026-06-17 13:36:57 +03:00
Guimu
4885bca456
Fix IFDEQ/IFDNE digest argument type in SET and DELEX command json file (#15305)
Corrects command introspection metadata for SET and DELEX so IFDEQ / IFDNE digest operands are documented as string, not integer.
2026-06-17 18:05:29 +08:00
Omer Shadmi
1f5c93ef36
MOD-16291: Update RediSearch module to v8.9.80 (#15343)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Reply-schemas linter / reply-schemas-linter (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Jira: https://redislabs.atlassian.net/browse/MOD-16291

Updates the bundled RediSearch module version used by Redis module
builds from `v8.8.0` to `v8.9.80`.

This picks up the tagged RediSearch 8.9.80 release.

Validation:
- Checked that `v8.9.80` exists in `RediSearch/RediSearch` and resolves
to `280c3ce1bd65fd7306cae75a1d54bce3a05518e4`.
- Checked the diff is limited to `modules/redisearch/Makefile`.
- Ran `git diff --check`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single Makefile version pin with no application logic changes; main
risk is behavioral differences in the upstream RediSearch 8.9.80 release
at runtime.
> 
> **Overview**
> **RediSearch module version bump** from `v8.8.0` to `v8.9.80` via
`MODULE_VERSION` in `modules/redisearch/Makefile`, so Redis module
builds fetch and compile the tagged 8.9.80 release instead of 8.8.0.
> 
> No other build flags or paths change in this PR.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7bd487572e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-06-16 14:05:12 +03:00
YaacovHazan
884d6c6895
Add SCRIPT_RUNNER command flag (#15337)
Add a new CMD_SCRIPT_RUNNER flag (bit 30) to identify commands that
execute scripts or functions. The flag is applied to EVAL, EVALSHA,
EVAL_RO, EVALSHA_RO, FCALL, and FCALL_RO.

These marker help clients better understand command semantics,
particularly in the context of Client Side Caching: clients can use
the SCRIPT_RUNNER flag to decide which commands
should not have their results cached, since script execution and
consumer group reads are inherently stateful and non-repeatable.
2026-06-16 14:03:18 +03:00
rantidhar
dcc8b118a8
RED-200844: avoid recomputing allocator fragmentation twice per cron tick (#15330)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Motivation

`getAllocatorFragmentation()` was called twice per `serverCron` tick:
once by `cronUpdateMemoryStats()` for `INFO MEMORY`, then again by
`computeDefragCycles()` for the threshold decision. Each call forces a
jemalloc epoch refresh (cross-thread sync via IPI) plus a per-arena
small-bin scan. The second call's result was almost always discarded
by the threshold check.

## Change

`struct defragFragCache` on `redisServer` (`src/server.h`) caches the
Lua-arena-subtracted `(frag_pct, frag_bytes)` plus `server.cronloops`
at the moment of measurement.

- `defragFragCachePut(frag_bytes, allocated)` — called from
  `cronUpdateMemoryStats()` (`src/server.c`) after the existing arena
  walk; mirrors `getAllocatorFragmentation()`'s Lua subtraction.
- `defragFragCacheTake(&pct, &bytes)` — called at the top of
  `computeDefragCycles()` (`src/defrag.c`); returns hit only when the
  recorded `cronloops` matches `server.cronloops` (same tick). Miss →
  falls through to a fresh `getAllocatorFragmentation()` call.

Freshness gate is `server.cronloops`: it advances at the top of
`serverCron()` and `whileBlockedCron()`, so any value published in a
previous tick is automatically stale on the next `Take`. No explicit
invalidate call needed. `initServerConfig()` seeds `cronloops = -1`
so the first `Take` is a clean miss.

`HAVE_DEFRAG=0` builds get no-op `Put`/`Take` stubs so the
unconditional call site in `cronUpdateMemoryStats()` always links.

## Observability

`DEBUG DEFRAG-FRAG-CACHE-STATS` exposes `defrag_frag_cache_hits`
(testing/diagnostic surface only; not in `INFO`).
2026-06-16 08:21:38 +08:00
Tom Gabsow
a933493c29
Update RedisTimeSeries module to v8.9.80 (#15336)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
## Summary

Bumps the pinned `MODULE_VERSION` for **RedisTimeSeries** from `v8.8.0`
to `v8.9.80`. RedisBloom and RedisJSON are unchanged.

| Module          | From    | To       |
|-----------------|---------|----------|
| RedisTimeSeries | v8.8.0  | v8.9.80  |

> **Note:** `v8.9.80` is the **8.10 LTS branch cut** (targets Redis
8.10) and has diverged from `v8.8.0`. The `v8.8.0...v8.9.80` ancestry
diff contains 37 commits, but **7 are backports already shipped in
`v8.8.0`** (verified with `git cherry`, see below); the **30 genuinely
new** changes are listed here.

## Module changes ([v8.8.0 →
v8.9.80](https://github.com/RedisTimeSeries/RedisTimeSeries/compare/v8.8.0...v8.9.80))

- MOD-14124 — m commands acl
([#1913](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1913))
- MOD-14250 — missing artifacts logs fix
([#1932](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1932))
- MOD-14289 — Increase LibMR execution timeout for Valgrind builds
([#1933](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1933))
- MOD-12726 — unique snapshot name + new output params for nighly event
([#1937](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1937))
- MOD-14674 — oss refresh list
([#1935](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1935))
- nightly build, upload snapshot artifact
([#1954](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1954))
- MOD-14902 — existence check to prevent Timeseries crash when Redis is
not aligned with new redis-core changes
([#1940](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1940))
- MOD-14850 — Bump libmr
([#1962](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1962))
- MOD-14715 — add apt retries/timeouts to Debian/Ubuntu Dockerfiles
([#1965](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1965))
- MOD-15262 — Align TimeSeries with the RedisModule_GetUserUserName API
changes
([#1975](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1975))
- RED-180951 RED-180027 — fixing bugs and improving the code
([#2004](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2004))
- MOD-14239 — Ignore known init-rdbchannel migration timeout under
sanitizer
([#1979](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1979))
- MOD-15105 — make one generic getUser function
([#1981](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1981))
- MOD-15741 MOD-14091 — add dont-cache command tip to non-cacheable read
commands
([#2031](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2031))
- MOD-15731 — modules modernization alignment
([#2027](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2027))
- MOD-15728 — Tal.ba/feat/blocking get
([#2028](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2028))
- MOD-8187 — fix the aggregation bugs (fix reverse iterator)
([#2036](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2036))
- MOD-15749 — Link to new version of LibMR with short-form CLUSTERSET
and add tests
([#2034](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2034))
- Fix REDISMODULE_MAIN typo + drop common.h forward-decl workarounds
([#2044](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2044))
- CI: switch flow-linux arm64 runner to standard ubuntu-24.04-arm
([#2048](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2048))
- MOD-15999 — Centralize Redis ref into a single source-of-truth file
for CI
([#2045](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2045))
- CI: trigger OSS cluster (multi-shard) benchmarks on demand
([#2050](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2050))
- MOD-15899 — oom try calloc
([#2051](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2051))
- MOD-8187 — fix non twa aggregation
([#2053](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2053))
- MOD-16224 — Fix filter-blind neighbor lookups in EMPTY aggregation
([#2056](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2056))
- MOD-15893 — Nrange new command
([#2052](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2052))
- MOD-16160 — update blocking get syntax
([#2054](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2054))
- MOD-16160 — Fix client-side cache tips for TS.BGET and
TS.NRANGE/TS.NREVRANGE
([#2057](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2057))
- Cut 8.10 LTS branch: bump version to 8.9.80, target Redis 8.10
([27484f7](https://github.com/RedisTimeSeries/RedisTimeSeries/commit/27484f7))
- Fix event-nightly CI on 8.10: read Redis ref from dedicated redis_ref
field
([#2058](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2058))

<details>
<summary>7 commits excluded — already shipped in v8.8.0 as
backports</summary>

These show up in the `v8.8.0...v8.9.80` ancestry diff with different
SHAs, but `git cherry` confirms their patch content is already in
`v8.8.0`: they were backported to the 8.8 release branch (the "in 8.8
via" PR below), so they are **not** new in this bump.

- MOD-9320 — Multiple aggregators single command
([#1916](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1916))
→ in 8.8 via
[#1921](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1921)
- MOD-9320 — rename AGGREGATION token in m commands
([#1926](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1926))
→ in 8.8 via
[#1927](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1927)
- MOD-13142 — Allow changing number of threads while pool hasn't started
([#1928](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1928))
→ in 8.8 via
[#1934](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1934)
- MOD-14439 — Detect cluster topology changes during a multi-shard
command
([#1930](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1930))
→ in 8.8 under the same PR
- ci: skip Event CI when PR only changes src/version.h
([#1984](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1984))
→ in 8.8 via
[#1986](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1986)
- MOD-15020 — remove bionic os support
([#1983](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1983))
→ in 8.8 via
[#1996](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/1996)
- MOD-14420 — fix count reducers return wrong NaN
([#2013](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2013))
→ in 8.8 via
[#2016](https://github.com/RedisTimeSeries/RedisTimeSeries/pull/2016)

</details>

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

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:57:22 +03:00