⚡ Bolt: Offload synchronous API operations to worker threads#340
⚡ Bolt: Offload synchronous API operations to worker threads#340anchapin wants to merge 2 commits into
Conversation
- Imported `anyio` and `functools.partial` in `api/main.py`. - Wrapped heavy blocking operations (`generator.generate`, `output_pdf.read_bytes`, `yaml.dump`, `converter.json_resume_to_yaml`, `generator.tailor_data`, `generator.generate_report`, and `generator._compile_pdf`) using `anyio.to_thread.run_sync`. - This ensures the FastAPI event loop is not starved by synchronous IO and PDF compilation processes. Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's GuideOffloads blocking PDF generation, file I/O, and heavy synchronous processing in FastAPI routes to worker threads using anyio.to_thread.run_sync and functools.partial, and documents the pattern in the Bolt guide. Sequence diagram for offloading PDF rendering to worker threadssequenceDiagram
actor Client
participant FastAPI_render_pdf as FastAPI_render_pdf
participant anyio_to_thread as anyio_to_thread_run_sync
participant TemplateGenerator as TemplateGenerator_generate
participant FileSystem
Client->>FastAPI_render_pdf: render_pdf(request)
FastAPI_render_pdf->>anyio_to_thread: run_sync(partial(generator.generate,...))
anyio_to_thread->>TemplateGenerator: generate(variant, output_format, output_path)
TemplateGenerator->>FileSystem: write_pdf(output_path)
FileSystem-->>TemplateGenerator: pdf_written
FastAPI_render_pdf->>anyio_to_thread: run_sync(output_pdf.exists)
anyio_to_thread-->>FastAPI_render_pdf: exists()
FastAPI_render_pdf->>anyio_to_thread: run_sync(output_pdf.read_bytes)
anyio_to_thread->>FileSystem: read_bytes(output_path)
FileSystem-->>anyio_to_thread: pdf_bytes
anyio_to_thread-->>FastAPI_render_pdf: pdf_bytes
FastAPI_render_pdf-->>Client: Response(content=pdf_bytes)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- Several places (e.g.,
render_pdf,render_resume_pdf) now callanyio.to_thread.run_syncmultiple times in a row on the same path (exists,read_bytes); consider wrapping the combined logic in a single helper function so it runs in one thread invocation and avoids repeated context switching overhead. - The ad-hoc inline helpers like
_read_pdfand_write_yamlare duplicated patterns for file I/O; extracting shared, well-named utility functions for common read/write operations would make the threading strategy clearer and reduce repetition.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Several places (e.g., `render_pdf`, `render_resume_pdf`) now call `anyio.to_thread.run_sync` multiple times in a row on the same path (`exists`, `read_bytes`); consider wrapping the combined logic in a single helper function so it runs in one thread invocation and avoids repeated context switching overhead.
- The ad-hoc inline helpers like `_read_pdf` and `_write_yaml` are duplicated patterns for file I/O; extracting shared, well-named utility functions for common read/write operations would make the threading strategy clearer and reduce repetition.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Converted `async def` routes to `def` in `api/main.py`. - This ensures FastAPI automatically runs these routes in an external threadpool, preventing the `asyncio` event loop from being starved by synchronous I/O and PDF compilation processes. - This change maintains complete readability without needing manual `anyio.to_thread` wrappers. Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com>
💡 What: We wrapped heavy blocking synchronous operations (such as file reading/writing, PDF compilation, and heavy string processing) within the FastAPI route handlers (
api/main.py) usinganyio.to_thread.run_syncandfunctools.partial.🎯 Why: FastAPI uses an
asyncioevent loop. Running long blocking, synchronous functions insideasync defroutes stalls the event loop, causing request starvation and severely limiting the concurrency and throughput of the API server. Offloading them to a worker thread pool keeps the main loop responsive.📊 Impact: Eliminates main thread blocking during I/O and PDF compilations, dramatically increasing server concurrency and throughput under load.
🔬 Measurement: The test suite passes fully, and the endpoints continue to behave as expected. You can verify the behavior by sending concurrent requests to the
/v1/render/pdfor/v1/cover-letterendpoints and noting the improved handling compared to sequential blocking processing.PR created automatically by Jules for task 2885066603634316720 started by @anchapin
Summary by Sourcery
Offload blocking file I/O, PDF generation, and heavy synchronous processing in FastAPI endpoints to background worker threads to prevent event loop starvation and improve concurrency.
Enhancements:
anyio.to_thread.run_syncto run them in worker threads..jules/bolt.md.