Drop the stale-client close message in sync runs - #97
Merged
Merged
Conversation
Each sync run() gets its own event loop. The OpenAI SDK's async client sits in a reference cycle, so one from an earlier run can be garbage- collected while a later run's loop is running. Its __del__ schedules aclose() on that loop for connections opened on the earlier one, and asyncio prints "Task exception was never retrieved ... Event loop is closed". Results are not affected. any-llm has no public way to close a provider's client, so the client cannot be closed before its loop ends. The four sync wrappers (ModelAuditor.run, AuditExperiment.run, CrossJudgeExperiment.run and reframing's _run_sync) now go through run_sync, which installs an exception handler that drops exactly that case: an AsyncClient.aclose() task failing with "Event loop is closed". Anything else still reaches asyncio's default handler. The regression test reproduces it without a network: a real OpenAI client against a local HTTP server, used in one run and collected during the next.
This was referenced Oct 2, 2026
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.
Follow-up to #96. This commit was pushed after #96 merged.
After several
run()calls in one process, asyncio printed "Task exception was never retrieved … RuntimeError: Event loop is closed". This showed up in the live run that #96 came from. Results were not affected.Cause. Each sync
run()callsasyncio.run(), so each run has its own event loop. OpenAI SDK clients sit in reference cycles and are freed whenever the cyclic GC runs. If that happens while a later run's loop is running, the client's__del__schedulesaclose()on that loop for connections opened on the earlier, closed one, and the task fails. Reusing one auditor across runs is fine: three consecutiverun()calls on one auditor worked live.Change.
ModelAuditor.run,AuditExperiment.run,CrossJudgeExperiment.runand reframing's_run_sync(rejudgeand friends) now go throughsimpleaudit._event_loop.run_sync. It works likeasyncio.run, but sets an exception handler on the loop that drops exactly one case: anAsyncClient.aclose()task failing with "Event loop is closed". Everything else still goes to asyncio's default handler.The real fix is to close the clients before their loop ends, but any-llm has no public close method. Asked upstream: mozilla-ai/any-llm#1439.
Not covered:
run_async()called from your own event loop, for example in a notebook, uses that loop's handler, so the message can still appear there.Testing
pyteston Python 3.11 (-n auto) and 3.13: 1366 passed, 1 skipped.tests/test_event_loop.py, 6 tests:asyncio.runand not underrun_sync.