Skip to content

Fix crash when active field-expiry leaves a single-entry HT vset buck…#3958

Merged
ranshid merged 1 commit into
valkey-io:9.0from
ranshid:backport-9.0
Jun 10, 2026
Merged

Fix crash when active field-expiry leaves a single-entry HT vset buck…#3958
ranshid merged 1 commit into
valkey-io:9.0from
ranshid:backport-9.0

Conversation

@ranshid

@ranshid ranshid commented Jun 10, 2026

Copy link
Copy Markdown
Member

backport of (#3950)

When a hash has many fields whose expirations fall in the same volatile-set time-bucket (>127 entries), that bucket is encoded as a hashtable (HT). The active field-expiry path drains expired entries via vsetBucketRemoveExpired_HASHTABLE(), but it is bounded by the per-key expire quota (max_count, from dbReclaimExpiredFields). When the quota stops the drain one entry short, the function only collapsed the bucket to NONE at size 0 and left it as an HT bucket holding a single entry.

That violates the invariant enforced by removeFromBucket_HASHTABLE(), which asserts hashtableSize(ht) > 0 after a delete and downgrades an HT bucket to SINGLE at size 1. A later normal removal of that lone field -- HDEL, or a cross-bucket HSETEX update routed through removeEntryFromRaxBucket() -- deletes the sole entry, drops the size 1 -> 0, and trips the assertion.

Fix: Apply the same downgrade in the expiry path: when draining leaves exactly one entry, downgrade the HT bucket to SINGLE so the invariant holds. Behavior is unchanged when the bucket drains fully (-> NONE) or retains two or more entries (-> HT, as the normal removal path already tolerates).

Add a regression test that fills one time-bucket past the HT threshold, expires all but one entry, then removes the survivor through the normal path; it crashed before this fix and passes after.

…et (valkey-io#3950)

When a hash has many fields whose expirations fall in the same
volatile-set time-bucket (>127 entries), that bucket is encoded as a
hashtable (HT). The active field-expiry path drains expired entries via
vsetBucketRemoveExpired_HASHTABLE(), but it is bounded by the per-key
expire quota (max_count, from dbReclaimExpiredFields). When the quota
stops the drain one entry short, the function only collapsed the bucket
to NONE at size 0 and left it as an HT bucket holding a single entry.

That violates the invariant enforced by removeFromBucket_HASHTABLE(),
which asserts hashtableSize(ht) > 0 after a delete and downgrades an HT
bucket to SINGLE at size 1. A later normal removal of that lone field --
HDEL, or a cross-bucket HSETEX update routed through
removeEntryFromRaxBucket() -- deletes the sole entry, drops the size 1
-> 0, and trips the assertion.

Fix: Apply the same downgrade in the expiry path: when draining leaves
exactly one entry, downgrade the HT bucket to SINGLE so the invariant
holds. Behavior is unchanged when the bucket drains fully (-> NONE) or
retains two or more entries (-> HT, as the normal removal path already
tolerates).

Add a regression test that fills one time-bucket past the HT threshold,
expires all but one entry, then removes the survivor through the normal
path; it crashed before this fix and passes after.

Signed-off-by: Ran Shidlansik <ranshid@amazon.com>
@ranshid ranshid added bug Something isn't working release-notes This issue should get a line item in the release notes labels Jun 10, 2026
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a1086f81-169e-4c0b-af78-8c5456322dae

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@enjoy-binbin enjoy-binbin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, I thought we already had a backport bot.

@ranshid ranshid merged commit 28af543 into valkey-io:9.0 Jun 10, 2026
66 of 72 checks passed
@github-project-automation github-project-automation Bot moved this to To be backported in Valkey 9.0 Jun 10, 2026
@valkeyrie-ops valkeyrie-ops Bot moved this from To be backported to Done in Valkey 9.0 Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working release-notes This issue should get a line item in the release notes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants