Skip to content

fix(agents): offload UploadsMiddleware uploads scan off the event loop#3311

Merged
WillemJiang merged 2 commits into
bytedance:mainfrom
ShenAC-SAC:fix/blocking-io-detect
May 30, 2026
Merged

fix(agents): offload UploadsMiddleware uploads scan off the event loop#3311
WillemJiang merged 2 commits into
bytedance:mainfrom
ShenAC-SAC:fix/blocking-io-detect

Conversation

@ShenAC-SAC
Copy link
Copy Markdown
Collaborator

Problem

UploadsMiddleware defines only the synchronous before_agent hook. LangChain wires a sync-only hook as RunnableCallable(before_agent, None), and LangGraph's ainvoke runs it directly on the event loop when afunc is None. So before_agent's uploads-directory scan (exists / iterdir / stat + reading sibling .md outlines) blocks the asyncio event loop on every message that has an uploads directory.

Fix

Add abefore_agent that offloads the scan to a worker thread via run_in_executor. run_in_executor copies the current context, so the user_id contextvar read by get_effective_user_id() is preserved. The sync before_agent path (used when the graph runs synchronously) is unchanged.

Test

Add a runtime anchor under backend/tests/blocking_io/ that drives the real create_agent graph via ainvoke under the strict Blockbuster gate:

  • Without the fix it fails with BlockingError: Blocking call to os.stat inside before_agent (verified).
  • With the fix it passes.
  • make detect-blocking-io no longer reports the SYNC_AGENT_MIDDLEWARE_HOOK finding for this path.

Docs (backend/CLAUDE.md, backend/docs/BLOCKING_IO_DETECTION.md) updated to list the new anchor.

Closes #3310

UploadsMiddleware defines only the sync `before_agent` hook. LangChain wires a
sync-only hook as `RunnableCallable(before_agent, None)`, and LangGraph's
`ainvoke` runs it directly on the event loop when `afunc is None` — so the
per-message uploads-directory scan (`exists`/`iterdir`/`stat` plus reading
sibling `.md` outlines) blocks the asyncio event loop on every message that has
an uploads directory.

Add `abefore_agent` that offloads the scan to a worker thread via
`run_in_executor`; it copies the current context, preserving the `user_id`
contextvar read by `get_effective_user_id()`.

Add a runtime anchor under `tests/blocking_io/` that drives the real
`create_agent` graph via `ainvoke` under the strict Blockbuster gate, so a
regression back onto the event loop fails CI. Update blocking-IO docs.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an async abefore_agent hook to UploadsMiddleware that offloads its blocking uploads-directory scan to a worker thread via run_in_executor, preventing event-loop stalls under async graph execution. A blocking-IO runtime anchor and docs updates are included.

Changes:

  • Implement abefore_agent in UploadsMiddleware delegating to before_agent via run_in_executor (preserves context).
  • Add tests/blocking_io/test_uploads_middleware.py driving the real create_agent graph via ainvoke under the Blockbuster gate.
  • Update BLOCKING_IO_DETECTION.md and CLAUDE.md to document the new anchor.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py Adds async hook offloading sync before_agent via run_in_executor.
backend/tests/blocking_io/test_uploads_middleware.py New runtime anchor under Blockbuster gate using real create_agent graph.
backend/docs/BLOCKING_IO_DETECTION.md Documents UploadsMiddleware runtime coverage.
backend/CLAUDE.md Lists the new blocking-IO anchor alongside existing ones.

@WillemJiang
Copy link
Copy Markdown
Collaborator

@ShenAC-SAC Please fix the conflict with the main branch.

@WillemJiang WillemJiang added question Further information is requested reviewing The PR is in reviewing status labels May 30, 2026
Resolve conflicts in backend/CLAUDE.md and backend/docs/BLOCKING_IO_DETECTION.md
by keeping both runtime anchors: the JsonlRunEventStore async-IO anchor (bytedance#3084)
from main and the UploadsMiddleware uploads-scan anchor from this PR.
@ShenAC-SAC
Copy link
Copy Markdown
Collaborator Author

@WillemJiang Thanks for the heads-up — conflict resolved. I merged the latest main into this branch. The only conflicts were in backend/CLAUDE.md and backend/docs/BLOCKING_IO_DETECTION.md, where main's JsonlRunEventStore async-IO anchor (#3084) and this PR's UploadsMiddleware uploads-scan anchor landed in the same list; I kept both entries. The PR now merges cleanly. PTAL.

@WillemJiang WillemJiang merged commit 9f3be2a into bytedance:main May 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

question Further information is requested reviewing The PR is in reviewing status

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blocking IO: UploadsMiddleware.before_agent scans uploads dir on the event loop

3 participants