Refactor: Move module-level mutable flags to JailServiceState
TASK-004: Replace module-level mutable runtime flags in service layer with injected state holder, eliminating hidden global state and improving testability and synchronization boundaries. Changes: - Create JailServiceState dataclass in app/utils/runtime_state.py to hold backend capability cache and synchronization lock - Add JailServiceState as a field in RuntimeState (with default_factory) - Remove module-level _backend_cmd_supported and _backend_cmd_lock from jail_service.py - Refactor _check_backend_cmd_supported() to accept state parameter - Inject JailServiceState into list_jails() and _fetch_jail_summary() via parameters - Add get_jail_service_state() dependency provider in app/dependencies.py - Add JailServiceStateDep type alias for router injection - Update jails router to receive and pass state to service functions - Update all tests to use jail_service_state fixture and pass state to functions - Remove duplicate _MAX_PAGE_SIZE constant definition - Document mutable state management in Backend-Development.md - Update Architecture.md to describe JailServiceState and state nesting pattern Benefits: - Eliminates global mutable state and associated race conditions - Makes state visible to callers (not hidden in module scope) - Enables test isolation (each test gets fresh state) - Prepares codebase for multi-worker deployments (state can be extracted to shared backend) - Synchronization boundaries are now explicit (state.get_backend_cmd_lock()) Compliance: - All tests pass (17 passed in TestListJails, TestGetJail, TestLockInitialization) - No ruff linting errors - Type-safe: JailServiceState properly typed with asyncio.Lock, bool | None Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -34,7 +34,7 @@ from app.services.geo_cache import GeoCache
|
||||
from app.services.protocols import Fail2BanMetadataService
|
||||
from app.utils.constants import SESSION_COOKIE_NAME
|
||||
from app.utils.rate_limiter import RateLimiter
|
||||
from app.utils.runtime_state import ApplicationState, RuntimeState
|
||||
from app.utils.runtime_state import ApplicationState, JailServiceState, RuntimeState
|
||||
from app.utils.session_cache import NoOpSessionCache, SessionCache
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
@@ -333,6 +333,18 @@ async def get_pending_recovery(
|
||||
return app_context.pending_recovery
|
||||
|
||||
|
||||
async def get_jail_service_state(
|
||||
app_context: Annotated[ApplicationContext, Depends(get_app_context)],
|
||||
) -> JailServiceState:
|
||||
"""Return the jail service state holder from runtime state.
|
||||
|
||||
Returns:
|
||||
The JailServiceState containing capability detection cache and
|
||||
synchronization primitives for jail operations.
|
||||
"""
|
||||
return app_context.runtime_state.jail_service_state
|
||||
|
||||
|
||||
async def get_health_probe() -> Callable[[str], Awaitable[ServerStatus]]:
|
||||
"""Provide the health probe function for checking fail2ban connectivity.
|
||||
|
||||
@@ -439,6 +451,7 @@ Fail2BanStartCommandDep = Annotated[str, Depends(get_fail2ban_start_command)]
|
||||
GeoCacheDep = Annotated[GeoCache, Depends(get_geo_cache)]
|
||||
ServerStatusDep = Annotated[ServerStatus, Depends(get_server_status)]
|
||||
PendingRecoveryDep = Annotated[PendingRecovery | None, Depends(get_pending_recovery)]
|
||||
JailServiceStateDep = Annotated[JailServiceState, Depends(get_jail_service_state)]
|
||||
HealthProbeDep = Annotated[Callable[[str], Awaitable[ServerStatus]], Depends(get_health_probe)]
|
||||
SessionCacheDep = Annotated[SessionCache, Depends(get_session_cache)]
|
||||
SessionRepoDep = Annotated[SessionRepository, Depends(get_session_repo)]
|
||||
|
||||
Reference in New Issue
Block a user