Skip to content

[backport] Backport sweep for 9.0 - #4226

Open
valkeyrie-ops[bot] wants to merge 9 commits into
9.0from
agent/backport/sweep/9.0
Open

[backport] Backport sweep for 9.0#4226
valkeyrie-ops[bot] wants to merge 9 commits into
9.0from
agent/backport/sweep/9.0

Conversation

@valkeyrie-ops

@valkeyrie-ops valkeyrie-ops Bot commented Jul 20, 2026

Copy link
Copy Markdown

Backport sweep for 9.0

Automated cherry-picks from PRs marked "To be backported".

Applied

Source PR Title Detail
#4104 Avoid offloading writes to IO threads for the slot migration export job while snapshotting. conflicts resolved by Claude Code
#4229 Validate slot import ranges when loading from RDB
#4210 Check and reject invalid slot import job names
#4301 Fix out-of-bounds read in getNodeByQuery's cross-DB COPY check conflicts resolved by Claude Code
#4211 Prevent double-free of the module timer when the callback stops it
#4300 Fix FIELDS token validation for HPERSIST and HTTL family commands
#4253 Fix RESP3 push frame torn apart on self-publish with copy avoidance

AI resolution details are posted as comments on this PR when available.

Needs attention

These candidates could not be applied automatically and need a maintainer to follow up.

1 candidate(s)
Source PR Title Outcome Reason
#4302 Skip bad-arity commands when prefetching queued command keys skipped-conflict test adaptation not applied: invalid generated test path(s): tests/unit/networking.tcl

Generated by valkey-ci-agent using Claude Code.

@valkeyrie-ops valkeyrie-ops Bot added ai-resolved-conflicts AI is used to resolve conflicts in automated backport PRs. backport Backport PR opened by valkey-ci-agent labels Jul 20, 2026
@valkeyrie-ops

valkeyrie-ops Bot commented Jul 20, 2026

Copy link
Copy Markdown
Author

AI conflict resolution: source PR #4104

Avoid offloading writes to IO threads for the slot migration export job while snapshotting.

2 conflicted files

Claude Summary

All conflicts are resolved. Here's a summary of what I did:

Source PR intent: Prevent IO-thread write offloading for slot migration export clients while snapshotting (to avoid query buffer desync during pipelining), plus a defensive early-return in killSlotMigrationChild and IO-state checks before snapshotting.

Resolutions:

  1. src/io_threads.c — The conflict was in the include block at the top of the file. Upstream's unstable branch has #include "queues.h" and #include <sys/resource.h> as pre-existing context, but neither exists on the 9.0 branch (I confirmed src/queues.h is absent). The PR itself only added #include "cluster_migrateslots.h", so I kept just that one include. The functional change — the guard if (c->slot_migration_job && !clusterSlotMigrationShouldInstallWriteHandler(c)) return C_ERR; in trySendWriteToIOThreads — auto-merged cleanly, and I verified both the slot_migration_job client field (server.h:1298) and clusterSlotMigrationShouldInstallWriteHandler (cluster_migrateslots.h:20) exist on 9.0.

  2. tests/unit/cluster/cluster-migrateslots.tcl — The conflict was at the end of the file, where the cherry-pick tried to append two start_cluster test blocks (remaining_repl_size and slot-migration-max-failover-repl-bytes -1) that exist on unstable but not on 9.0. The PR only modified their tag lines (adding network); it didn't introduce those tests. I dropped them rather than importing tests outside the PR's scope. The network tag additions to the 5 start_cluster blocks that do exist on 9.0 were auto-merged and are intact.

  3. src/cluster_migrateslots.c (auto-merged, staged) — Verified it matches the upstream diff: the exit-pipe early-return in killSlotMigrationChild and the io_write_state/io_read_state idle checks before snapshotting in proceedWithSlotMigration. server.slot_migration_child_exit_pipe, CLIENT_IDLE, and both IO-state fields all exist on 9.0. No adaptation was needed
    ... summary truncated.

AI-resolved conflicted files

  • src/io_threads.cview diff
  • tests/unit/cluster/cluster-migrateslots.tclview diff

Full backport commit diff: commit efe4f502181a.

Please review these AI resolutions for correctness before merging.

