refactor(discord): one liveness-knob resolver; int knobs reject inf and fractions
`_finite_positive_config_float` and `_config_int` were the same shape with the warn call pasted six times and asymmetric sign checks. Collapse both onto `_liveness_knob(key, default, cast)`: usable iff finite, >= 0 and exact for the cast; else warn and return 0; explicit 0 stays silent. Two int-path holes closed on the way: `websocket_liveness_failure_threshold: .inf` raised OverflowError inside `DiscordAdapter.__init__`, and `0.5` truncated to 0 and disabled the probe silently — the bug class this PR exists to remove. `2.0` / `"2"` still resolve to 2.
This commit is contained in:
@@ -1115,40 +1115,30 @@ class DiscordAdapter(DiscordMediaMixin, BasePlatformAdapter):
|
||||
self.name, key, raw,
|
||||
)
|
||||
|
||||
def _liveness_knob(self, key: str, default: Any, cast: type, *, env_key: Optional[str] = None):
|
||||
"""Resolve a liveness knob: usable iff finite, >= 0 and exact for ``cast``; else warn and return 0.
|
||||
|
||||
``0`` is the documented opt-out and stays silent. Bools, unparsable strings, nan/inf,
|
||||
negatives and (for int knobs) fractional values all disable the probe WITH a warning.
|
||||
"""
|
||||
raw = self._config_value(key, default, env_key=env_key)
|
||||
try:
|
||||
value = None if isinstance(raw, bool) else float(raw)
|
||||
except (TypeError, ValueError):
|
||||
value = None
|
||||
if value is not None and math.isfinite(value) and value >= 0 and cast(value) == value:
|
||||
return cast(value)
|
||||
if value != 0:
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return cast(0)
|
||||
|
||||
def _finite_positive_config_float(
|
||||
self, key: str, default: float, *, env_key: Optional[str] = None
|
||||
) -> float:
|
||||
"""Resolve a finite positive liveness duration; invalid values disable it (with a warning)."""
|
||||
raw = self._config_value(key, default, env_key=env_key)
|
||||
if isinstance(raw, bool):
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0.0
|
||||
try:
|
||||
value = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0.0
|
||||
if math.isfinite(value) and value > 0:
|
||||
return value
|
||||
if value != 0:
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0.0
|
||||
return self._liveness_knob(key, default, float, env_key=env_key)
|
||||
|
||||
def _config_int(self, key: str, default: int, *, env_key: Optional[str] = None) -> int:
|
||||
"""Resolve a positive liveness count; invalid values disable it (with a warning)."""
|
||||
raw = self._config_value(key, default, env_key=env_key)
|
||||
if isinstance(raw, bool):
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0
|
||||
try:
|
||||
value = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0
|
||||
if value < 0:
|
||||
self._warn_liveness_config_disabled(key, raw)
|
||||
return 0
|
||||
return value
|
||||
return self._liveness_knob(key, default, int, env_key=env_key)
|
||||
|
||||
def _handle_bot_task_done(self, task: asyncio.Task) -> None:
|
||||
"""Surface post-startup discord.py task exits as a retryable fatal so GatewayRunner
|
||||
|
||||
@@ -124,6 +124,8 @@ class _BrokenWebSocket:
|
||||
("websocket_max_latency_seconds", "_max_latency_seconds", "-inf"),
|
||||
("websocket_max_latency_seconds", "_max_latency_seconds", True),
|
||||
("websocket_max_latency_seconds", "_max_latency_seconds", "15s"),
|
||||
("websocket_liveness_failure_threshold", "_liveness_failure_threshold", 2.5),
|
||||
("websocket_liveness_failure_threshold", "_liveness_failure_threshold", float("inf")),
|
||||
],
|
||||
)
|
||||
def test_nonfinite_liveness_config_disables_that_probe_dimension(monkeypatch, key, attribute, raw):
|
||||
@@ -149,6 +151,7 @@ def test_unusable_liveness_config_warns_instead_of_disabling_silently(caplog):
|
||||
"websocket_liveness_interval_seconds": "15s",
|
||||
"websocket_max_latency_seconds": True,
|
||||
"websocket_liveness_failure_threshold": -1,
|
||||
"websocket_heartbeat_ack_max_age_seconds": "nan",
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -156,8 +159,10 @@ def test_unusable_liveness_config_warns_instead_of_disabling_silently(caplog):
|
||||
assert adapter._liveness_interval_seconds == 0.0
|
||||
assert adapter._max_latency_seconds == 0.0
|
||||
assert adapter._liveness_failure_threshold == 0
|
||||
assert adapter._heartbeat_ack_max_age_seconds == 0.0
|
||||
warned = [r.getMessage() for r in caplog.records if "liveness knob" in r.getMessage()]
|
||||
assert len(warned) == 3
|
||||
assert len(warned) == 4
|
||||
assert any("websocket_heartbeat_ack_max_age_seconds='nan'" in w for w in warned)
|
||||
assert any("websocket_liveness_interval_seconds='15s'" in w for w in warned)
|
||||
assert any("websocket_max_latency_seconds=True" in w for w in warned)
|
||||
assert any("websocket_liveness_failure_threshold=-1" in w for w in warned)
|
||||
@@ -170,11 +175,16 @@ def test_explicit_zero_liveness_knob_disables_without_warning(caplog):
|
||||
PlatformConfig(
|
||||
enabled=True,
|
||||
token="test-token",
|
||||
extra={"websocket_liveness_interval_seconds": 0, "websocket_liveness_failure_threshold": 0},
|
||||
extra={
|
||||
"websocket_liveness_interval_seconds": 0,
|
||||
"websocket_max_latency_seconds": "0",
|
||||
"websocket_liveness_failure_threshold": 0,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert adapter._liveness_interval_seconds == 0.0
|
||||
assert adapter._max_latency_seconds == 0.0
|
||||
assert adapter._liveness_failure_threshold == 0
|
||||
assert not [r for r in caplog.records if "liveness knob" in r.getMessage()]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user