84d5c5c7

By: Michael Lynch <git@mtlynch.io>

Bound media work to request and shutdown lifecycles

Media conversion previously ran synchronously inside HTTP handlers without a single owner. Request cancellation stopped at the handler boundary, ffmpeg, ffprobe, S3, and the final SQLite insert used detached operations, and a timed-out shutdown could close SQLite while an upload was still finishing. That combination could waste CPU after a disconnect and race successful media work against a closed database.

This follows Litestream's lifecycle model in reference/litestream/store.go and reference/litestream/db.go: the component that starts background or expensive work owns its context, cancellation, concurrency bound, and wait group, and Close cancels and joins that work before releasing downstream resources. Litestream's replicate command also uses context-bound child processes and preserves an explicit final-sync phase during shutdown.

Add a bounded medialifecycle owner and run validation, conversion, variant writes, and the final media insert inside it. Thread context through upload and Tinybeans conversion APIs, local and S3 writes, ExecContext, and exec.CommandContext for ffmpeg and ffprobe. Shutdown now rejects and cancels media work, drains HTTP, joins admitted jobs, and closes SQLite only after both drains complete, preserving the clean child exit that gives Litestream its configured final-sync window.

The main implementation difficulty was that uploads and Tinybeans share converter and writer signatures, so context propagation required a complete in-tree API migration rather than compatibility wrappers. Pure Go image operations cannot be interrupted internally, so they check cancellation between bounded stages. If draining times out, the process deliberately avoids explicitly closing SQLite instead of racing active handlers; process exit releases the descriptor and still lets Litestream observe a normal child exit.

No external blockers were encountered. Focused tests, the full Go checks, and every flake target passed before the amendment.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 11s 3s 3s 15s 11s
Eval 51s 18s 18s 1m10s 36s
Build 1m09s 4m29s 8m32s 9m42s 6m41s
Suite 11s 4m51s 9m30s 9m42s 7m28s

Timeline

0s1m1m10s8m8m10s8m20s8m30s8m40s8m50s9m9m10s9m20s9m30s9m40s