diff --git a/odoo_client.py b/odoo_client.py index 6842610..02bf605 100644 --- a/odoo_client.py +++ b/odoo_client.py @@ -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