Skip to content

Commit

Permalink
Add proc_alive() util that handles zombies
Browse files Browse the repository at this point in the history
  • Loading branch information
danijar committed Sep 12, 2024
1 parent 6770465 commit 6af1696
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 36 deletions.
3 changes: 2 additions & 1 deletion portal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '3.2.1'
__version__ = '3.2.0'

import multiprocessing as mp
try:
Expand Down Expand Up @@ -29,4 +29,5 @@
from .sharray import SharedArray

from .utils import free_port
from .utils import proc_alive
from .utils import run
10 changes: 2 additions & 8 deletions portal/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,7 @@ def running(self):
return False
if not self.process.is_alive():
return False
try:
os.kill(self.pid, 0)
except OSError as err:
if err.errno == errno.ESRCH:
return False
return True
return utils.proc_alive(self.pid)

@property
def exitcode(self):
Expand All @@ -86,8 +81,7 @@ def start(self):

def join(self, timeout=None):
assert self.started
if self.running:
self.process.join(timeout)
self.process.join(timeout)
return self

def kill(self, timeout=1):
Expand Down
3 changes: 1 addition & 2 deletions portal/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ def start(self):
return self

def join(self, timeout=None):
if self.running:
self.thread.join(timeout)
self.thread.join(timeout)
return self

def kill(self, timeout=1.0):
Expand Down
17 changes: 16 additions & 1 deletion portal/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import ctypes
import errno
import os
import socket
import sys
import threading
Expand Down Expand Up @@ -73,9 +75,22 @@ def eachproc(fn, procs):
# Should never happen but print warning if any survived.
eachproc(lambda p: (
print('Killed subprocess is still alive.')
if p.status() != psutil.STATUS_ZOMBIE else None), procs)
if proc_alive(p.pid) else None), procs)


def proc_alive(pid):
try:
if psutil.Process(pid).status() == psutil.STATUS_ZOMBIE:
return False
except psutil.NoSuchProcess:
return False
try:
os.kill(pid, 0)
except OSError as e:
if e.errno == errno.ESRCH:
return False
assert True


def free_port():
# Return a port that is currently free. This function is not thread or
Expand Down
17 changes: 4 additions & 13 deletions tests/test_errfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,7 @@ def hang_process(ready, queue):
assert not worker.running
pids = [queue.get() for _ in range(4)]
time.sleep(2.0) # On some systems this can take a while.
assert not alive(pids[0])
assert not alive(pids[1])
assert not alive(pids[2])
assert not alive(pids[3])


def alive(pid):
try:
os.kill(pid, 0)
except OSError:
assert True
else:
assert False
assert not portal.proc_alive(pids[0])
assert not portal.proc_alive(pids[1])
assert not portal.proc_alive(pids[2])
assert not portal.proc_alive(pids[3])
13 changes: 2 additions & 11 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def inner(ready, queue):
worker.kill()
assert not worker.running
assert worker.exitcode < 0
assert not alive(queue.get())
assert not alive(queue.get())
assert not portal.proc_alive(queue.get())
assert not portal.proc_alive(queue.get())

@pytest.mark.parametrize('repeat', range(5))
def test_kill_with_subthread(self, repeat):
Expand Down Expand Up @@ -103,12 +103,3 @@ def inner(ready):
ready.wait()
assert ready.is_set()
portal.context.initfns.clear()


def alive(pid):
try:
os.kill(pid, 0)
except OSError:
assert True
else:
assert False

0 comments on commit 6af1696

Please sign in to comment.