Skip to content

Multiprocessing.Event.wait race condition when timeout is not none on Mac OS #95826

Closed as not planned
@mcclurem

Description

@mcclurem

Current Python.org docs for multiprocessing.Event link directly to threading.Event
The documents for threading.Event.wait read (emphasis mine)

This method returns True if and only if the internal flag has been set to true, either before the wait call or after the wait starts, so it will always return True except if a timeout is given and the operation times out.

It appears that a race condition exists in the multiprocessing implementation but not in the threading implementation.
If we implement a child process that just does a set/clear on an event over and over (as a heartbeat), then in the parent process do a wait() on that event (with extremely long timeout), we would expect to never see a timeout.

I've attached the proof of concept below showing how both threading.Event and multiprocessing.Event yield different results.

Possible cousin bug: #85772

Observed on:

  • Mac OS 12.3 Monterey
  • Apple M1 Max MacBook Pro
  • Homebrew python: Python 3.9.13 (main, May 24 2022, 21:13:54) [Clang 13.0.0 (clang-1300.0.29.30)]
#!/usr/bin/env python3

import multiprocessing
import threading

class SimpleRepro:
    def __init__(self, use_thread=False):
        if use_thread:
            self.heartbeat_event = threading.Event()
            self.shutdown_event = threading.Event()
            self.child_proc = threading.Thread(target=self.child_process)
        else:
            self.heartbeat_event = multiprocessing.Event()
            self.shutdown_event = multiprocessing.Event()
            self.child_proc = multiprocessing.Process(target=self.child_process, daemon=True)
        self.child_proc.start()

    def child_process(self):
        while True:
            if self.shutdown_event.is_set():
                return
            self.heartbeat_event.set()
            self.heartbeat_event.clear()

    def test_heartbeat(self):
        any_failures=False
        for i in range(10000):
            success = self.heartbeat_event.wait(100)
            if not success:
                print(f"Failed at iteration {i}")
                any_failures = True
        self.shutdown_event.set()
        if not any_failures:
            print("Successfully tested 10000 times without failure")


if __name__ == '__main__':
    print("Testing with multiprocessing.Event")
    foo = SimpleRepro(use_thread=False)
    foo.test_heartbeat()
    print("Testing with threading.Event")
    foo = SimpleRepro(use_thread=True)
    foo.test_heartbeat()

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions