Skip to content

perf: project scaffolding disk writes block the event loop in ProjectCodeGenerator #1250

Description

@groupthinking

Problem

ProjectCodeGenerator performs all of its scaffolding disk I/O with blocking primitives
inside async def bodies. Every one of these calls parks the entire event loop for its
duration — not just the calling request.

Site File Blocking calls What runs on the loop
generate_project code_generator.py:153 tempfile.mkdtemp directory creation
_generate_react_project code_generator.py:196-289 14 Path.mkdir, 6× open+write, 1× json.dump
_generate_vanilla_js_project code_generator.py:292-343 8 open+write
_generate_python_api code_generator.py:356-390 6 open+write

Measured payload is small: 6 files / 5,793 B (React), 4 files / 6,888 B (vanilla),
3 files / 2,776 B (FastAPI). On a warm local SSD the whole sequence completes in
sub-millisecond time.

No throughput improvement is claimed. The defect is tail latency and blast radius:

Reachability evidence

Verified at call level, not merely import level:

POST /api/v1/video-to-software        router.py:778   (@router.post, mounted in main.py)
  -> VideoProcessingService.process_video_to_software   video_processing_service.py:317
  -> code_generator.generate_project                    video_processing_service.py:380
       -> tempfile.mkdtemp                              code_generator.py:153
       -> _generate_web_project                         code_generator.py:184
            -> _generate_react_project                  code_generator.py:188
            -> _generate_vanilla_js_project             code_generator.py:192
       -> _generate_api_project -> _generate_python_api code_generator.py:350,354

This is the same HTTP entrypoint whose reachability was independently verified and
merged in #1240, so the chain above is already established in this repository.

Acceptance criteria

  • No blocking filesystem call executes on the event loop thread in
    generate_project, _generate_react_project, _generate_vanilla_js_project,
    or _generate_python_api.
  • Byte-for-byte identical output: same filenames, same contents, same directory
    layout, same return dictionaries.
  • Write ordering and failure semantics are preserved — a failure surfaces the same
    exception type to the same caller.
  • Regression tests prove off-loop execution by thread identity, not wall-clock
    timing.
  • ruff diagnostics unchanged versus main for every touched file.

Proposed fix

Keep content generation where it is — it is pure in-memory string building and is not
the problem. Collect the resulting (path, content) pairs into a plan, then perform the
whole batch inside a single asyncio.to_thread(...) hop per generator.

One hop per generator rather than one per file keeps the thread-pool cost at O(1) per
request instead of O(files), and preserves write ordering exactly because the batch is
applied sequentially inside the worker.

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