fix: never park the last present family as absent

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc9V1v7iXruubpoLiSE24M
This commit is contained in:
2026-09-21 09:50:43 -04:00
co-authored by Claude Sonnet 5
parent 8122c8d7dc
commit 95355374f8
5 changed files with 72 additions and 3 deletions
+7 -1
View File
@@ -485,12 +485,18 @@ class Host:
"""Host-level status: down, overdue, simplex, up or unknown.
overdue: every present family is overdue. simplex: both present, one up, one overdue
(same severity as up). Families the client reports absent are not considered.
(same severity as up). Families the client reports absent are not considered,
unless none is present: then every connection not parked as ABSENT is considered.
"""
conns = self.connections
if any(c.state == Connection.DOWN for c in conns.values()):
return "down"
present = [c for afam, c in conns.items() if self.family_present(afam)]
if not present:
# The client reports no protocol present (e.g. its internet probe fails on every
# family) yet may still be heard: monitor whatever is not parked as absent, so a
# host that then dies is "overdue", not silently "unknown".
present = [c for c in conns.values() if c.state != Connection.ABSENT]
up = sum(c.state == Connection.UP for c in present)
overdue = sum(c.state == Connection.OVERDUE for c in present)
if up and overdue:
+7 -1
View File
@@ -249,7 +249,11 @@ def _make_timer_callbacks(uname, host, ctx):
return
now = time.time()
afam = connection.afam
if not host.flag_present(afam):
# Park only while some other family is still present: the last monitored family
# must never be parked silently, or a host whose client reports no internet on any
# protocol would die without an alert.
if not host.flag_present(afam) and any(
af != afam and host.family_present(af) for af in host.connections):
# The client says this protocol has no connectivity: not an outage.
connection.newstate(cls.ABSENT, now)
connection.cancel_overdue_timer()
@@ -258,6 +262,8 @@ def _make_timer_callbacks(uname, host, ctx):
msg_to_websockets("host", host.stateinfo())
return
# If any connection is DOWN (clean shutdown reported) Host.status() is "down", so a
# later overdue of the other family raises no separate event/alert; the shutdown did.
prev = host.status()
connection.newstate(cls.OVERDUE, now, cfg.get("grace", 2))
new = host.status()