fix(gateway): send cron store notices for a symlinked cron/ directory
Follow-up to #133283 (greptile review). Store records are keyed by the resolved store path, but the notice sender rebuilt the owning profile from that path's parent. A profile whose cron/ is a symlink to another disk has a foreign parent, so neither its outage nor recovery notice reached any home channel; forget_homes had the same mismatch and never dropped such a departed profile's record. Both now match by resolving each served home's own cron/ path.
This commit is contained in:
@@ -165,11 +165,10 @@ def outage_covers(cron_dir: Path, due_at: float, grace: float) -> bool:
|
||||
def forget_homes(home_keys) -> None:
|
||||
"""Drop the state of stores whose profile home (``hermes_home_key``) this process no longer
|
||||
ticks, so a profile that left this gateway cannot keep the host-wide gauges at writable=0."""
|
||||
from hermes_constants import hermes_home_key
|
||||
keys = set(home_keys)
|
||||
stores = {_key(Path(home) / "cron") for home in home_keys} # a symlinked cron/ resolves elsewhere
|
||||
with _lock:
|
||||
for records in (_degraded, _recovered):
|
||||
for store in [s for s in records if hermes_home_key(Path(s).parent) in keys]:
|
||||
for store in [s for s in records if s in stores]:
|
||||
del records[store]
|
||||
|
||||
|
||||
|
||||
@@ -95,12 +95,13 @@ async def send_cron_store_notice(runner, event: str, record) -> None:
|
||||
|
||||
fields = record.notice_fields()
|
||||
key = "gateway.cron_store.unwritable" if event == "unwritable" else "gateway.cron_store.recovered"
|
||||
store_home = hermes_home_key(Path(record.store).parent)
|
||||
store_key = hermes_home_key(record.store)
|
||||
served_homes = runner._served_profile_homes or {}
|
||||
logger.info("Broadcasting cron store %s notice for %s", event, record.store)
|
||||
for profile, platform, _cfg, home, transport in list(runner._served_home_channel_transports()):
|
||||
served_home = served_homes.get(profile) if profile is not None else None
|
||||
if hermes_home_key(served_home or get_routing_process_hermes_home()) != store_home:
|
||||
# record.store is resolved: a cron/ symlinked to another disk has a foreign parent.
|
||||
if hermes_home_key(Path(served_home or get_routing_process_hermes_home()) / "cron") != store_key:
|
||||
continue
|
||||
# The opt-out is the owning profile's; the launch profile needs no extra scope.
|
||||
scope = _async_profile_runtime_scope(Path(served_home)) if served_home else contextlib.nullcontext()
|
||||
|
||||
@@ -380,7 +380,10 @@ def test_unwritable_store_is_shown_in_cron_status_and_announced_once(cron_store,
|
||||
return True
|
||||
|
||||
home = SimpleNamespace(chat_id="c1", thread_id=None)
|
||||
runner = SimpleNamespace(_send_home_channel_message=send, _served_profile_homes={"p": cron_store},
|
||||
profile = cron_store / "profile" # its cron/ is a symlink to the store on another disk
|
||||
profile.mkdir()
|
||||
(profile / "cron").symlink_to(cron_dir, target_is_directory=True)
|
||||
runner = SimpleNamespace(_send_home_channel_message=send, _served_profile_homes={"p": profile},
|
||||
_served_home_channel_transports=lambda: iter([("p", "telegram", None, home, object())]))
|
||||
monkeypatch.setattr("hermes_constants.get_routing_process_hermes_home", lambda: cron_store / "launch")
|
||||
# A manual loop clock: the real one-hour "recovered" window only elapses when the test says
|
||||
|
||||
Reference in New Issue
Block a user