fix(tracker): add rate limiter to prevent event dispatch loops#2515
Merged
tusharmath merged 4 commits intomainfrom Mar 11, 2026
Merged
fix(tracker): add rate limiter to prevent event dispatch loops#2515tusharmath merged 4 commits intomainfrom
tusharmath merged 4 commits intomainfrom
Conversation
Comment on lines
+26
to
+45
| pub fn check(&self) -> bool { | ||
| let now = Utc::now().timestamp() as u64; | ||
| let window_start = self.window_start.load(Ordering::Relaxed); | ||
|
|
||
| // If a minute has passed, reset the window and counter | ||
| if now.saturating_sub(window_start) >= 60 { | ||
| // We use compare_exchange to avoid race conditions when multiple threads try to | ||
| // reset | ||
| if self | ||
| .window_start | ||
| .compare_exchange(window_start, now, Ordering::SeqCst, Ordering::Relaxed) | ||
| .is_ok() | ||
| { | ||
| self.count.store(0, Ordering::SeqCst); | ||
| } | ||
| } | ||
|
|
||
| // Increment and check the rate limit | ||
| self.count.fetch_add(1, Ordering::Relaxed) < self.max_per_minute | ||
| } |
Contributor
There was a problem hiding this comment.
Critical race condition: The window reset is not atomic with the count reset. After window_start is updated via compare_exchange (line 36), but before count.store(0) executes (line 39), other threads may see the new window_start value and skip the reset check (line 31), then increment the old (un-reset) count value (line 44). This allows events beyond the rate limit.
Scenario:
- Thread A: CAS succeeds, sets
window_startto new time - Thread A: Gets preempted before executing
count.store(0) - Thread B: Loads new
window_start, sees no reset needed (time diff < 60) - Thread B: Increments old count value (e.g., 1005) via
fetch_add - Thread A: Resumes and resets count to 0
- Result: Events from the old window weren't properly limited
Fix: Use a single atomic operation or add a generation counter:
let current_count = self.count.fetch_add(1, Ordering::Relaxed);
if now.saturating_sub(window_start) >= 60 && current_count >= self.max_per_minute {
if self.window_start.compare_exchange(window_start, now, Ordering::SeqCst, Ordering::Relaxed).is_ok() {
self.count.store(1, Ordering::SeqCst);
return true;
}
}
current_count < self.max_per_minuteSpotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
tusharmath
added a commit
that referenced
this pull request
Mar 13, 2026
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add rate limiter to the event tracker to prevent runaway dispatch loops that can occur when stderr/stdout are closed, causing each write error to trigger another error event.
Context
The tracker can enter a runaway loop scenario when stdout/stderr becomes unavailable (e.g., when the output stream is closed). Each write error would trigger another error event, potentially creating an infinite loop of error events. This was identified as a potential issue during error handling improvements.
Changes
RateLimitermodule (rate_limit.rs) with a thread-safe, atomic-based rate limiterdispatchmethod - events exceeding the limit are silently droppedKey Implementation Details
The rate limiter uses atomic operations for thread-safety without requiring a mutex:
AtomicU64for window start timestamp andAtomicUsizefor event counttrueif event is allowed,falseif rate limit exceeded (event is dropped)The 1,000 events/minute limit was chosen to:
Use Cases
Testing
Links