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.
|
Browser localStorage is no longer used as a credential store.
|
||||||
- **Image Job Deduplication.** Consistent portrait/reference generation accepts an `Idempotency-Key` and returns the
|
- **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.
|
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
|
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||||
rejection and oversized JSON request rejection.
|
rejection and oversized JSON request rejection.
|
||||||
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
- **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.
|
- [ ] Give every job an ID, progress, cancellation, timeout, retry, and idempotency key.
|
||||||
- [ ] Prevent duplicate generation when a user clicks twice.
|
- [ ] Prevent duplicate generation when a user clicks twice.
|
||||||
- [ ] Centralize provider health checks and error handling.
|
- [ ] 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.
|
- [ ] Use bounded server queues.
|
||||||
|
|
||||||
## P0 — Security
|
## P0 — Security
|
||||||
|
|||||||
@ -61,6 +61,7 @@ _attribution_llm_lock = threading.Lock()
|
|||||||
_consistent_image_jobs: dict[str, dict] = {}
|
_consistent_image_jobs: dict[str, dict] = {}
|
||||||
_consistent_image_jobs_lock = threading.Lock()
|
_consistent_image_jobs_lock = threading.Lock()
|
||||||
_consistent_image_idempotency: dict[str, str] = {}
|
_consistent_image_idempotency: dict[str, str] = {}
|
||||||
|
_consistent_image_slots = threading.BoundedSemaphore(2)
|
||||||
|
|
||||||
|
|
||||||
def _cij_new(job_id: str, name: str) -> None:
|
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
|
_consistent_image_idempotency[idempotency_key] = job_id
|
||||||
|
|
||||||
def _worker() -> None:
|
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:
|
try:
|
||||||
identity_image = identity_image_input
|
identity_image = identity_image_input
|
||||||
if identity_image.startswith("/"):
|
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))
|
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(exc.detail))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_cij_set(job_id, stage="error", detail="Failed", done=True, error=str(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()
|
threading.Thread(target=_worker, daemon=True, name=f"consistent-images-{job_id}").start()
|
||||||
return {"job_id": job_id}
|
return {"job_id": job_id}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user