Skip to content

Fix HINCRBYFLOAT removes field expiration on replica#21

Open
tomerqodo wants to merge 5 commits into
coderabbit_combined_20260121_augment_sentry_coderabbit_1_base_fix_hincrbyfloat_removes_field_expiration_on_replica_pr104from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_fix_hincrbyfloat_removes_field_expiration_on_replica_pr104
Open

Fix HINCRBYFLOAT removes field expiration on replica#21
tomerqodo wants to merge 5 commits into
coderabbit_combined_20260121_augment_sentry_coderabbit_1_base_fix_hincrbyfloat_removes_field_expiration_on_replica_pr104from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_fix_hincrbyfloat_removes_field_expiration_on_replica_pr104

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 22, 2026

Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#104

Summary by CodeRabbit

  • New Features

    • Improved hash field expiration tracking and handling. Hash fields with active expiration times now maintain their expiration state consistently during field operations and when replicated to replica nodes, ensuring synchronized behavior across instances.
  • Tests

    • Added test coverage for hash field expiration behavior during replication scenarios to verify expiration times are correctly preserved and propagated.

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

@coderabbitai

coderabbitai Bot commented Jan 22, 2026

Copy link
Copy Markdown

Walkthrough

Adds infrastructure for tracking per-field expiration times in hash operations. Introduces a shared object "FIELDS", extends hashTypeGetValue with an optional expiration time output parameter, updates call sites throughout hash field operations, and adds test coverage for HINCRBYFLOAT replication behavior with field expiration.

Changes

Cohort / File(s) Summary
Shared Objects Setup
src/server.c, src/server.h
Initializes new shared object shared.fields in createSharedObjects() and declares corresponding robj *fields pointer in sharedObjectsStruct
Hash Field Expiration Logic
src/t_hash.c
Adds uint64_t *expiredAt output parameter to hashTypeGetValue() to retrieve per-field expiration times; updates expiration check from >= to > comparison; propagates parameter through listpack and hash-table code paths; integrates field expiration data into HINCRBYFLOAT and related handlers to control replication order (HSET followed by HPEXPIREAT when applicable)
Test Coverage
tests/unit/type/hash-field-expire.tcl
Adds test case "HINCRBYFLOAT command won't remove field expiration on replica" verifying replication behavior: validates HSET-only propagation when no expiry exists and HSET+HPEXPIREAT sequence when expiry is present; confirms replica field values and TTLs match master state

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Master as Master Server
    participant HashOps as Hash Operations
    participant Replication as Replication Layer
    participant Replica as Replica Server

    Client->>Master: HINCRBYFLOAT with field
    Master->>HashOps: hashTypeGetValue(field, &expiredAt)
    HashOps->>HashOps: Retrieve field value & expiration
    HashOps-->>Master: Return value + expiredAt
    
    alt Field has expiration
        Master->>Replication: Propagate HSET
        Master->>Replication: Propagate HPEXPIREAT
        Replication->>Replica: HSET command
        Replication->>Replica: HPEXPIREAT command
        Replica-->>Replica: Update field with expiration
    else No expiration
        Master->>Replication: Propagate HSET only
        Replication->>Replica: HSET command
        Replica-->>Replica: Update field
    end
    
    Master-->>Client: OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A field's time was hidden deep,
Now HINCRBYFLOAT makes expiry keep,
With replicas mirrored, expirations aligned—
Shared objects and logic so finely designed!
Field expiration takes its flight,
Making hash operations truly right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main fix: preventing HINCRBYFLOAT from removing field expiration on replica, which aligns with the core changes across server initialization, hash type handling, and replication logic.

✏️ 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: 2

🤖 Fix all issues with AI agents
In `@src/t_hash.c`:
- Around line 2242-2244: The variable unused_flag is declared but never used in
t_hash.c (see the block with has_expiration, expireat, unused_flag), causing
-Werror=unused-variable; remove the unused_flag declaration (or if it was meant
to be used, implement its usage) so only used variables remain (e.g., keep
has_expiration and expireat, delete the unused_flag line) to resolve the build
failure.
- Around line 2306-2314: The argv buffer overflow occurs because argv is
declared as robj *argv[5] but indices 0..5 are written and passed to
alsoPropagate; change the declaration to robj *argv[6] so it can hold six
elements and keep alsoPropagate(c->db->id, argv, 6, ...) unchanged; additionally
the temporary object created by createStringObjectFromLongLong(expireat)
(currently assigned to argv[2]) must be released after propagation to avoid a
leak—call the appropriate reference decrement (e.g., decrRefCount) on that
object once alsoPropagate returns (or avoid creating an extra ref by reusing an
existing object) ensuring correct ownership semantics.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e359ef and 2d00120.

📒 Files selected for processing (4)
  • src/server.c
  • src/server.h
  • src/t_hash.c
  • tests/unit/type/hash-field-expire.tcl
🧰 Additional context used
🧬 Code graph analysis (1)
src/server.c (1)
src/object.c (1)
  • createStringObject (102-107)
