fix debounce link config changes
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 1s
Build and Deploy MITM Webserver / build (push) Successful in 9s

This commit is contained in:
2026-03-09 16:28:25 +01:00
parent 3f0e3c5400
commit 8efe797220
2 changed files with 47 additions and 1 deletions

View File

@@ -53,6 +53,7 @@ class BackendSettings:
sniffer_thread_join_timeout_seconds: float sniffer_thread_join_timeout_seconds: float
bridge_bpf_build_dir: str bridge_bpf_build_dir: str
bridge_link_state_thread_join_timeout_seconds: float bridge_link_state_thread_join_timeout_seconds: float
bridge_link_state_failure_holdoff_seconds: float
bridge_link_state_recovery_holdoff_seconds: float bridge_link_state_recovery_holdoff_seconds: float
bridge_link_state_degraded_recheck_seconds: float bridge_link_state_degraded_recheck_seconds: float
telemetry_process_stop_timeout_seconds: float telemetry_process_stop_timeout_seconds: float
@@ -93,6 +94,10 @@ def load_settings() -> BackendSettings:
"BACKEND_BRIDGE_LINK_STATE_THREAD_JOIN_TIMEOUT_SECONDS", "BACKEND_BRIDGE_LINK_STATE_THREAD_JOIN_TIMEOUT_SECONDS",
2.0, 2.0,
), ),
bridge_link_state_failure_holdoff_seconds=_env_float(
"BACKEND_BRIDGE_LINK_STATE_FAILURE_HOLDOFF_SECONDS",
0.75,
),
bridge_link_state_recovery_holdoff_seconds=_env_float( bridge_link_state_recovery_holdoff_seconds=_env_float(
"BACKEND_BRIDGE_LINK_STATE_RECOVERY_HOLDOFF_SECONDS", "BACKEND_BRIDGE_LINK_STATE_RECOVERY_HOLDOFF_SECONDS",
1.0, 1.0,

View File

@@ -99,6 +99,7 @@ class BridgeLinkStateWatcher:
) )
self._running = False self._running = False
self._suppressed_members: dict[str, bool] = {} self._suppressed_members: dict[str, bool] = {}
self._failing_since: dict[str, float] = {}
self._settle_deadlines: dict[str, float] = {} self._settle_deadlines: dict[str, float] = {}
self._managed_event_deadlines: dict[str, float] = {} self._managed_event_deadlines: dict[str, float] = {}
self._all_clear_since: Optional[float] = None self._all_clear_since: Optional[float] = None
@@ -152,6 +153,7 @@ class BridgeLinkStateWatcher:
"last_error": self._last_error, "last_error": self._last_error,
"last_action": self._last_action, "last_action": self._last_action,
"suppressed_members": sorted(self._suppressed_members), "suppressed_members": sorted(self._suppressed_members),
"failure_holdoff_seconds": settings.bridge_link_state_failure_holdoff_seconds,
"recovery_holdoff_seconds": self.recovery_holdoff_seconds, "recovery_holdoff_seconds": self.recovery_holdoff_seconds,
"degraded_recheck_seconds": settings.bridge_link_state_degraded_recheck_seconds, "degraded_recheck_seconds": settings.bridge_link_state_degraded_recheck_seconds,
"members": members, "members": members,
@@ -209,6 +211,11 @@ class BridgeLinkStateWatcher:
for iface, restore_up in self._suppressed_members.items() for iface, restore_up in self._suppressed_members.items()
if iface in states if iface in states
} }
self._failing_since = {
iface: first_seen
for iface, first_seen in self._failing_since.items()
if iface in states
}
self._settle_deadlines = { self._settle_deadlines = {
iface: deadline iface: deadline
for iface, deadline in self._settle_deadlines.items() for iface, deadline in self._settle_deadlines.items()
@@ -223,6 +230,7 @@ class BridgeLinkStateWatcher:
if len(states) < 2: if len(states) < 2:
with self._lock: with self._lock:
self._degraded = False self._degraded = False
self._failing_since = {}
self._all_clear_since = None self._all_clear_since = None
self._restore_suppressed_members(reason="bridge_has_fewer_than_two_members") self._restore_suppressed_members(reason="bridge_has_fewer_than_two_members")
return return
@@ -238,13 +246,22 @@ class BridgeLinkStateWatcher:
) )
if failing_members: if failing_members:
matured_failing_members = self._track_failing_members(failing_members, now)
if not matured_failing_members:
self._set_last_action(
f"waiting {settings.bridge_link_state_failure_holdoff_seconds:.2f}s before suppressing due to failures={failing_members}"
)
return
with self._lock: with self._lock:
self._degraded = True self._degraded = True
self._all_clear_since = None self._all_clear_since = None
self._suppress_other_members(states, failing_members) self._suppress_other_members(states, matured_failing_members)
return return
if suppressed_snapshot: if suppressed_snapshot:
with self._lock:
self._failing_since = {}
should_restore = False should_restore = False
with self._lock: with self._lock:
self._degraded = True self._degraded = True
@@ -261,6 +278,7 @@ class BridgeLinkStateWatcher:
with self._lock: with self._lock:
self._degraded = False self._degraded = False
self._failing_since = {}
self._all_clear_since = None self._all_clear_since = None
if source_ifname in states and self._is_source_eligible(source_ifname, states): if source_ifname in states and self._is_source_eligible(source_ifname, states):
@@ -293,12 +311,35 @@ class BridgeLinkStateWatcher:
state = states.get(ifname) state = states.get(ifname)
return state is not None and state.link_ready return state is not None and state.link_ready
def _track_failing_members(self, failing_members: list[str], now: float) -> list[str]:
"""Record first-seen timestamps and return failures that exceeded the holdoff."""
with self._lock:
self._degraded = True
tracked = {
ifname: self._failing_since.get(ifname, now)
for ifname in failing_members
}
self._failing_since = tracked
matured = [
ifname
for ifname, first_seen in tracked.items()
if now - first_seen >= settings.bridge_link_state_failure_holdoff_seconds
]
return sorted(matured)
def _next_wait_timeout(self) -> Optional[float]: def _next_wait_timeout(self) -> Optional[float]:
"""Return how long the watcher may sleep before the next restore deadline.""" """Return how long the watcher may sleep before the next restore deadline."""
with self._lock: with self._lock:
if self._suppressed_members and self._all_clear_since is not None: if self._suppressed_members and self._all_clear_since is not None:
deadline = self._all_clear_since + self.recovery_holdoff_seconds deadline = self._all_clear_since + self.recovery_holdoff_seconds
return max(0.0, min(deadline - time.time(), settings.bridge_link_state_degraded_recheck_seconds)) return max(0.0, min(deadline - time.time(), settings.bridge_link_state_degraded_recheck_seconds))
if self._failing_since:
earliest_deadline = min(
first_seen + settings.bridge_link_state_failure_holdoff_seconds
for first_seen in self._failing_since.values()
)
return max(0.0, min(earliest_deadline - time.time(), settings.bridge_link_state_degraded_recheck_seconds))
if self._degraded: if self._degraded:
return settings.bridge_link_state_degraded_recheck_seconds return settings.bridge_link_state_degraded_recheck_seconds
return None return None