refactor(app_controller): add submit_io helper; migrate log_pruner ad-hoc threads
Phase 6 (partial) of startup_speedup_20260606 track. Added AppController.submit_io(fn, *args, **kwargs) as the public API for submitting fire-and-forget background work. Returns a concurrent.futures.Future for lifecycle tracking. The _io_pool is the shared 4-worker pool from src/io_pool.py. Migrated 2 ad-hoc threading.Thread spawns to use submit_io: - _manual_prune_logs() spawn: manual log pruning (cb) - _prune_old_logs() spawn: startup log pruning (startup) Both were threading.Thread(target=fn, daemon=True).start() calls. The spawn cost (~1-5ms per thread creation) is eliminated; both jobs now share the 4-worker _io_pool. REMAINING AD-HOC THREADS (documented in state.toml as follow-up): - app_controller.py: ~13 more threading.Thread() spawns (models fetch, project switch, fetch workers, post workers, MMA spawn workers, etc.) - gui_2.py: 2 spawns (stats worker, secondary worker) - api_hooks.py: 2 spawns (HookServer and WebSocketServer threads - these are domain-specific, NOT migrated per the spec exemption) - multi_agent_conductor.py: 1 spawn (WorkerPool - domain-specific) - performance_monitor.py: 1 spawn (CPU monitor - continuous sampling) The remaining ad-hoc thread migrations could be a follow-up sub-track. The architectural pattern is now established (submit_io); the migration of the remaining cases is mechanical and lower-risk. TESTS: - tests/test_log_pruner.py, test_log_pruning_heuristic.py, test_logging_e2e.py, test_app_controller_mcp.py, test_app_controller_offloading.py, test_app_controller_no_top_level_fastapi.py: 15/15 PASS
This commit is contained in:
+21
-4
@@ -2002,8 +2002,7 @@ class AppController:
|
|||||||
self.ai_status = f"Manual prune error: {e}"
|
self.ai_status = f"Manual prune error: {e}"
|
||||||
print(f"Error during manual log pruning: {e}")
|
print(f"Error during manual log pruning: {e}")
|
||||||
|
|
||||||
thread = threading.Thread(target=run_manual_prune, daemon=True)
|
self.submit_io(run_manual_prune)
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def _load_active_project(self) -> None:
|
def _load_active_project(self) -> None:
|
||||||
"""Loads the active project configuration, with fallbacks."""
|
"""Loads the active project configuration, with fallbacks."""
|
||||||
@@ -2052,8 +2051,7 @@ class AppController:
|
|||||||
pruner.prune()
|
pruner.prune()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during log pruning: {e}")
|
print(f"Error during log pruning: {e}")
|
||||||
thread = threading.Thread(target=run_prune, daemon=True)
|
self.submit_io(run_prune)
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def _fetch_models(self, provider: str) -> None:
|
def _fetch_models(self, provider: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -2152,6 +2150,25 @@ class AppController:
|
|||||||
"""
|
"""
|
||||||
self._warmup.on_complete(callback)
|
self._warmup.on_complete(callback)
|
||||||
|
|
||||||
|
def submit_io(self, fn: Callable, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
"""
|
||||||
|
Submit a background job to the shared _io_pool. Use this for any
|
||||||
|
fire-and-forget background work; avoids the per-spawn cost of a new
|
||||||
|
threading.Thread.
|
||||||
|
|
||||||
|
Returns a concurrent.futures.Future that can be used to track
|
||||||
|
completion, raise exceptions, or cancel the job. The pool is capped
|
||||||
|
at 4 workers (see src/io_pool.py) so the job may queue briefly if
|
||||||
|
the pool is saturated.
|
||||||
|
|
||||||
|
Domain-specific threads (HookServer, WebSocketServer, MMA WorkerPool,
|
||||||
|
asyncio loop) are NOT submitted here - they have their own lifecycle
|
||||||
|
management.
|
||||||
|
[SDM: src/app_controller.py:submit_io]
|
||||||
|
"""
|
||||||
|
import concurrent.futures
|
||||||
|
return self._io_pool.submit(fn, *args, **kwargs)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user