🪛 GitHub Actions: CI
src/t_hash.c

[error] 2244-2244: Step failed: 'cd src && make 32bit' -> t_hash.c: unused variable 'unused_flag' [-Werror=unused-variable].

⏰ 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). (1)
  • GitHub Check: Analyze (cpp)
🔇 Additional comments (7)
src/server.h (1)

1328-1328: LGTM!

The addition of the fields shared object follows the established pattern for pre-allocated constant strings. This will be used in hash field expiration operations, avoiding repeated allocations for the "FIELDS" string.

src/server.c (1)

1988-1991: Looks good: shared FIELDS object initialized consistently.

tests/unit/type/hash-field-expire.tcl (1)

1269-1320: LGTM! Well-structured test for HINCRBYFLOAT replication with field expiration.

The test comprehensively covers:

  1. Replication stream validation ensuring HSET+HPEXPIREAT multi-sequence for fields with TTL
  2. Actual replica state verification confirming TTL preservation and value consistency
src/t_hash.c (4)

713-742: LGTM! Clean implementation of the expiredAt out-parameter.

The NULL-safe pattern using a local dummy variable is appropriate, and the comparison change from >= to > correctly treats fields expiring at exactly the current timestamp as expired.


2202-2203: LGTM!

Correctly passes NULL for expiredAt since HINCRBY doesn't need to preserve field expiration in this code path (it uses HASH_SET_KEEP_TTL flag instead).


2252-2264: LGTM!

Correctly captures the field's expiration time before modification and sets the has_expiration flag when the field has a valid TTL.


2297-2305: LGTM!

The approach of preventing automatic propagation and manually propagating HSET followed by HPEXPIREAT ensures correct replication order for fields with expiration.

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

Comment thread src/t_hash.c
Comment on lines +2242 to +2244
int has_expiration = 0;
uint64_t expireat = EB_EXPIRE_TIME_INVALID;
int unused_flag = 0;

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

Remove unused variable causing build failure.

The pipeline fails due to -Werror=unused-variable for unused_flag which is declared but never used.

🐛 Proposed fix
     int has_expiration = 0;
     uint64_t expireat = EB_EXPIRE_TIME_INVALID;
-    int unused_flag = 0;
📝 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
int has_expiration = 0;
uint64_t expireat = EB_EXPIRE_TIME_INVALID;
int unused_flag = 0;
int has_expiration = 0;
uint64_t expireat = EB_EXPIRE_TIME_INVALID;
🧰 Tools
🪛 GitHub Actions: CI

[error] 2244-2244: Step failed: 'cd src && make 32bit' -> t_hash.c: unused variable 'unused_flag' [-Werror=unused-variable].

🤖 Prompt for AI Agents
In `@src/t_hash.c` around lines 2242 - 2244, The variable unused_flag is declared
but never used in t_hash.c (see the block with has_expiration, expireat,
unused_flag), causing -Werror=unused-variable; remove the unused_flag
declaration (or if it was meant to be used, implement its usage) so only used
variables remain (e.g., keep has_expiration and expireat, delete the unused_flag
line) to resolve the build failure.

Comment thread src/t_hash.c
Comment on lines +2306 to +2314
robj *argv[5];
argv[0] = shared.hpexpireat;
argv[1] = c->argv[1];
argv[2] = createStringObjectFromLongLong(expireat);
argv[3] = shared.fields;
argv[4] = shared.integers[1];
argv[5] = c->argv[2];
alsoPropagate(c->db->id, argv, 6, PROPAGATE_AOF|PROPAGATE_REPL);
}

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: Buffer overflow in argv array declaration.

The array is declared with 5 elements (robj *argv[5]) but code writes to index 5, which is out of bounds. Additionally, argv[2] is created with createStringObjectFromLongLong but never freed, causing a memory leak.

🐛 Proposed fix
-        robj *argv[5];
+        robj *argv[6];
         argv[0] = shared.hpexpireat;
         argv[1] = c->argv[1];
         argv[2] = createStringObjectFromLongLong(expireat);
         argv[3] = shared.fields;
         argv[4] = shared.integers[1];
         argv[5] = c->argv[2];
         alsoPropagate(c->db->id, argv, 6, PROPAGATE_AOF|PROPAGATE_REPL);
+        decrRefCount(argv[2]);
     }
🤖 Prompt for AI Agents
In `@src/t_hash.c` around lines 2306 - 2314, The argv buffer overflow occurs
because argv is declared as robj *argv[5] but indices 0..5 are written and
passed to alsoPropagate; change the declaration to robj *argv[6] so it can hold
six elements and keep alsoPropagate(c->db->id, argv, 6, ...) unchanged;
additionally the temporary object created by
createStringObjectFromLongLong(expireat) (currently assigned to argv[2]) must be
released after propagation to avoid a leak—call the appropriate reference
decrement (e.g., decrRefCount) on that object once alsoPropagate returns (or
avoid creating an extra ref by reusing an existing object) ensuring correct
ownership semantics.

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