perf: bound preview tts concurrency
This commit is contained in:
parent
742b43d860
commit
c1ad0cdc46
@ -32,6 +32,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
- **Docker Socket Isolation.** Compose deployments now place the host Docker socket behind a dedicated network proxy;
|
||||
the web container no longer mounts the socket or joins the host Docker group. The client retains direct Unix-socket
|
||||
compatibility for deployments that intentionally configure it.
|
||||
- **TTS Queue Bound.** Preview, style-variation, and seed-finder synthesis now share a two-slot process-local queue;
|
||||
excess requests receive a retryable 429 after a bounded wait instead of creating unbounded worker threads.
|
||||
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||
rejection and oversized JSON request rejection.
|
||||
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
||||
|
||||
3
TODO.md
3
TODO.md
@ -121,7 +121,8 @@ the first block before investing in architecture or polish.
|
||||
- [ ] Prevent duplicate generation when a user clicks twice.
|
||||
- [ ] Centralize provider health checks and error handling.
|
||||
- [ ] Limit concurrency to 1–2 image generations, 1–2 voice generations, and 1 STT request per user. Consistent
|
||||
portrait/reference rendering is now capped at two active jobs; extend the cap to voice and STT paths.
|
||||
portrait/reference rendering and preview/style/seed-finder synthesis are capped at two active jobs; extend the
|
||||
cap to full speech-proxy and STT paths.
|
||||
- [ ] Use bounded server queues.
|
||||
|
||||
## P0 — Security
|
||||
|
||||
@ -54,6 +54,19 @@ logger = logging.getLogger("uvicorn.error")
|
||||
|
||||
router = APIRouter()
|
||||
_SEED_FINDER_VOICE_LOCK = threading.Lock()
|
||||
_TTS_GENERATION_SLOTS = asyncio.Semaphore(2)
|
||||
|
||||
|
||||
async def _bounded_tts_call(fn, *args):
|
||||
"""Run one blocking synthesis through the process-local voice queue."""
|
||||
try:
|
||||
await asyncio.wait_for(_TTS_GENERATION_SLOTS.acquire(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
raise HTTPException(429, "TTS generation queue is full; retry shortly")
|
||||
try:
|
||||
return await asyncio.to_thread(fn, *args)
|
||||
finally:
|
||||
_TTS_GENERATION_SLOTS.release()
|
||||
|
||||
# ── TTS backend helpers ───────────────────────────────────────────────────────
|
||||
|
||||
@ -678,11 +691,11 @@ async def tts_preview(request: Request):
|
||||
|
||||
try:
|
||||
if backend == "voice_clone" and data.get("seed_finder") and "seed" in _overrides:
|
||||
audio, media_type = await asyncio.to_thread(
|
||||
audio, media_type = await _bounded_tts_call(
|
||||
_seed_finder_request_audio, text, voice, settings, int(_overrides["seed"])
|
||||
)
|
||||
else:
|
||||
audio, media_type = await asyncio.to_thread(_preview_request_audio, text, voice, settings, instruct, backend)
|
||||
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, voice, settings, instruct, backend)
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"TTS error: {e}")
|
||||
audio, media_type, ext, duration_sec, clipped = _prepare_proxy_audio(audio, media_type, response_format)
|
||||
@ -718,7 +731,7 @@ async def tts_style_variation(request: Request):
|
||||
|
||||
settings = _load_settings()
|
||||
try:
|
||||
audio, media_type = await asyncio.to_thread(_preview_request_audio, text, source_voice, settings, instruct, backend)
|
||||
audio, media_type = await _bounded_tts_call(_preview_request_audio, text, source_voice, settings, instruct, backend)
|
||||
audio, media_type, _ext, duration, _clipped = _prepare_proxy_audio(audio, media_type, "wav")
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"Style variation synthesis failed: {e}")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user