Browse Source

Fix queue empty after restart by checkpointing WAL on shutdown (#523)

The SQLite WAL was never checkpointed during container shutdown, leaving
uncommitted data (including schema migrations) in the -wal file. On
restart, partial WAL recovery could cause inconsistent query results,
making the queue appear empty until a filter was applied. Add
PRAGMA wal_checkpoint(TRUNCATE) and engine.dispose() to the lifespan
shutdown handler.
maziggy 2 months ago
parent
commit
2c8fbf3e33
2 changed files with 12 additions and 2 deletions
  1. 1 0
      CHANGELOG.md
  2. 11 2
      backend/app/main.py

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.1] - Unreleased
 
 ### Fixed
+- **Queue Empty After Container Restart Due to Uncheckpointed WAL** ([#523](https://github.com/maziggy/bambuddy/issues/523)) — The print queue appeared empty after a Docker container restart until a filter was applied. SQLite WAL mode keeps uncommitted data in a separate `-wal` file, but the shutdown handler never checkpointed the WAL back into the main database or disposed of engine connections. If the container was stopped or crashed, the WAL could contain partial schema migrations or uncommitted data, causing inconsistent query results on restart. Deleting the `-wal` and `-shm` files was the only workaround. Now runs `PRAGMA wal_checkpoint(TRUNCATE)` and disposes the engine on shutdown, ensuring all data is flushed to the main database file before exit.
 - **Virtual Printer Queue Sends Wrong Plate ID and Ignores AMS Mapping** ([#529](https://github.com/maziggy/bambuddy/issues/529)) — Files sent to a virtual printer in queue mode had two issues. First, `plate_id` was always `1`, generating the wrong MQTT gcode path for multi-plate 3MF files (HMS error 0500_4003). Now extracts the plate index from the 3MF's `slice_info.config`. Second, `ams_mapping` was never computed for printer-specific queue items (VP assigned to a particular printer), so the printer always used the first AMS slot regardless of which filament the 3MF required. The scheduler now computes AMS mapping for all queue items that lack one, not just model-based assignments.
 - **Unnecessary Target Model Selector on "Any" Tab** ([#528](https://github.com/maziggy/bambuddy/issues/528)) — When scheduling a print to "Any {model}", a redundant "Target Model" dropdown appeared even though the G-code is already sliced for a specific printer model. Changing the target model would lead to print failures. The dropdown is now hidden when the sliced model is known (the tab label already shows "Any {model}"). It still appears as a fallback for legacy files without model metadata.
 - **"Clear Plate & Start Next" Button Shown on Printers Without Correct Filament** ([#527](https://github.com/maziggy/bambuddy/issues/527)) — When a print job was queued for "any printer" of a model (e.g., "any H2S"), the "Clear Plate & Start Next" button appeared on ALL printers of that model, including those without the required filament loaded. Clicking it on a printer without the right filament would start a print that fails. The `PrinterQueueWidget` now filters queue items by filament compatibility — it checks the printer's loaded filament types (from AMS and external spools) against the queue item's `required_filament_types` and only shows items the printer can actually print. If no compatible items exist, the widget is hidden.

+ 11 - 2
backend/app/main.py

@@ -8,7 +8,7 @@ from logging.handlers import RotatingFileHandler
 from fastapi import FastAPI
 from fastapi.responses import FileResponse
 from fastapi.staticfiles import StaticFiles
-from sqlalchemy import delete, or_, select
+from sqlalchemy import delete, or_, select, text
 
 from backend.app.api.routes import (
     ams_history,
@@ -52,7 +52,7 @@ from backend.app.api.routes import (
 from backend.app.api.routes.maintenance import _get_printer_maintenance_internal, ensure_default_types
 from backend.app.api.routes.support import init_debug_logging
 from backend.app.core.config import APP_VERSION, settings as app_settings
-from backend.app.core.database import async_session, init_db
+from backend.app.core.database import async_session, engine, init_db
 from backend.app.core.websocket import ws_manager
 from backend.app.models.smart_plug import SmartPlug
 from backend.app.services.archive import ArchiveService
@@ -3354,6 +3354,15 @@ async def lifespan(app: FastAPI):
 
     await mqtt_relay.disconnect(timeout=2)
 
+    # Checkpoint WAL and close all database connections
+    try:
+        async with engine.begin() as conn:
+            await conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)"))
+        logging.info("WAL checkpoint completed")
+    except Exception as e:
+        logging.warning("WAL checkpoint failed: %s", e)
+    await engine.dispose()
+
 
 app = FastAPI(
     title=app_settings.app_name,