Skip to content

Clean up lookahead-related code#25

Open
tomerqodo wants to merge 7 commits into
coderabbit_full_base_clean_up_lookahead-related_code_pr3from
coderabbit_full_head_clean_up_lookahead-related_code_pr3
Open

Clean up lookahead-related code#25
tomerqodo wants to merge 7 commits into
coderabbit_full_base_clean_up_lookahead-related_code_pr3from
coderabbit_full_head_clean_up_lookahead-related_code_pr3

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 29, 2026

Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#3

Summary by CodeRabbit

  • Bug Fixes
    • Improved detection and handling of cross-slot commands in cluster mode for more accurate error reporting.
    • Enhanced cluster slot identification logic for more reliable command routing across cluster nodes.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown

Walkthrough

This pull request refactors cluster slot extraction logic by introducing a new CLUSTER_CROSSSLOT sentinel constant, replacing older sentinel values (GETSLOT_NOKEYS and GETSLOT_CROSSSLOT), and modifying extractSlotFromKeysResult to track slots via a temporary buffer with updated error semantics for zero-key and cross-slot scenarios.

Changes

Cohort / File(s) Summary
Core Slot Extraction Logic
src/cluster.c
Rewrites extractSlotFromKeysResult to allocate a temporary buffer for tracking computed slots per key, returns CLUSTER_CROSSSLOT on detecting cross-slot conflicts instead of INVALID_CLUSTER_SLOT, and treats zero keys as INVALID_CLUSTER_SLOT only when cluster is disabled.
Sentinel Constant Definitions
src/cluster.h, src/server.h
Adds new CLUSTER_CROSSSLOT macro in cluster.h; removes obsolete GETSLOT_NOKEYS and GETSLOT_CROSSSLOT macros from server.h public API surface.
Cross-Slot Handling Updates
src/cluster_asm.c, src/db.c, src/server.c
Updates references from GETSLOT_CROSSSLOT to CLUSTER_CROSSSLOT and from GETSLOT_NOKEYS to INVALID_CLUSTER_SLOT; adjusts control flow in preprocessCommand to detect cross-slot via new constant and set read error accordingly.
Sentinel Value Replacements
src/cluster_slot_stats.c
Replaces hard-coded sentinel -1 checks with INVALID_CLUSTER_SLOT named constant in slot validity predicates.
Documentation & Comment Adjustments
src/networking.c
Updates comment documenting that argv[j] may be NULL in freed pending command loop.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 We hop through slots with careful grace,
Cross-slot checks now have their place,
With buffers tracked and constants renamed,
The cluster slots are tamed!
A refactor done with wisdom's touch—
Small changes that mean oh-so-much. 🎯

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Clean up lookahead-related code' is misleading. The changes focus on cluster slot extraction logic and sentinel value refactoring, not lookahead-related code cleanup. Revise the title to reflect the actual changes, such as 'Refactor cluster slot extraction and unify sentinel values' or 'Clean up slot extraction logic and rename cluster constants'.
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/cluster_slot_stats.c`:
- Around line 139-141: The guard in canAddNetworkBytesOut only excludes
INVALID_CLUSTER_SLOT and can still allow CLUSTER_CROSSSLOT (a negative
sentinel), which can trigger serverAssert(c->slot >= 0...) — change the
condition to explicitly reject negative slots (e.g. require c->slot >= 0) or
check both sentinels (c->slot != INVALID_CLUSTER_SLOT && c->slot !=
CLUSTER_CROSSSLOT) in canAddNetworkBytesOut and update the other similar guards
in this file that currently only compare against INVALID_CLUSTER_SLOT so they
also reject negative/CLUSTER_CROSSSLOT values before doing slot-based
accounting.

In `@src/cluster.c`:
- Around line 1112-1115: The for-loop iterating over keys_result uses j <=
keys_result->numkeys which causes an off-by-one and out-of-bounds access; update
the loop condition to iterate j from 0 to j < keys_result->numkeys so you only
access keys_result->keys[j], argv[keys_result->keys[j].pos], and slot_buffer[j];
ensure slot_buffer is sized for keys_result->numkeys and keep use of
keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr)) and robj *this_key
unchanged.
- Around line 1110-1124: Replace use of deprecated malloc/free for slot_buffer
with Redis allocation APIs: allocate slot_buffer with zmalloc(sizeof(int) *
keys_result->numkeys) and free it with zfree(slot_buffer); ensure you still
handle allocation failure (check for NULL and return an error or cleanup) in the
same block that references slot_buffer, preserving the existing logic around
keyHashSlot, keys_result, first_slot, INVALID_CLUSTER_SLOT and the
CLUSTER_CROSSSLOT return path.
🧹 Nitpick comments (2)
src/cluster.h (1)

26-26: Add an inline comment for the new sentinel.

CLUSTER_CROSSSLOT is public API but lacks a brief semantic note like INVALID_CLUSTER_SLOT, which helps readers and downstream usage.

📝 Suggested tweak
-#define CLUSTER_CROSSSLOT  (-2)
+#define CLUSTER_CROSSSLOT  (-2) /* Keys hash to different slots (cross-slot). */
src/cluster.c (1)

1108-1124: slot_buffer is allocated but never read — consider removing it.

The slot_buffer array is populated with slot values (slot_buffer[j] = this_slot) but these values are never used. The cross-slot detection logic only uses first_slot and this_slot directly. This buffer appears to be leftover from a previous implementation or intended for future use.

If the buffer is not needed, removing it would simplify the code and avoid unnecessary allocations.

♻️ Simplified implementation without unused buffer
 int extractSlotFromKeysResult(robj **argv, getKeysResult *keys_result) {
     if (keys_result->numkeys == 0 || !server.cluster_enabled)
         return INVALID_CLUSTER_SLOT;

     int first_slot = INVALID_CLUSTER_SLOT;

-    /* Allocate temporary buffer for slot tracking */
-    int *slot_buffer = zmalloc(sizeof(int) * keys_result->numkeys);
-
     for (int j = 0; j < keys_result->numkeys; j++) {
         robj *this_key = argv[keys_result->keys[j].pos];
         int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
-        slot_buffer[j] = this_slot;

         if (first_slot == INVALID_CLUSTER_SLOT)
             first_slot = this_slot;
         else if (first_slot != this_slot) {
-            zfree(slot_buffer);
             return CLUSTER_CROSSSLOT;
         }
     }
-    zfree(slot_buffer);
     return first_slot;
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77a2509 and cfae119.

📒 Files selected for processing (8)
  • src/cluster.c
  • src/cluster.h
  • src/cluster_asm.c
  • src/cluster_slot_stats.c
  • src/db.c
  • src/networking.c
  • src/server.c
  • src/server.h
🧰 Additional context used
🧬 Code graph analysis (3)
src/cluster_slot_stats.c (1)
src/server.h (1)
  • clusterSlotStatsEnabled (3741-3741)
src/db.c (1)
src/cluster.c (1)
  • extractSlotFromKeysResult (1103-1126)
src/cluster.c (2)
src/cluster.h (1)
  • keyHashSlot (66-85)
src/sds.h (1)
  • sdslen (73-86)
🪛 GitHub Actions: Codecov
src/cluster.c

[error] 1110-1124: lcov: 'malloc' is deprecated and 'free' is deprecated when building with -Werror. Update code to use non-deprecated allocation/free patterns.

🪛 GitHub Actions: External Server Tests
src/cluster.c

[error] 1110-1124: Werror: 'malloc' and 'free' are deprecated declarations. Build failed due to treating deprecated declarations as errors while compiling cluster.c (lines 1110-1124). Command causing the failure: make REDIS_CFLAGS=-Werror (via 'make -C src all'), triggered by -Werror in the build.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Analyze (cpp)
  • GitHub Check: build-old-chain-jemalloc
  • GitHub Check: code-coverage
  • GitHub Check: build-centos-jemalloc
  • GitHub Check: build-centos-jemalloc
🔇 Additional comments (7)
src/networking.c (1)

5099-5100: LGTM!

Good documentation addition. The comment accurately explains the scenario where argv[j] can be NULL: when reclaimPendingCommand handles IO thread commands, it decrements the refcount and sets argv[j] = NULL for shared objects before deferring the free operation. This clarifies the purpose of the existing NULL guard.

src/cluster_asm.c (2)

754-778: Cross-slot handling in migration feed looks correct.

Deferring cancellation via cross_slot_during_propagating avoids recursion while preventing further propagation in the same cycle.


3455-3467: Module propagation slot checks are consistent with new sentinels.

Rejecting CLUSTER_CROSSSLOT and allowing INVALID_CLUSTER_SLOT (no-keys) matches the updated semantics.

src/server.h (1)

2410-2413: LGTM: flag addition is clean and non-overlapping.

src/server.c (1)

4135-4146: LGTM — cross-slot precheck looks correct.

Setting CLIENT_READ_CROSS_SLOT and resetting to INVALID_CLUSTER_SLOT keeps downstream routing from using a sentinel slot.

src/db.c (2)

429-441: LGTM!

The refactoring of getSlotFromCommand to delegate slot extraction to extractSlotFromKeysResult simplifies the code and centralizes the slot extraction logic. The early return for no command or disabled cluster is appropriate.


3200-3203: LGTM!

The conditional now correctly guards the slot extraction call, which is appropriate since extractSlotFromKeysResult handles the zero-key case internally by returning INVALID_CLUSTER_SLOT.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/cluster_slot_stats.c
Comment on lines 139 to 141
static int canAddNetworkBytesOut(client *c) {
return clusterSlotStatsEnabled() && c->slot != -1;
return clusterSlotStatsEnabled() && c->slot != INVALID_CLUSTER_SLOT;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard against CLUSTER_CROSSSLOT to avoid assertion crashes.
If c->slot can be CLUSTER_CROSSSLOT (new sentinel), the current checks only exclude INVALID_CLUSTER_SLOT, so serverAssert(c->slot >= 0...) can fire on cross-slot commands. Recommend excluding any negative slot (or explicitly checking both sentinels) before accounting.

🔧 Suggested fix
static int canAddNetworkBytesOut(client *c) {
-    return clusterSlotStatsEnabled() && c->slot != INVALID_CLUSTER_SLOT;
+    return clusterSlotStatsEnabled() && c->slot >= 0;
}

static int canAddCpuDuration(client *c) {
     return server.cluster_slot_stats_enabled &&  /* Config should be enabled. */
            server.cluster_enabled &&             /* Cluster mode should be enabled. */
-           c->slot != INVALID_CLUSTER_SLOT &&    /* Command should be slot specific. */
+           c->slot >= 0 &&                       /* Command should be slot specific. */
            (!server.execution_nesting ||         /* Either command should not be nested, */
             (c->realcmd->flags & CMD_BLOCKING)); /* or it must be due to unblocking. */
}

static int canAddNetworkBytesIn(client *c) {
-    return clusterSlotStatsEnabled() && c->slot != INVALID_CLUSTER_SLOT &&
+    return clusterSlotStatsEnabled() && c->slot >= 0 &&
         !(c->flags & CLIENT_BLOCKED) && !server.in_exec;
}

Also applies to: 223-227, 246-253

🤖 Prompt for AI Agents
In `@src/cluster_slot_stats.c` around lines 139 - 141, The guard in
canAddNetworkBytesOut only excludes INVALID_CLUSTER_SLOT and can still allow
CLUSTER_CROSSSLOT (a negative sentinel), which can trigger serverAssert(c->slot
>= 0...) — change the condition to explicitly reject negative slots (e.g.
require c->slot >= 0) or check both sentinels (c->slot != INVALID_CLUSTER_SLOT
&& c->slot != CLUSTER_CROSSSLOT) in canAddNetworkBytesOut and update the other
similar guards in this file that currently only compare against
INVALID_CLUSTER_SLOT so they also reject negative/CLUSTER_CROSSSLOT values
before doing slot-based accounting.

Comment thread src/cluster.c
Comment on lines +1110 to +1124
int *slot_buffer = malloc(sizeof(int) * keys_result->numkeys);

for (int j = 0; j <= keys_result->numkeys; j++) {
robj *this_key = argv[keys_result->keys[j].pos];
int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
slot_buffer[j] = this_slot;

if (first_slot == INVALID_CLUSTER_SLOT)
first_slot = this_slot;
else if (first_slot != this_slot) {
return INVALID_CLUSTER_SLOT;
free(slot_buffer);
return CLUSTER_CROSSSLOT;
}
}
free(slot_buffer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Build failure: Use zmalloc/zfree instead of deprecated malloc/free.

The pipeline failures indicate that malloc and free are deprecated in this codebase. Redis/Valkey uses its own memory allocation functions (zmalloc/zfree) for consistency and memory tracking.

🔧 Proposed fix
     /* Allocate temporary buffer for slot tracking */
-    int *slot_buffer = malloc(sizeof(int) * keys_result->numkeys);
+    int *slot_buffer = zmalloc(sizeof(int) * keys_result->numkeys);

-    for (int j = 0; j <= keys_result->numkeys; j++) {
+    for (int j = 0; j < keys_result->numkeys; j++) {
         robj *this_key = argv[keys_result->keys[j].pos];
         int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
         slot_buffer[j] = this_slot;

         if (first_slot == INVALID_CLUSTER_SLOT)
             first_slot = this_slot;
         else if (first_slot != this_slot) {
-            free(slot_buffer);
+            zfree(slot_buffer);
             return CLUSTER_CROSSSLOT;
         }
     }
-    free(slot_buffer);
+    zfree(slot_buffer);
     return first_slot;
🧰 Tools
🪛 GitHub Actions: Codecov

[error] 1110-1124: lcov: 'malloc' is deprecated and 'free' is deprecated when building with -Werror. Update code to use non-deprecated allocation/free patterns.

🪛 GitHub Actions: External Server Tests

[error] 1110-1124: Werror: 'malloc' and 'free' are deprecated declarations. Build failed due to treating deprecated declarations as errors while compiling cluster.c (lines 1110-1124). Command causing the failure: make REDIS_CFLAGS=-Werror (via 'make -C src all'), triggered by -Werror in the build.

🤖 Prompt for AI Agents
In `@src/cluster.c` around lines 1110 - 1124, Replace use of deprecated
malloc/free for slot_buffer with Redis allocation APIs: allocate slot_buffer
with zmalloc(sizeof(int) * keys_result->numkeys) and free it with
zfree(slot_buffer); ensure you still handle allocation failure (check for NULL
and return an error or cleanup) in the same block that references slot_buffer,
preserving the existing logic around keyHashSlot, keys_result, first_slot,
INVALID_CLUSTER_SLOT and the CLUSTER_CROSSSLOT return path.

Comment thread src/cluster.c
Comment on lines +1112 to +1115
for (int j = 0; j <= keys_result->numkeys; j++) {
robj *this_key = argv[keys_result->keys[j].pos];
int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
slot_buffer[j] = this_slot;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Off-by-one error causes out-of-bounds array access.

The loop condition j <= keys_result->numkeys iterates one element past the valid range. Array indices are 0 to numkeys - 1, so accessing keys_result->keys[numkeys] is undefined behavior.

🐛 Proposed fix
-    for (int j = 0; j <= keys_result->numkeys; j++) {
+    for (int j = 0; j < keys_result->numkeys; j++) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (int j = 0; j <= keys_result->numkeys; j++) {
robj *this_key = argv[keys_result->keys[j].pos];
int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
slot_buffer[j] = this_slot;
for (int j = 0; j < keys_result->numkeys; j++) {
robj *this_key = argv[keys_result->keys[j].pos];
int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
slot_buffer[j] = this_slot;
🤖 Prompt for AI Agents
In `@src/cluster.c` around lines 1112 - 1115, The for-loop iterating over
keys_result uses j <= keys_result->numkeys which causes an off-by-one and
out-of-bounds access; update the loop condition to iterate j from 0 to j <
keys_result->numkeys so you only access keys_result->keys[j],
argv[keys_result->keys[j].pos], and slot_buffer[j]; ensure slot_buffer is sized
for keys_result->numkeys and keep use of keyHashSlot((char*)this_key->ptr,
sdslen(this_key->ptr)) and robj *this_key unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants