@ -759,13 +759,30 @@ class ORBDaemonController:
@property
@property
def running ( self ) - > bool :
def running ( self ) - > bool :
""" True if the daemon process is alive (PID file + kill-0 check )."""
""" True if the daemon process is alive (PID file + kill-0 check , zombie-safe )."""
pid_file = self . _pid_file ( )
pid_file = self . _pid_file ( )
if not pid_file . exists ( ) :
if not pid_file . exists ( ) :
return False
return False
try :
try :
pid = int ( pid_file . read_text ( ) . strip ( ) )
pid = int ( pid_file . read_text ( ) . strip ( ) )
os . kill ( pid , 0 ) # signal 0: probe without sending anything
os . kill ( pid , 0 ) # signal 0: probe without sending anything
# kill-0 succeeds for zombie processes too — check /proc or ps to exclude zombies
try :
import subprocess as _sp
result = _sp . run (
[ " ps " , " -p " , str ( pid ) , " -o " , " stat= " ] ,
capture_output = True , text = True , timeout = 2 ,
)
stat = result . stdout . strip ( )
if stat . startswith ( " Z " ) :
# Zombie — treat as dead, clean up PID file
try :
pid_file . unlink ( )
except Exception :
pass
return False
except Exception :
pass # if ps fails, trust kill-0 result
return True
return True
except ( ValueError , ProcessLookupError , PermissionError , OSError ) :
except ( ValueError , ProcessLookupError , PermissionError , OSError ) :
# Process dead or PID stale — remove the file
# Process dead or PID stale — remove the file