mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
fix(shutdown): exec uvicorn as PID 1 in Docker, and bound the graceful-shutdown wait
Two defects, both invisible until you ask the app to stop. Docker never shut down gracefully at all. CMD ["sh","-c","uvicorn ..."] left the shell as PID 1 with uvicorn as its child, and dash does not forward signals, so docker stop SIGTERMed the shell and uvicorn never heard about it. Measured on the shipped image: the full 10s grace period, exit 137, and no "Shutting down" line in the log. Every stop, restart and image update was a hard kill -- no WAL checkpoint, no MQTT disconnect, no virtual-printer teardown. `exec` makes uvicorn PID 1; the rebuilt image now stops in 1s with exit 0 and checkpoints the WAL. Separately, uvicorn's timeout_graceful_shutdown defaults to None -- wait forever for in-flight requests. An MJPEG camera stream is a response that never completes (httptools' connection shutdown() only flips keep_alive on an in-flight cycle, it never closes the transport), so one open camera tile pinned the process until systemd SIGKILLed at 90s. The ordering makes it unfixable from inside the app: uvicorn fires the lifespan shutdown -- the code that tears the streams down -- only after connections drain. All six launchers now pass --timeout-graceful-shutdown 5: Dockerfile, deploy/bambuddy.service, the systemd unit and launchd plist from install/install.sh, the SpoolBuddy installer's unit, and the Windows NSSM registration. On timeout uvicorn cancels the request tasks; the camera generators already unwind cleanly on CancelledError. TimeoutStopSec raised to 30s on the units and stop_grace_period: 30s added to compose, as backstops rather than the mechanism. On Windows NSSM's default 1500ms AppStopMethodConsole was force-killing uvicorn mid-teardown; raised to 15s, with the WM_CLOSE and thread-message stages skipped (uvicorn is a console app with neither a window nor a message loop).
This commit is contained in:
@@ -30,7 +30,11 @@ REM "service not found" returns non-zero and we want to proceed.
|
||||
REM Register the service. NSSM wraps uvicorn so Windows treats it as a
|
||||
REM proper service (autostart, recovery, supervised restart).
|
||||
REM --loop asyncio required: uvloop can truncate VP FTP uploads (#1896).
|
||||
"%NSSM%" install Bambuddy "%PYTHON%" "-m uvicorn backend.app.main:app --host 0.0.0.0 --port %PORT% --loop asyncio"
|
||||
REM --timeout-graceful-shutdown required: uvicorn otherwise waits forever for
|
||||
REM in-flight requests, and an MJPEG camera stream is a response that never
|
||||
REM completes — one open camera tile hangs the stop until NSSM force-kills,
|
||||
REM skipping the WAL checkpoint and the MQTT / virtual-printer teardown.
|
||||
"%NSSM%" install Bambuddy "%PYTHON%" "-m uvicorn backend.app.main:app --host 0.0.0.0 --port %PORT% --loop asyncio --timeout-graceful-shutdown 5"
|
||||
if errorlevel 1 (
|
||||
echo [install-service] nssm install failed
|
||||
exit /b 1
|
||||
@@ -42,6 +46,19 @@ REM Service configuration
|
||||
"%NSSM%" set Bambuddy Description "Bambuddy — local-first Bambu Lab printer manager"
|
||||
"%NSSM%" set Bambuddy Start SERVICE_AUTO_START
|
||||
|
||||
REM Shutdown behaviour. NSSM's stop sequence is Ctrl-C, then WM_CLOSE, then a
|
||||
REM thread message, then TerminateProcess — each with a 1500 ms default wait.
|
||||
REM Uvicorn shuts down on the Ctrl-C, but needs longer than 1.5 seconds to
|
||||
REM finish: it drains in-flight requests (bounded at 5s by the flag above) and
|
||||
REM then runs the app teardown — WAL checkpoint, MQTT disconnect, virtual-
|
||||
REM printer stop. At the default timeout Windows force-killed it mid-teardown.
|
||||
REM
|
||||
REM Skip=6 drops the WM_CLOSE (2) and thread-message (4) methods: uvicorn is a
|
||||
REM console app with no window and no message loop, so both were only burning
|
||||
REM another 3 seconds before the kill. Ctrl-C is the one that works.
|
||||
"%NSSM%" set Bambuddy AppStopMethodSkip 6
|
||||
"%NSSM%" set Bambuddy AppStopMethodConsole 15000
|
||||
|
||||
REM Environment: point DATA_DIR + LOG_DIR at ProgramData, prepend our
|
||||
REM bin/ to PATH so ffmpeg/ffprobe are found by the shutil.which() lookup
|
||||
REM in backend/app/services/layer_timelapse.py.
|
||||
|
||||
Reference in New Issue
Block a user