Skip to content

perf: fix_build_errors repairs files serially and blocks the event loop on file I/O #1335

Description

@groupthinking

Problem

AICodeGenerator.fix_build_errors repairs each failing file in a strictly
sequential for loop. For every file it performs, on the event loop thread:

  1. a blocking Path.exists() + Path.read_text()
  2. an await self.router.generate(...) LLM round-trip
  3. a blocking Path.write_text()

Two independent costs follow:

Wall clock scales linearly with the number of broken files. The files are
mutually independent — each gets its own prompt, its own completion and its own
write. Nothing in the loop reads state produced by a previous iteration, so the
serialisation is incidental rather than required. With an LLM round-trip
dominating each iteration, 8 broken files cost ~8× one file.

The event loop is blocked during file I/O. read_text/write_text are
synchronous syscalls executed directly in a coroutine, so every other task on
the loop stalls for their duration.

Production impact

DeploymentManager.verify_and_fix_project
(src/youtube_extension/backend/deployment_manager.py:343) calls
fix_build_errors from inside a retry loop (max_retries, declared at line
301). The serial cost is therefore multiplied by the retry count on every
deployment that fails to build.

Proposed fix

  • Move read_text / write_text off the loop with asyncio.to_thread.
  • Fan the per-file work out with asyncio.gather, bounded by an
    asyncio.Semaphore so provider rate limits are respected.
  • Keep the bound conservative and configurable via a new optional
    max_concurrency parameter, defaulting to a module constant.
  • Preserve today's failure semantics exactly: one file failing (missing,
    unreadable, or provider error) must not abort its siblings.
  • Sort the returned fixed_files so the result is deterministic
    (error_files is a set, so completion order is not stable).

Acceptance criteria

  • More than one fix is in flight simultaneously
  • Concurrency never exceeds the configured bound
  • Non-positive max_concurrency clamps to 1 rather than falling back to the default
  • read_text / write_text execute off the event loop thread
  • A single file's failure leaves its siblings' fixes intact
  • fixed_files is deterministically ordered

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions