Add XML-RPC timeout to odoo_client (hung Odoo leaked call slots)
Reliability drill finding: xmlrpc.client has no default timeout, and the call
slot is released only after run_agent returns (which awaits post-call
persistence). A hung -- not refused -- Odoo would block forever; two hung
persists would leave the phone line permanently at capacity ("busy") until a
restart. All XML-RPC round-trips now use a 15s socket timeout
(ODOO_TIMEOUT_SECS), http and https transports both covered.
Drilled: black-hole address fails cleanly at the timeout; real auth unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,18 +26,45 @@ ODOO_TEAM_ID = int(os.environ["ODOO_TEAM_ID"]) if os.environ.get("ODOO_TEAM_ID")
|
||||
ODOO_USER_ID = int(os.environ["ODOO_USER_ID"]) if os.environ.get("ODOO_USER_ID") else None
|
||||
|
||||
|
||||
# Hard cap on any single XML-RPC round-trip. Without it, xmlrpc.client uses the OS
|
||||
# default (no timeout): a HUNG Odoo (as opposed to a refused connection) would block
|
||||
# post-call persistence forever — and since the call slot is released only after
|
||||
# run_agent returns, two hung persists would leave the line permanently "busy".
|
||||
ODOO_TIMEOUT_SECS = float(os.environ.get("ODOO_TIMEOUT_SECS", "15"))
|
||||
|
||||
|
||||
class OdooError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class _TimeoutTransport(xmlrpc.client.Transport):
|
||||
def make_connection(self, host):
|
||||
conn = super().make_connection(host)
|
||||
conn.timeout = ODOO_TIMEOUT_SECS
|
||||
return conn
|
||||
|
||||
|
||||
class _TimeoutSafeTransport(xmlrpc.client.SafeTransport):
|
||||
def make_connection(self, host):
|
||||
conn = super().make_connection(host)
|
||||
conn.timeout = ODOO_TIMEOUT_SECS
|
||||
return conn
|
||||
|
||||
|
||||
def _proxy(path):
|
||||
transport = (_TimeoutSafeTransport() if ODOO_URL.lower().startswith("https")
|
||||
else _TimeoutTransport())
|
||||
return xmlrpc.client.ServerProxy(f"{ODOO_URL}{path}", transport=transport)
|
||||
|
||||
|
||||
def _connect():
|
||||
if not (ODOO_USER and ODOO_API_KEY):
|
||||
raise OdooError("ODOO_USER / ODOO_API_KEY not set")
|
||||
common = xmlrpc.client.ServerProxy(f"{ODOO_URL}/xmlrpc/2/common")
|
||||
common = _proxy("/xmlrpc/2/common")
|
||||
uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_API_KEY, {})
|
||||
if not uid:
|
||||
raise OdooError("Odoo authentication failed (check db/user/key)")
|
||||
models = xmlrpc.client.ServerProxy(f"{ODOO_URL}/xmlrpc/2/object")
|
||||
models = _proxy("/xmlrpc/2/object")
|
||||
return uid, models
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user