mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(runtime): yield once in RunJournal.flush() so cross-thread events survive on Python 3.13+ (#6565)
Since CPython gh-105836 (backported to 3.13) an awaited executor future can complete without a loop iteration when the worker finished before the future was chained, so a middleware event a worker handed to the owner loop with call_soon_threadsafe may still be pending when flush() starts. The run-end flush() -> _detach_runtime_dependencies() sequence then dropped it. Yield to the loop once before draining and re-check _closed. Fixes #6564 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7ff13bad45
commit
259e1d1377
@ -100,7 +100,10 @@ producers (currently slash-skill activation via `asyncio.to_thread`) schedule
|
||||
journal mutation directly onto its owning event loop; they never mutate or
|
||||
flush `RunJournal._buffer` from the worker thread. The task-tool subagent proxy
|
||||
rejects a loop that differs from the journal owner, so its close fence always
|
||||
drains the only scheduling hop.
|
||||
drains the only scheduling hop. `flush()` yields to the loop once before
|
||||
draining: since Python 3.13 an awaited executor future can complete without a
|
||||
loop iteration, so a hop queued by a worker that already returned may still be
|
||||
pending when flush starts.
|
||||
The persisted projection accepts
|
||||
only framework-defined error/action values and strict booleans (using null for
|
||||
invalid values) from the producer-supplied tool stamp; tool content, args,
|
||||
|
||||
@ -1352,6 +1352,15 @@ class RunJournal(BaseCallbackHandler):
|
||||
|
||||
async def flush(self) -> None:
|
||||
"""Force flush remaining buffer. Called in worker's finally block."""
|
||||
if self._closed:
|
||||
return
|
||||
# Events recorded from worker threads reach this loop through
|
||||
# call_soon_threadsafe (see record_middleware). Since Python 3.13 an
|
||||
# awaited run_in_executor()/to_thread() may complete without yielding
|
||||
# to the loop when the worker finished before its future was chained,
|
||||
# so such callbacks can still be queued when flush() starts. Yield once
|
||||
# so they land in the buffer instead of being dropped after detach.
|
||||
await asyncio.sleep(0)
|
||||
if self._closed:
|
||||
return
|
||||
self._explicit_flush_in_progress = True
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user