Skip to content

multiprocessing.Process.is_alive() can incorrectly return True after join() #130895

Description

@colesbury

Bug report

Bug description:

This came up in #130849 (comment)

The problem is that popen_fork.Popen (and popen_spawn.Popen and popen_forkserver.Popen) are not thread-safe:

defpoll(self, flag=os.WNOHANG):
ifself.returncodeisNone:
try:
pid, sts=os.waitpid(self.pid, flag)
exceptOSError:
# Child process not yet created. See #1731717
# e.errno == errno.ECHILD == 10
returnNone
ifpid==self.pid:
self.returncode=os.waitstatus_to_exitcode(sts)
returnself.returncode

The first successful call to os.waitpid() may reap the pid so that subsequent calls raise an OSError. I've only seen this on macOS (not Linux). We may not yet however have set self.returncode -- that happens a few statements later, so poll() can return None if:

  1. The process has finished
  2. Another thread called poll(), but hasn't yet set self.returncode

And then is_alive() can return True:

defis_alive(self):
'''
Return whether process is alive
'''
self._check_closed()
ifselfis_current_process:
returnTrue
assertself._parent_pid==os.getpid(), 'can only test a child process'
ifself._popenisNone:
returnFalse
returncode=self._popen.poll()
ifreturncodeisNone:
returnTrue
else:
_children.discard(self)
returnFalse

Note that some classes like concurrent.futures.ProcessPoolExecutor use threads internally, so the user may not even know that threads are involved.

Repro:

repro.py
importosimportmultiprocessingasmpimportthreadingimporttimeimportsysoriginal_excepthook=threading.excepthookdefon_except(args):
original_excepthook(args)
os._exit(1)
threading.excepthook=on_exceptdefp1():
passdefthread1(p):
whilep.is_alive():
time.sleep(0.00001)
passdeftest():
foriinrange(1000):
print(i)
p=mp.Process(target=p1)
p.start()
t=threading.Thread(target=thread1, args=(p,))
t.start()
p.join()
assertnotp.is_alive()
t.join()
defmain():
threads= [threading.Thread(target=test) for_inrange(10)]
fortinthreads:
t.start()
fortinthreads:
t.join()
if__name__=="__main__":
main()

NOTE:

  • This is unrelated to free threading
  • popen_fork.Popen (and subclasses) are distinct from subprocess.Popen

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytopic-multiprocessingtype-bugAn unexpected behavior, bug, or error

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions