Skip to content

Clamp replenishment timer period to 1ms in rate limiters#130027

Open
su-senka wants to merge 1 commit into
dotnet:mainfrom
su-senka:fix/ratelimiter-submillisecond-timer-period
Open

Clamp replenishment timer period to 1ms in rate limiters#130027
su-senka wants to merge 1 commit into
dotnet:mainfrom
su-senka:fix/ratelimiter-submillisecond-timer-period

Conversation

@su-senka

Copy link
Copy Markdown

Fixes #109027

Problem

System.Threading.Timer truncates its period to whole milliseconds. So a sub-millisecond ReplenishmentPeriod (TokenBucket) or Window (FixedWindow / SlidingWindow) ends up as a timer period of 0. A period of 0 fires once and then never again, so auto-replenishment silently stops. No exception, the limiter just stalls. The repro in the issue used ReplenishmentPeriod = 500us.

Fix

Clamp the timer period to a 1ms floor at timer creation, in all three replenishing limiters (TokenBucketRateLimiter, FixedWindowRateLimiter, SlidingWindowRateLimiter).

This is the clamp-not-throw direction settled on in the issue. Existing limiters keep working, and a config that would have produced a sub-1ms interval ends up slightly more restrictive than asked for, which is safer than slightly less restrictive.

Notes

The clamp is on the timer only, not on the options. The public ReplenishmentPeriod property still returns whatever the user configured. The tests check this.

SlidingWindow is the easy one to miss here. Its effective period is Window / SegmentsPerWindow, so it can be sub-millisecond even when Window itself is >= 1ms (e.g. Window = 5ms, SegmentsPerWindow = 10 gives 500us). There's a test case for that.

No new exception, so no new resource string. The doc comments on the three options types now mention the 1ms floor.

One thing I left alone: FixedWindowRateLimiter.CreateFailedWindowLease and TokenBucketRateLimiter.CreateFailedTokenLease still compute RetryAfter from the original un-clamped value, so with a sub-1ms config the reported RetryAfter can be a touch under the real timer cadence. Didn't want to change user-visible lease metadata as part of this.

Testing

One regression test per limiter: a sub-millisecond period constructs fine with AutoReplenishment = true, and the observable ReplenishmentPeriod still reflects the configured value. The SlidingWindow test also covers the derived-period case above.

Built and ran the RateLimiting test suite locally on macOS/arm64. Relying on CI for the .NET Framework leg.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Jun 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @VSadov
See info in area-owners.md if you want to be subscribed.

@su-senka

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

System.Threading.Timer truncates its period to whole milliseconds, so a
sub-millisecond ReplenishmentPeriod/Window produced a timer period of 0,
which fires once and never again, silently stopping auto-replenishment.
Clamp the timer period to a 1ms floor in TokenBucket, FixedWindow, and
SlidingWindow limiters. The observable ReplenishmentPeriod is unchanged.

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

Labels

area-System.Threading community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET 8 Timer Implementation Has Silent Period Minimum and TokenBucketRateLimiter Is Not Aware of This

1 participant