Skip to content

Upload page: resumable/parallel uploads, per-session cancel, disk-backed jobs#71

Merged
aperson30 merged 4 commits into
mainfrom
feature/upload-resumable-cancel
Jul 15, 2026
Merged

Upload page: resumable/parallel uploads, per-session cancel, disk-backed jobs#71
aperson30 merged 4 commits into
mainfrom
feature/upload-resumable-cancel

Conversation

@aperson30

@aperson30 aperson30 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

What & why

Upgrades the existing inference-upload flow (which recently gained server-side queueing + a global kill-all cancel) with resumable/parallel uploads and per-session cancellation. Nothing is a parallel system — this improves the code already on main.

Frontend (UploadPage)

  • Resumable uploads — the picked file + a chunk cursor are stashed in IndexedDB (pendingUploads.ts), so closing the tab mid-upload resumes on reopen instead of dying. Best-effort: falls back to a beforeunload warning if IDB is unavailable.
  • Parallel runs with independent per-session polling. A new Active section shows each in-flight run with a phase label (Uploading / Queued for GPU / Running) and a per-card Cancel button.
  • Delete button on Recent Uploads and a terminal Cancelled status (amber, non-clickable).

Backend

  • Per-session cancel_tracked_run / bind_session / cancel_session replace the global single-process tracking. POST /api/cancel-inference/<sid> SIGTERMs (then SIGKILLs) exactly one session's process group; other users' jobs keep running. The global POST /api/cancel-inference kill-all is kept and re-implemented on the same registry.
  • Disk-backed jobsinference_jobs is mirrored to tmp/<sid>/job.json (atomic write) and read via _get_inference_job, so status/viewer endpoints survive a gunicorn restart. A job left running/queued on disk by a dead process is coerced to failed (no phantom infinite poll).
  • Lifecycle queued → running → completed/failed/cancelled — an on_start callback flips queued → running at GPU acquisition and aborts a run cancelled while still queued.
  • Renamed the per-session cancel view to cancel_inference_session to avoid a duplicate Flask endpoint name with the existing global cancel view (would have prevented app startup).

Notes

  • Model-info icons intentionally omitted in favour of the existing one-line descriptions.
  • InferenceUpload.tsx (an orphaned component, imported nowhere) is left untouched.

Testing

  • tsc -b clean · 101/101 Vitest tests pass · production build succeeds.
  • Backend boots; verified /api/inference-status/<sid> → 404 not_found, per-session /api/cancel-inference/<sid> → 404 for unknown, global /api/cancel-inference → 200.

…ked jobs

Frontend (UploadPage):
- Resumable uploads: the picked file is stashed in IndexedDB with a chunk
  cursor, so a tab close mid-upload resumes on reopen instead of dying.
- Parallel runs with independent per-session polling; an "Active" section
  shows each in-flight run with a phase label (Uploading / Queued for GPU /
  Running) and a per-card Cancel button.
- Testing model: a client-side 20s dry run for local dev without a GPU.
- Delete button and a terminal "Cancelled" status on Recent Uploads.

Backend:
- Per-session subprocess tracking (_tracked_run / bind_session /
  cancel_session) replaces the global single-process tracking, so
  /api/cancel-inference/<sid> kills exactly one session's process group and
  leaves other users' jobs running. The global /cancel-inference kill-all is
  kept and re-implemented on the same registry.
- inference_jobs is mirrored to tmp/<sid>/job.json (atomic write) and read
  back through _get_inference_job, so status/viewer endpoints survive a
  gunicorn restart; a job left "running"/"queued" on disk by a dead process
  is coerced to "failed" instead of polling forever.
- Job lifecycle is queued -> running -> completed/failed/cancelled; an
  on_start callback flips queued->running at GPU acquisition and aborts a
  run cancelled while still queued.

Renamed the per-session cancel view to cancel_inference_session to avoid a
duplicate Flask endpoint name with the existing global cancel view.
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
Comment thread flask-server/api/api_blueprint.py Fixed
It was dev scaffolding for exercising the flow without a GPU; not meant to
ship. Removes the Testing option, its localStorage timer state, and the
resume/cancel branches that special-cased it.
…e exposure

- Route job.json paths through secure_filename (the CodeQL-recognised
  path-injection barrier per path_safety.py); session_id reaches the
  filesystem in _job_meta_path/_persist_inference_job. Real ids are UUIDs
  and pass through unchanged.
- Per-session cancel endpoint no longer returns str(e) to the client;
  logs the detail server-side and returns a generic message.
@aperson30 aperson30 merged commit 46059e1 into main Jul 15, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants