Skip to content

fix: settle pending async sleeps on close - #892

Open
sylvesterkaczmarek wants to merge 2 commits into
openai:mainfrom
sylvesterkaczmarek:fix/default-sleeper-close-pending
Open

fix: settle pending async sleeps on close#892
sylvesterkaczmarek wants to merge 2 commits into
openai:mainfrom
sylvesterkaczmarek:fix/default-sleeper-close-pending

Conversation

@sylvesterkaczmarek

Copy link
Copy Markdown

Summary

Make DefaultSleeper.close() settle all pending sleepAsync() futures instead of abandoning them when its backing Timer is cancelled.

Fixes #889.

Problem

DefaultSleeper.sleepAsync() completes its returned future from a scheduled TimerTask, while close() currently only calls:

timer.cancel()

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 RetryingHttpClient is waiting on sleeper.sleepAsync(backoffDuration).

A second edge case is sleepAsync() after close: Timer.schedule() throws synchronously because the timer has already been cancelled.

Fix

DefaultSleeper now:

  • tracks pending async sleep futures under a small lock;
  • removes futures when their timer task runs or when the future is otherwise settled;
  • marks itself closed and cancels the timer exactly once;
  • cancels every still-pending future during close;
  • returns an already-cancelled future for post-close sleepAsync() calls instead of throwing synchronously.

The synchronous sleep() path is unchanged.

Regression coverage

Added tests verifying:

  1. closing the sleeper cancels a long pending async sleep immediately;
  2. a normally completed async sleep stays successfully completed after close;
  3. repeated close is safe;
  4. sleepAsync() after close returns a cancelled future without throwing from the method call.

Validation

  • branch is based on current upstream main at cf942a40074291290634321ad9fe21e514030b4c;
  • branch is 0 commits behind upstream;
  • changes are limited to DefaultSleeper and focused lifecycle tests;
  • normal timer-driven completion remains unchanged for pending sleeps.

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.

@sylvesterkaczmarek
sylvesterkaczmarek requested a review from a team as a code owner August 18, 2026 15:58

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +22 to +23
if (synchronized(lock) { pending.remove(future) }) {
future.complete(null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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.

DefaultSleeper.close can strand pending sleepAsync futures

1 participant