Context
From the post-merge review of #248 (FASTA prep backend).
The frontend "cancel" action only closes the EventSource and aborts the bundle fetch (fasta-prep-client.ts / runtime.ts). The server-side background pipeline task keeps running to completion: embedding/projection compute is wasted and a max_concurrent_jobs slot stays held until the job finishes and the bundle is later swept.
Impact
Medium under load — abandoned jobs occupy concurrency slots and run network/CPU work nobody will collect.
Suggested fix
The /events handler in api.py already detects await request.is_disconnected(). On disconnect (and/or a dedicated DELETE /api/prepare/{job_id}), cancel JobRegistry._tasks[job_id]. _run already handles asyncio.CancelledError (sets ERROR, emits an error event) and _run_step kills the subprocess on cancel — so the plumbing is mostly there; it just needs to be triggered.
Refs
services/protspace-prep/src/protspace_prep/api.py (events)
services/protspace-prep/src/protspace_prep/jobs.py (_run, _tasks)
Context
From the post-merge review of #248 (FASTA prep backend).
The frontend "cancel" action only closes the
EventSourceand aborts the bundle fetch (fasta-prep-client.ts/runtime.ts). The server-side background pipeline task keeps running to completion: embedding/projection compute is wasted and amax_concurrent_jobsslot stays held until the job finishes and the bundle is later swept.Impact
Medium under load — abandoned jobs occupy concurrency slots and run network/CPU work nobody will collect.
Suggested fix
The
/eventshandler inapi.pyalready detectsawait request.is_disconnected(). On disconnect (and/or a dedicatedDELETE /api/prepare/{job_id}), cancelJobRegistry._tasks[job_id]._runalready handlesasyncio.CancelledError(sets ERROR, emits an error event) and_run_stepkills the subprocess on cancel — so the plumbing is mostly there; it just needs to be triggered.Refs
services/protspace-prep/src/protspace_prep/api.py(events)services/protspace-prep/src/protspace_prep/jobs.py(_run,_tasks)