satheeshaGowda and others added 4 commits July 22, 2026 09:48
…ob while snapshotting. (#4104)

**Problem Description**

Atomic slot migration is failing when io-threads enabled and pipeline
requests are ongoing

```== CRITICAL == This slot-import-target is sending an error to its slot-import-source: 'Protocol error: invalid CRLF in request' after processing the command 'set'```

**Proposed Fix**
 Disable I/O threads offloading for slot migration jobs when snapshotting to prevent query buffer desynchronization during pipelining

Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
When loading slot migration import jobs from the RDB, the start and
end slots of each range were not validated. A corrupted or truncated
RDB could produce out-of-range slot values or a reversed range
(start > end), which would then be used to build migration jobs and
slot ranges.

Reject any slot import range whose start or end slot is greater than
or equal to CLUSTER_SLOTS, or whose start slot is greater than the
end slot, logging a warning and aborting the load.

Fixes #4222.

Signed-off-by: Binbin <binloveplay1314@qq.com>
## Summary
- `clusterRDBLoadSlotImport()` accepted a variable-length `job_name`
from RDB, but `createSlotImportJob()` always `memcpy`'s
`CLUSTER_NAMELEN` (40) bytes. A crafted RDB with a shorter name caused a
heap out-of-bounds read during startup.
- Reject slot-import records whose `job_name` length is not exactly
`CLUSTER_NAMELEN`, matching the existing command-path check.
- Add an integration test that crafts a short `job_name` RDB and asserts
the server fails closed.

Fixes #4207

## Test plan
- [x] `./runtest --single tests/integration/rdb-slot-import.tcl`

---------

Signed-off-by: quanyeyang <quanyemostima@gmail.com>
@valkeyrie-ops
valkeyrie-ops Bot force-pushed the agent/backport/sweep/9.0 branch from d41fa59 to c1744a1 Compare July 22, 2026 09:52
The cross-DB `COPY` guard in `getNodeByQuery()` (added in #1671) checks
`margc >= 4` but then dereferences `margv[4]`:

```c
if (mcmd->proc == copyCommand &&
    margc >= 4 && !strcasecmp(objectGetVal(margv[3]), "db")) {
    long long value;
    if (getLongLongFromObject(margv[4], &value) != C_OK || ...
```

`COPY` has arity `-3`, so `COPY k1 k2 DB` (`argc == 4`) passes the arity
check in `processCommand()` and reaches `getNodeByQuery()` — which runs
*before* `copyCommand()`'s own parser would reject it as a syntax error.
The read is one past the end of the argument vector, and the resulting
garbage `robj *` is dereferenced.

Reproduced on a two-node cluster (`cluster_state:ok`) with one slot
`MIGRATING`:

```
control:  COPY {b}k1 {b}k2 DB 0   -> TRYAGAIN ...               (server survives)
trigger:  COPY {b}k1 {b}k2 DB     -> Server closed the connection
```

```
=== VALKEY BUG REPORT START ===
crashed by signal: 11, si_code: 2
0  valkey-server  getLongLongFromObject + 28
1  valkey-server  getNodeByQuery + 1312
2  valkey-server  processCommand + 1128
```

Reaching the check requires the slot to be in migrating or importing
state (`cluster.c` short-circuits otherwise), i.e. during a resharding.

`COPY`'s `DB` and `REPLACE` tokens are optional and order-independent,
and `DB` may appear more than once (`copyCommand()` keeps the last value
— see the note above `copyDbIdArgs()`), but the guard only inspected
`argv[3]`. So the check was silently skipped whenever `REPLACE` came
first.

On the same cluster, both keys present, slot migrating:

```
COPY {b}k1 {b}k2 DB 1 REPLACE  -> TRYAGAIN ...   (correctly blocked)
COPY {b}k1 {b}k2 REPLACE DB 1  -> 1              (cross-DB copy went through)
```

The second form performed exactly the cross-DB copy during slot
migration that this guard exists to prevent.

Scan every `DB` clause the way `copyCommand()` does, instead of
hardcoding `argv[3]`/`argv[4]`. A `DB` token with no value is left alone
so `copyCommand()` reports the syntax error.

Behavior with the fix, same setup:

| command | before | after |
|---|---|---|
| `COPY k1 k2 DB` | **SIGSEGV** | `ERR syntax error` |
| `COPY k1 k2 REPLACE DB` | `ERR syntax error` | `ERR syntax error` |
| `COPY k1 k2 REPLACE DB 1` | `1` (cross-DB copy) | `TRYAGAIN ...` |
| `COPY k1 k2 DB 1 REPLACE` | `TRYAGAIN ...` | `TRYAGAIN ...` |
| `COPY k1 k2 DB 0 DB 1` | `1` (cross-DB copy) | `TRYAGAIN ...` |
| `COPY k1 k2 DB 0 REPLACE` | `1` | `1` |
| `COPY k1 k2 REPLACE DB 0` | `1` | `1` |

Extended the existing "Cross-DB COPY command should not be allow during
slot migration" test. Verified the new assertions fail on unstable
without the code change and pass with it; `unit/cluster/slot-migration`,
`unit/cluster/multidb` and `unit/keyspace` all pass.

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

valkeyrie-ops Bot commented Aug 1, 2026

Copy link
Copy Markdown
Author

AI conflict resolution: source PR #4301

Fix out-of-bounds read in getNodeByQuery's cross-DB COPY check

2 conflicted files

Claude Summary

Both conflicts are resolved and no conflict markers remain. Here's a summary of what I did:

src/cluster.c (conflict inside getNodeByQuery(), in the per-key loop's cross-DB COPY guard): I took the PR's version — replacing the hardcoded margv[3]/margv[4] check with a loop that scans every DB clause and bounds-checks with k + 1 &lt; margc — with one branch adaptation: the PR used objectGetVal(margv[k]), but that accessor doesn't exist on the 9.0 branch (verified with git grep against HEAD — zero matches). The 9.0 code, including the old HEAD side of this conflict, accesses the object payload as margv[k]-&gt;ptr, so I used that. A gcc -fsyntax-only check on the resolved file passes.

tests/unit/cluster/slot-migration.tcl (conflict inside the "Cross-DB COPY command should not be allow during slot migration" test): The conflict was purely between the old trailing-whitespace comment line on HEAD and the PR's new assertion block. I took the PR side, which adds the syntax-error assertions for a valueless DB token, the order-independence TRYAGAIN assertions, and keeps the existing comment. The assert_error/assert_equal helpers both exist on the target branch, and tclsh's info complete confirms the file parses. The trailing COPY ... REPLACE DB 0 line had already auto-merged cleanly below the conflict region.

The resolved diff matches the upstream PR's size (cluster.c: +15/-8 lines net of context, test file: +12/-1), with the only deviation being the objectGetVal-&gt;ptr adaptation. I did not stage or commit anything, per the constraints.

AI-resolved conflicted files

Full backport commit diff: commit 8409457f5f44.

Please review these AI resolutions for correctness before merging.

quanyeyang and others added 4 commits August 3, 2026 10:47
…4211)

Fix a double free in `moduleTimerHandler()` when a module timer callback
calls`ValkeyModule_StopTimer()` on the currently firing timer (`VM_StopTimer`
frees the object, then the dispatcher freed it again).

After the callback returns, only remove/free the timer if the original timer
ID still maps to the same live object in `Timers`.

Fixes #4200.
)

#4049 fixed the FIELDS token validation for `HGETDEL`. The same parsing
issue also exists in `HPERSIST` and `HTTL/HPTTL/HEXPIRETIME/HPEXPIRETIME`:

The `FIELDS` keyword was never validated, so a malformed command like
`HPERSIST key a 1 c` would silently treat `a` as a no-op and parse the
remaining arguments instead of returning a syntax error.

This PR aligns these commands with the HGETDEL fix: reject the command
with a syntax error when the `FIELDS` keyword is missing, and adds tests
covering the validation for all affected commands.

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
…4253)

Fixes #4231.

### Problem
On a RESP3 connection that both subscribes and publishes to the same
channel, a large pubsub payload can be emitted as a torn frame: the bulk
payload first, then the PUBLISH integer reply, then an incomplete push
header. Clients that track command replies (e.g. StackExchange.Redis)
treat the bare bulk string as the PUBLISH reply and desync.

### Cause
Self-publish defers push bytes into `pending_push_messages` via
`_addReplyToBufferOrList`, but reply copy avoidance writes `bulkStrRef`
directly into `c->buf` / `c->reply`, bypassing that deferral. The
default
copy-avoid threshold is 16384 bytes, matching the reported size.

### Fix
Disable copy avoidance while `c->flag.pushing` is set in
`isCopyAvoidPreferred()`, so push construction always uses the normal
reply path that respects `pending_push_messages`.

Signed-off-by: quanyeyang <quanyemostima@gmail.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-resolved-conflicts AI is used to resolve conflicts in automated backport PRs. backport Backport PR opened by valkey-ci-agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants