fix(async): chiamate bloccanti in asyncio.to_thread (#36 punto 1)

kaleido (pio.to_image), WeasyPrint (write_pdf), pdfplumber (extract_text_from_pdf)
e la scrittura del CSV di produzione giravano sincrone dentro handler async:
un PDF pesante o un run grande fermava il worker per tutte le altre richieste.
Ora avvolte in await asyncio.to_thread(...) in report_service.py,
ai_service.py e production_export_service.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:38:28 +02:00
parent 78970c69b0
commit 3f70f99a40
3 changed files with 27 additions and 9 deletions
+17 -7
View File
@@ -1,4 +1,5 @@
"""Report generation service — PDF reports via Jinja2 + Plotly/Kaleido + WeasyPrint."""
import asyncio
import base64
from datetime import datetime
from pathlib import Path
@@ -204,6 +205,11 @@ def _generate_control_chart_svg(chart_data, subtask_info: dict | None) -> str:
return svg_bytes.decode("utf-8")
def _render_pdf(html_content: str) -> bytes:
"""Blocking WeasyPrint render — always call via asyncio.to_thread."""
return HTML(string=html_content).write_pdf()
def _generate_histogram_svg(hist_data, subtask_info: dict | None) -> str:
"""Generate histogram SVG using Plotly + Kaleido."""
if not hist_data.bins:
@@ -300,9 +306,13 @@ async def generate_spc_report(
)
histogram = compute_histogram(values)
# 3. Generate SVG charts
control_chart_svg = _generate_control_chart_svg(control_chart, subtask_info)
histogram_svg = _generate_histogram_svg(histogram, subtask_info)
# 3. Generate SVG charts (kaleido shells out and blocks: off the event loop)
control_chart_svg = await asyncio.to_thread(
_generate_control_chart_svg, control_chart, subtask_info
)
histogram_svg = await asyncio.to_thread(
_generate_histogram_svg, histogram, subtask_info
)
# 4. Get company info and recipe info
company = await _get_company_info(db)
@@ -336,8 +346,8 @@ async def generate_spc_report(
n_measurements=len(measurements),
)
# 7. Convert HTML to PDF
pdf_bytes = HTML(string=html_content).write_pdf()
# 7. Convert HTML to PDF (WeasyPrint blocks: off the event loop)
pdf_bytes = await asyncio.to_thread(_render_pdf, html_content)
return pdf_bytes
@@ -424,6 +434,6 @@ async def generate_measurement_report(
n_measurements=len(measurements),
)
# 8. Convert to PDF
pdf_bytes = HTML(string=html_content).write_pdf()
# 8. Convert to PDF (WeasyPrint blocks: off the event loop)
pdf_bytes = await asyncio.to_thread(_render_pdf, html_content)
return pdf_bytes