From c3b6b13efa9098b99e272e8e7d7f0a212b073400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=BCmin=20K=C3=B6yk=C4=B1ran?= Date: Sat, 6 Jun 2026 11:14:47 +0300 Subject: [PATCH] Fix endless motion restart loop when motion is not a direct child (ECHILD) running() called os.waitpid(pid, WNOHANG) and os.kill(pid, 0) inside a single try block that swallowed both ESRCH and ECHILD. When the motion process is not a direct child of the motionEye process -- e.g. when it is launched by a process supervisor such as s6 in the Home Assistant add-on -- os.waitpid() raises OSError(ECHILD) *before* os.kill() ever runs. The ECHILD was caught and the function returned False, reporting a perfectly healthy motion process as 'not running'. The watchdog then killed and restarted motion every check interval, leaving motionEye unusable. Separate the best-effort zombie reap from the liveness check: ECHILD from waitpid() is ignored (it only means motion is not our child), and the running state is decided solely by os.kill(pid, 0), where only ESRCH means the process is gone. --- motioneye/motionctl.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/motioneye/motionctl.py b/motioneye/motionctl.py index c5f024013..22bbc6cb1 100644 --- a/motioneye/motionctl.py +++ b/motioneye/motionctl.py @@ -194,15 +194,26 @@ def running(): if pid is None: return False + # Best-effort reap: if motion is our child and has already exited, collect + # it so a dead process is not later reported as running. ECHILD here only + # means motion is not a direct child of this process (e.g. when launched by + # a process supervisor such as s6 in the Home Assistant add-on) and must NOT + # be treated as "not running" -- doing so triggers an endless restart loop. try: os.waitpid(pid, os.WNOHANG) + except OSError as e: + if e.errno not in (errno.ESRCH, errno.ECHILD): + raise + + # The actual liveness check. Only ESRCH means the process is gone. + try: os.kill(pid, 0) # the process is running return True except OSError as e: - if e.errno not in (errno.ESRCH, errno.ECHILD): + if e.errno != errno.ESRCH: raise return False