▮ REFACTORED PATCH
# Fix 1: real bounded backpressure via asyncio.Queue; submit awaits a slot
import asyncio
class TTSJobPipeline:
def __init__(self, max_concurrent=4, queue_limit=100):
self.max_concurrent = max_concurrent
self.queue = asyncio.Queue(maxsize=queue_limit)
self.semaphore = asyncio.Semaphore(max_concurrent)
self._workers = set(); self._stopping = asyncio.Event()
async def submit(self, text, voice):
job = Job(id=..., text=text, voice=voice)
await self.queue.put(job) # BACKPRESSURE: await free slot, never raise
self._callback_event(job.id, JobEvent.QUEUED); return job.id
async def _worker(self):
while not self._stopping.is_set():
try: job = await asyncio.wait_for(self.queue.get(), timeout=0.5)
except asyncio.TimeoutError: continue
if job.cancelled:
self._callback_event(job.id, JobEvent.CANCELLED); self.queue.task_done(); continue
async with self.semaphore: # the ONLY concurrency gate
self._callback_event(job.id, JobEvent.STARTED)
try:
await self._process_with_retry(job)
self._callback_event(job.id, JobEvent.COMPLETED)
except Exception as e:
self._callback_event(job.id, JobEvent.FAILED, str(e))
finally: self.queue.task_done()
async def drain(self): await self.queue.join() # no busy-poll
async def aclose(self): # clean shutdown
self._stopping.set()
for w in self._workers: w.cancel()
await asyncio.gather(*self._workers, return_exceptions=True)
# Fix 2 (tests): backpressure should be observed as submit() BLOCKING under load,
# not raising. Over-submit and assert it waited, didn't raise.