feat(host-agent): add single-instance lock to prevent duplicate-process dispatch races
Acquire an exclusive, non-blocking filelock on the identity state directory as the first action of create_application(), before resolve_host_identity() or any enrollment/heartbeat side effect. A second process against the same identity_path exits immediately with InstanceAlreadyRunningError naming the lock path; the lock releases automatically on any process exit (including SIGKILL) via OS-level advisory locking, and explicitly during run_async()'s shutdown finally block. filelock is promoted from transitive to direct dependency (version unchanged at 3.29.7). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ from dataclasses import replace
|
||||
|
||||
from host_agent.app import create_application
|
||||
from host_agent.config import load_host_agent_config
|
||||
from host_agent.instance_lock import InstanceAlreadyRunningError
|
||||
from host_agent.local_account import LocalAccountStore
|
||||
|
||||
|
||||
@@ -30,16 +31,22 @@ def main(argv: Sequence[str] | None = None) -> None:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
create_application(config=config).run()
|
||||
try:
|
||||
create_application(config=config).run()
|
||||
except InstanceAlreadyRunningError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
|
||||
def _run_setup() -> None:
|
||||
config = load_host_agent_config()
|
||||
store = LocalAccountStore(config.local_account_path)
|
||||
if store.load() is not None:
|
||||
confirm = input(
|
||||
"A local account already exists. Overwrite it? [y/N] "
|
||||
).strip().lower()
|
||||
confirm = (
|
||||
input("A local account already exists. Overwrite it? [y/N] ")
|
||||
.strip()
|
||||
.lower()
|
||||
)
|
||||
if confirm != "y":
|
||||
print("Setup cancelled; existing account left unchanged.")
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user