111 lines
4.9 KiB
Python
111 lines
4.9 KiB
Python
import time
|
|
from logger import logger
|
|
|
|
# Defensive monkeypatch: aioice Transaction.__retry may run after the
|
|
# underlying datagram transport or loop was torn down which results in
|
|
# AttributeError being raised and flooding logs. Wrap the original
|
|
# implementation to catch and suppress AttributeError while preserving
|
|
# other exceptions. This is a temporary mitigation to keep logs readable
|
|
# while we investigate/upstream a proper fix or upgrade aioice.
|
|
try:
|
|
import aioice.stun as _aioice_stun # type: ignore
|
|
|
|
# The method is defined with a double-underscore name (__retry) which
|
|
# gets name-mangled. Detect the actual attribute name robustly.
|
|
retry_attr_name = None
|
|
for name in dir(_aioice_stun.Transaction):
|
|
if name.endswith("retry"):
|
|
obj = getattr(_aioice_stun.Transaction, name)
|
|
if callable(obj):
|
|
retry_attr_name = name
|
|
_orig_retry = obj
|
|
break
|
|
|
|
if retry_attr_name is not None:
|
|
# Simple in-process dedupe cache so we only log the same AttributeError
|
|
# once per interval. This prevents flooding the logs when many
|
|
# transactions race to run after shutdown.
|
|
_MONKEYPATCH_LOG_CACHE: dict[str, float] = {}
|
|
_MONKEYPATCH_LOG_SUPPRESSION_INTERVAL = 5.0
|
|
|
|
def _should_log_once(key: str) -> bool:
|
|
now = time.time()
|
|
last = _MONKEYPATCH_LOG_CACHE.get(key)
|
|
if last is None or (now - last) > _MONKEYPATCH_LOG_SUPPRESSION_INTERVAL:
|
|
_MONKEYPATCH_LOG_CACHE[key] = now
|
|
return True
|
|
return False
|
|
|
|
def _safe_transaction_retry(self, *args, **kwargs): # type: ignore
|
|
try:
|
|
return _orig_retry(self, *args, **kwargs) # type: ignore
|
|
except AttributeError as e: # type: ignore
|
|
# Transport or event-loop already closed; log once per key
|
|
key = f"Transaction.{retry_attr_name}:{e}"
|
|
if _should_log_once(key):
|
|
logger.warning(
|
|
"aioice Transaction.%s AttributeError suppressed: %s",
|
|
retry_attr_name,
|
|
e,
|
|
)
|
|
except Exception: # type: ignore
|
|
# Preserve visibility for other unexpected exceptions
|
|
logger.exception(
|
|
"aioice Transaction.%s raised an unexpected exception",
|
|
retry_attr_name,
|
|
)
|
|
|
|
setattr(_aioice_stun.Transaction, retry_attr_name, _safe_transaction_retry) # type: ignore
|
|
logger.info("Applied safe aioice Transaction.%s monkeypatch", retry_attr_name)
|
|
else:
|
|
logger.warning("aioice Transaction.__retry not found; skipping monkeypatch")
|
|
except Exception as e:
|
|
logger.exception("Failed to apply aioice Transaction.__retry monkeypatch: %s", e)
|
|
|
|
# Additional defensive patch: wrap the protocol-level send_stun implementation
|
|
# (e.g. StunProtocol.send_stun) which ultimately calls the datagram transport's
|
|
# sendto. If the transport or its loop is already torn down, sendto can raise
|
|
# AttributeError which then triggers asyncio's fatal error path (calling a None
|
|
# loop). Wrapping here prevents the flood of selector_events/_fatal_error
|
|
# AttributeError traces.
|
|
try:
|
|
import aioice.ice as _aioice_ice # type: ignore
|
|
|
|
# Prefer to patch StunProtocol.send_stun which is used by the ICE code.
|
|
send_attr_name = None
|
|
if hasattr(_aioice_ice, "StunProtocol"):
|
|
proto_cls = getattr(_aioice_ice, "StunProtocol")
|
|
for name in dir(proto_cls):
|
|
if name.endswith("send_stun"):
|
|
attr = getattr(proto_cls, name)
|
|
if callable(attr):
|
|
send_attr_name = name
|
|
_orig_send_stun = attr
|
|
break
|
|
|
|
if send_attr_name is not None:
|
|
|
|
def _safe_send_stun(self, message, addr): # type: ignore
|
|
try:
|
|
return _orig_send_stun(self, message, addr) # type: ignore
|
|
except AttributeError as e: # type: ignore
|
|
# Likely transport._sock or transport._loop is None; log once
|
|
key = f"StunProtocol.{send_attr_name}:{e}"
|
|
if _should_log_once(key):
|
|
logger.warning(
|
|
"aioice StunProtocol.%s AttributeError suppressed: %s",
|
|
send_attr_name,
|
|
e,
|
|
)
|
|
except Exception: # type: ignore
|
|
logger.exception(
|
|
"aioice StunProtocol.%s raised unexpected exception", send_attr_name
|
|
)
|
|
|
|
setattr(proto_cls, send_attr_name, _safe_send_stun) # type: ignore
|
|
logger.info("Applied safe aioice StunProtocol.%s monkeypatch", send_attr_name)
|
|
else:
|
|
logger.warning("aioice StunProtocol.send_stun not found; skipping monkeypatch")
|
|
except Exception as e:
|
|
logger.exception("Failed to apply aioice StunProtocol.send_stun monkeypatch: %s", e)
|