feat(views): prune stale session keys with local-only bookkeeping (Phase 4)
New pruning.py sidecar module with load_pruning_state / save_pruning_state. The sidecar lives at ~/.config/muxplex/pruning.json and is NEVER in SYNCABLE_KEYS — bookkeeping is per-device, not federated. prune_stale_keys(settings, live_keys, *, pruning_state, grace_seconds, now) drops keys from hidden_sessions and view.sessions after they have been missing past the configurable grace period (default 24h, syncable via stale_key_grace_hours). Wired into _run_poll_cycle as step 14 with try/except so failures never abort the cycle. The prune ACTION (settings change) syncs normally via existing LWW; the bookkeeping does not. 24 new tests pass; total muxplex/tests/ = 1263 passed.
This commit is contained in:
@@ -70,7 +70,10 @@ from muxplex.settings import (
|
||||
load_federation_key,
|
||||
load_settings,
|
||||
patch_settings,
|
||||
save_settings,
|
||||
)
|
||||
from muxplex.pruning import load_pruning_state, save_pruning_state
|
||||
from muxplex.views import prune_stale_keys
|
||||
from muxplex.identity import load_device_id
|
||||
from muxplex.ttyd import kill_orphan_ttyd, kill_ttyd, spawn_ttyd, TTYD_PORT
|
||||
|
||||
@@ -269,6 +272,48 @@ async def _run_poll_cycle() -> None:
|
||||
except Exception:
|
||||
_log.exception("settings sync cycle error")
|
||||
|
||||
# 14. Prune stale session keys from views and hidden_sessions.
|
||||
#
|
||||
# Each device only knows its own live sessions natively. The live_keys
|
||||
# set below includes both the bare session name (legacy bare-name entries)
|
||||
# and the canonical device_id:name form. Remote-device keys tracked in
|
||||
# views/hidden_sessions are also covered: if this device has not seen a
|
||||
# remote key for the full grace period the key is pruned locally; the
|
||||
# remote device handles pruning for its own keys independently. The grace
|
||||
# period prevents thrash when sessions briefly disappear during restarts
|
||||
# or sync gaps.
|
||||
#
|
||||
# Pruning bookkeeping (first-missed-at timestamps) is NEVER written to
|
||||
# settings.json and is NEVER sent to peers — it lives in pruning.json.
|
||||
# The prune action (removing dead keys from settings) IS a normal
|
||||
# settings write that syncs via the existing LWW mechanism.
|
||||
try:
|
||||
_prune_settings = load_settings()
|
||||
_prune_state = load_pruning_state()
|
||||
_grace_hours = float(_prune_settings.get("stale_key_grace_hours", 24.0))
|
||||
_grace_seconds = _grace_hours * 3600.0
|
||||
|
||||
_local_device_id = load_device_id()
|
||||
_live_keys: set[str] = set()
|
||||
for _name in names:
|
||||
# Include both the bare name (for legacy stored entries) and the
|
||||
# canonical device_id:name form.
|
||||
_live_keys.add(_name)
|
||||
_live_keys.add(f"{_local_device_id}:{_name}")
|
||||
|
||||
_prune_settings, _prune_state, _prune_changed = prune_stale_keys(
|
||||
_prune_settings,
|
||||
_live_keys,
|
||||
pruning_state=_prune_state,
|
||||
grace_seconds=_grace_seconds,
|
||||
)
|
||||
save_pruning_state(_prune_state)
|
||||
if _prune_changed:
|
||||
# Stale keys were removed — persist (triggers LWW sync on next cycle).
|
||||
save_settings(_prune_settings)
|
||||
except Exception:
|
||||
_log.exception("stale-key prune cycle error")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Poll loop
|
||||
|
||||
Reference in New Issue
Block a user