perf: bound concurrent image generation
This commit is contained in:
parent
ebf7b8baa1
commit
885fa9b065
@ -25,6 +25,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
Browser localStorage is no longer used as a credential store.
|
||||
- **Image Job Deduplication.** Consistent portrait/reference generation accepts an `Idempotency-Key` and returns the
|
||||
existing job for repeated submissions; completed job mappings are evicted with the job cache.
|
||||
- **Image Concurrency Bound.** Consistent image generation is limited to two active GPU jobs; additional requests
|
||||
remain observable as queued jobs and fail clearly if no slot becomes available within 15 minutes.
|
||||
- **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
@ -120,7 +120,8 @@ the first block before investing in architecture or polish.
|
||||
- [ ] Give every job an ID, progress, cancellation, timeout, retry, and idempotency key.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
- [ ] Use bounded server queues.
|
||||
|
||||
## P0 — Security
|
||||
|
||||
@ -61,6 +61,7 @@ _attribution_llm_lock = threading.Lock()
|
||||
_consistent_image_jobs: dict[str, dict] = {}
|
||||
_consistent_image_jobs_lock = threading.Lock()
|
||||
_consistent_image_idempotency: dict[str, str] = {}
|
||||
_consistent_image_slots = threading.BoundedSemaphore(2)
|
||||
|
||||
|
||||
def _cij_new(job_id: str, name: str) -> None:
|
||||
@ -3085,6 +3086,11 @@ async def character_generate_consistent_images(request: Request):
|
||||
_consistent_image_idempotency[idempotency_key] = job_id
|
||||
|
||||
def _worker() -> None:
|
||||
acquired = _consistent_image_slots.acquire(timeout=900)
|
||||
if not acquired:
|
||||
_cij_set(job_id, stage="error", detail="Image queue timed out", done=True,
|
||||
error="No image-generation slot became available within 15 minutes")
|
||||
return
|
||||
try:
|
||||
identity_image = identity_image_input
|
||||
if identity_image.startswith("/"):
|
||||
@ -3121,6 +3127,8 @@ async def character_generate_consistent_images(request: Request):
|
||||
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc.detail))
|
||||
except Exception as exc:
|
||||
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc))
|
||||
finally:
|
||||
_consistent_image_slots.release()
|
||||
|
||||
threading.Thread(target=_worker, daemon=True, name=f"consistent-images-{job_id}").start()
|
||||
return {"job_id": job_id}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user