From f674f2ed6c0f33403a9060918c2cbd7117d4d724 Mon Sep 17 00:00:00 2001 From: Typosaurus Date: Sun, 19 Jul 2026 21:13:37 +0000 Subject: [PATCH] ticket #102 attempt 1 --- devplacepy/services/xmlrpc/__init__.py | 41 ++++++++++++++++++++++++-- devplacepy/services/xmlrpc/server.py | 4 +++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/devplacepy/services/xmlrpc/__init__.py b/devplacepy/services/xmlrpc/__init__.py index 68088532..e015af26 100644 --- a/devplacepy/services/xmlrpc/__init__.py +++ b/devplacepy/services/xmlrpc/__init__.py @@ -2,8 +2,10 @@ from __future__ import annotations +import socket import subprocess import sys +import time from devplacepy.config import XMLRPC_BIND, XMLRPC_PORT from devplacepy.services.base import BaseService @@ -25,20 +27,48 @@ class XmlrpcService(BaseService): def __init__(self) -> None: super().__init__("xmlrpc", interval_seconds=XMLRPC_INTERVAL_SECONDS) self._process: subprocess.Popen | None = None + self._last_stderr: str | None = None def _alive(self) -> bool: return self._process is not None and self._process.poll() is None def _spawn(self) -> None: + self._last_stderr = None self._process = subprocess.Popen( [sys.executable, "-m", SERVER_MODULE], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, + stderr=subprocess.PIPE, ) self.log( - f"Forking XML-RPC server started (pid {self._process.pid}) on " + f"Forking XML-RPC server spawning (pid {self._process.pid}) on " f"{XMLRPC_BIND}:{XMLRPC_PORT}" ) + time.sleep(0.5) + if self._process.poll() is not None: + stderr_data = self._process.communicate()[1] + if stderr_data: + self._last_stderr = stderr_data.decode("utf-8", errors="replace") + self.log( + f"Forking XML-RPC server died after spawn (pid {self._process.pid}, " + f"exit code {self._process.returncode})" + ) + if self._last_stderr: + for line in self._last_stderr.strip().split("\n"): + self.log(f" stderr: {line}") + return + try: + sock = socket.create_connection((XMLRPC_BIND, XMLRPC_PORT), timeout=1) + sock.close() + except (OSError, socket.timeout): + self.log( + f"Forking XML-RPC server not yet ready (pid {self._process.pid}) " + f"on {XMLRPC_BIND}:{XMLRPC_PORT}" + ) + else: + self.log( + f"Forking XML-RPC server started (pid {self._process.pid}) on " + f"{XMLRPC_BIND}:{XMLRPC_PORT}" + ) def _terminate(self) -> None: if not self._alive(): @@ -65,6 +95,13 @@ class XmlrpcService(BaseService): self.log(f"XML-RPC server healthy (pid {self._process.pid})") return self.log("XML-RPC server not running, starting it") + if self._process is not None: + self.log( + f"Previous process exited with code {self._process.returncode}" + ) + if self._last_stderr: + for line in self._last_stderr.strip().split("\n"): + self.log(f" last stderr: {line}") self._spawn() def collect_metrics(self) -> dict: diff --git a/devplacepy/services/xmlrpc/server.py b/devplacepy/services/xmlrpc/server.py index b8a8da1d..57c61cac 100644 --- a/devplacepy/services/xmlrpc/server.py +++ b/devplacepy/services/xmlrpc/server.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +import sys from socketserver import ForkingMixIn from xmlrpc.server import SimpleXMLRPCDispatcher, SimpleXMLRPCRequestHandler, SimpleXMLRPCServer @@ -116,6 +117,9 @@ def main() -> None: server.serve_forever() except KeyboardInterrupt: logger.info("XML-RPC server interrupted") + except Exception: + logging.exception("XML-RPC server crashed with unhandled exception") + sys.exit(1) finally: server.server_close()