Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Handle invalid active defrag fragmentation thresholds#15513

Open
vitahlin wants to merge 17 commits into
redis:unstableredis/redis:unstablefrom
vitahlin:limit-defrag-thresholdvitahlin/redis:limit-defrag-thresholdCopy head branch name to clipboard
Open

Handle invalid active defrag fragmentation thresholds#15513
vitahlin wants to merge 17 commits into
redis:unstableredis/redis:unstablefrom
vitahlin:limit-defrag-thresholdvitahlin/redis:limit-defrag-thresholdCopy head branch name to clipboard

Conversation

@vitahlin

@vitahlin vitahlin commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Issue

computeDefragCycles() uses the difference between active-defrag-threshold-upper and active-defrag-threshold-lower as a divisor.

Equal thresholds cause a division-by-zero UBSAN error.

Change

Allow equal or reversed thresholds for backward compatibility and log a warning.

When active_defrag_threshold_upper <= active_defrag_threshold_lower, computeDefragCycles() uses active_defrag_cycle_max. This avoids division by zero without rejecting existing configurations.

Test

make SANITIZER=undefined DEBUG_DEFRAG=force             
./runtest --single unit/memefficiency --only "Active defrag handles equal fragmentation thresholds"

Before fix:
CleanShot 2026-07-22 at 19 03 42@2x

After fix:
CleanShot 2026-07-22 at 19 05 49@2x


Note

Low Risk
Small, localized change to defrag CPU calculation with a regression test; no auth, persistence, or replication impact.

Overview
Fixes undefined behavior in computeDefragCycles() when active-defrag-threshold-upper is not greater than active-defrag-threshold-lower. The adaptive CPU percentage used to come from INTERPOLATE(), which divides by (upper - lower) and UBSAN fails when those values are equal.

When upper <= lower, the code now skips interpolation and sets defrag effort to active_defrag_cycle_max, so equal or reversed threshold pairs remain accepted without crashing. redis.conf comments note that the lower threshold should stay below the upper bound.

Adds unit/memefficiency coverage that enables active defrag with equal thresholds (optionally under DEBUG_DEFRAG=force) and asserts the server stays up and defrag runs.

Reviewed by Cursor Bugbot for commit 4ca7681. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/config.c Outdated
Comment on lines +2557 to +2571
static int isValidActiveDefragThresholdLower(long long val, const char **err) {
if (val >= server.active_defrag_threshold_upper) {
*err = "active-defrag-threshold-lower must be less than active-defrag-threshold-upper";
return 0;
}
return 1;
}

static int isValidActiveDefragThresholdUpper(long long val, const char **err) {
if (val <= server.active_defrag_threshold_lower) {
*err = "active-defrag-threshold-upper must be greater than active-defrag-threshold-lower";
return 0;
}
return 1;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It'll be a breaking change.
Users who use the same threshold are not able to start now.
Maybe we can just add a warning here, then check if they are equal in computeDefragCycles().
If they are equal, we always use active_defrag_cycle_min.
@ShooterIT WDYT?

int cpu_pct;
if (server.active_defrag_threshold_upper <= server.active_defrag_threshold_lower) {
    cpu_pct = server.active_defrag_cycle_min;
} else {
    cpu_pct = INTERPOLATE(frag_pct,
            server.active_defrag_threshold_lower,
            server.active_defrag_threshold_upper,
            server.active_defrag_cycle_min,
            server.active_defrag_cycle_max);
}

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.

I feel like we should allow the same threshold.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated

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.

maybe i misunderstood, i was thinking it is not illegal if the lower value is equal to the upper value.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@vitahlin what happen if active_defrag_threshold_upper < active_defrag_threshold_lower?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When upper <= lower, reaching the lower threshold also means reaching the upper threshold, so using active_defrag_cycle_max is more consistent with the configuration semantics. It adds no extra computation cost, although an invalid threshold configuration may switch directly to the maximum defrag effort. I updated the code and added a comment.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if so, we don't need isValidActiveDefragThresholdLower, right?

@vitahlin vitahlin Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

isValidActiveDefragThresholdLower() only logs a warning . Maybe we can keep it to warn users and guide them toward a valid config?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm no object to it, @ShooterIT WDYT?

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.

it seems the most important thing of this PR is to fix calculating cpu_pct, right?

I am not sure if printing a log is useful, as @sundb said, maybe we can just add some comments in redis.conf

Comment thread src/config.c Outdated
Comment thread src/defrag.c Outdated
@vitahlin vitahlin changed the title Reject invalid active defrag fragmentation thresholds Handle invalid active defrag fragmentation thresholds Jul 23, 2026
Comment thread src/config.c Outdated
This reverts commit b294d35.
Comment thread src/defrag.c
Comment thread tests/unit/memefficiency.tcl Outdated
Comment thread tests/unit/memefficiency.tcl Outdated
Co-authored-by: debing.sun <debing.sun@redis.com>
Comment thread tests/unit/memefficiency.tcl
Co-authored-by: debing.sun <debing.sun@redis.com>
Comment thread tests/unit/memefficiency.tcl Outdated
Comment thread src/config.c Outdated
@vitahlin

Copy link
Copy Markdown
Contributor Author

CI failure is unrelated to this commit

!!! WARNING The following tests failed:
*** [err]: HPEXPIRETIME persists after RDB reload (listpackex) in tests/unit/type/hash-field-expire.tcl
Expected '1784863868456' to be equal to '-2' (context: type eval line 8 cmd {assert_equal $before $after} proc ::test)
Cleanup: may take some time... OK
make[1]: *** [Makefile:546: lcov] Error 1
make[1]: Leaving directory '/home/runner/work/redis/redis/src'
make: *** [Makefile:89: lcov] Error 2

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit e140680. Configure here.

[s total_active_defrag_time] ne 0
} else {
fail "defrag not started."
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test fails on jemalloc builds without DEBUG_DEFRAG_FORCE

Medium Severity

On standard jemalloc CI builds (without DEBUG_DEFRAG=force), catch {r config set activedefrag yes} succeeds, so the if guard at line 69 evaluates to true and the test enters wait_for_condition. However, getAllocatorFragmentation() returns real (near-zero) fragmentation for a fresh empty server, so computeDefragCycles() returns early at the frag_pct < active_defrag_threshold_lower check and defrag never starts. The wait_for_condition then times out and the test fails with "defrag not started." The catch only handles non-jemalloc builds, not jemalloc-without-forced-fragmentation builds.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e140680. Configure here.

@vitahlin

Copy link
Copy Markdown
Contributor Author

Fully daily test with command :--loops 100 --single unit/memefficiency --only "Active defrag handles equal fragmentation thresholds": https://github.com/vitahlin/redis/actions/runs/30109529061

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.

3 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.