diff --git a/app/assets/lifecycle.py b/app/assets/lifecycle.py index 3d51cdbb6..50468863e 100644 --- a/app/assets/lifecycle.py +++ b/app/assets/lifecycle.py @@ -124,6 +124,20 @@ def run_asset_startup() -> None: start_asset_seeder() +def run_startup(*, enable_assets: bool) -> None: + """Startup temp maintenance entry point, owning the enabled/disabled policy. + + Enabled: full asset startup (DB temp-row wipe -> filesystem sweep -> seeder), which + intentionally orders row deletion before filesystem deletion and skips the sweep when + the wipe fails. Disabled: filesystem sweep only (master parity -- master called + cleanup_temp() unconditionally); with assets off there are no asset rows to wipe. + """ + if enable_assets: + run_asset_startup() + else: + cleanup_temp_filesystem() + + def run_asset_shutdown_cleanup() -> None: with create_session() as session: wipe_temp_db_rows(session) diff --git a/main.py b/main.py index 11be6f2c2..3d6468a42 100644 --- a/main.py +++ b/main.py @@ -478,14 +478,13 @@ def hijack_progress(server_instance): def setup_database(): from app.assets import mode - from app.assets.lifecycle import init_db_and_state, run_asset_startup + from app.assets.lifecycle import init_db_and_state, run_startup try: if dependencies_available(): mode.init(args) init_db_and_state() - if args.enable_assets: - run_asset_startup() + run_startup(enable_assets=args.enable_assets) except Exception as e: if "database is locked" in str(e): logging.error( diff --git a/tests-unit/assets_test/services/test_lifecycle.py b/tests-unit/assets_test/services/test_lifecycle.py index cd37b6aa1..24bc5a861 100644 --- a/tests-unit/assets_test/services/test_lifecycle.py +++ b/tests-unit/assets_test/services/test_lifecycle.py @@ -15,6 +15,7 @@ from app.assets.lifecycle import ( get_excluded_scan_roots, run_asset_shutdown_cleanup, run_asset_startup, + run_startup, wipe_temp_db_rows, ) from app.assets.scanner import get_temp_prefixes, sync_temp_references_safely @@ -103,6 +104,41 @@ def test_db_wipe_failure_skips_rmtree_and_continues(mock_create_session): seeder_mock.assert_called_once() +def test_run_startup_disabled_sweeps_temp_filesystem_without_db_work(comfy_dirs): + """D9 — Given assets disabled and a stale temp file, When run_startup runs, Then the + filesystem sweep removes it and no DB-row wipe or seeder work happens (master parity: + master called cleanup_temp() unconditionally; with assets off there are no rows to wipe).""" + stale = comfy_dirs / "stale.png" + stale.write_bytes(b"\x00" * 10) + assert stale.exists() + + with ( + patch("app.assets.lifecycle.run_asset_startup") as asset_startup_mock, + patch("app.assets.lifecycle.wipe_temp_db_rows") as wipe_mock, + patch("app.assets.lifecycle.start_asset_seeder") as seeder_mock, + ): + run_startup(enable_assets=False) + + assert not stale.exists() + asset_startup_mock.assert_not_called() + wipe_mock.assert_not_called() + seeder_mock.assert_not_called() + + +def test_run_startup_enabled_delegates_to_asset_startup_not_bare_sweep(): + """D9 — Given assets enabled, When run_startup runs, Then it delegates to the ordered + run_asset_startup (sole owner of DB-row-before-filesystem deletion) and never calls the + bare filesystem sweep directly, so S12's enabled-path ordering is preserved.""" + with ( + patch("app.assets.lifecycle.run_asset_startup") as asset_startup_mock, + patch("app.assets.lifecycle.cleanup_temp_filesystem") as cleanup_mock, + ): + run_startup(enable_assets=True) + + asset_startup_mock.assert_called_once_with() + cleanup_mock.assert_not_called() + + def test_rmtree_failure_excludes_temp_from_scan(session, comfy_dirs, mock_create_session): record_id, content_id = _seed_temp_rows(session, comfy_dirs)