fix: settle pending async sleeps on close - #892
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28db2acd5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (synchronized(lock) { pending.remove(future) }) { | ||
| future.complete(null) |
There was a problem hiding this comment.
Keep the future tracked until it is completed
When the timer fires concurrently with close(), this removes the future from pending and releases the lock before calling future.complete(null). During that gap, close() can acquire the lock, find no pending future, and return while the future is still incomplete, so shutdown does not deterministically settle every outstanding sleep as intended. Complete the future within the same critical section, or otherwise keep it tracked until completion has occurred.
Useful? React with 👍 / 👎.
Summary
Make
DefaultSleeper.close()settle all pendingsleepAsync()futures instead of abandoning them when its backingTimeris cancelled.Fixes #889.
Problem
DefaultSleeper.sleepAsync()completes its returned future from a scheduledTimerTask, whileclose()currently only calls:Timer.cancel()discards scheduled tasks that have not run. Their futures remain incomplete forever.In the SDK this can strand an async retry chain if the client is closed while
RetryingHttpClientis waiting onsleeper.sleepAsync(backoffDuration).A second edge case is
sleepAsync()after close:Timer.schedule()throws synchronously because the timer has already been cancelled.Fix
DefaultSleepernow:sleepAsync()calls instead of throwing synchronously.The synchronous
sleep()path is unchanged.Regression coverage
Added tests verifying:
sleepAsync()after close returns a cancelled future without throwing from the method call.Validation
mainatcf942a40074291290634321ad9fe21e514030b4c;DefaultSleeperand focused lifecycle tests;Full repository validation is left to GitHub Actions.
Risk
Low. The behavioral change is limited to sleeper shutdown: pending async waits now terminate deterministically instead of becoming permanently incomplete.