Skip to content

test_multiprocessing[fork] fails on Python 3.15 #4086

Description

@QuLogic

Zarr version

3.2.1

Numcodecs version

0.16.5

Python Version

3.15.0b2

Operating System

Fedora Rawhide

Installation

building from source

Description

Python 3.15 has deprecated the fork mode when using threads, which the pytest-xdist plugin appears to use. This causes these two tests to fail due to an unhandled warning:

____________________ test_multiprocessing[None-local-fork] _____________________

store = LocalStore('file:///tmp/pytest-of-mockbuild/pytest-0/test_multiprocessing_None_loca0')
method = 'fork', shards = None

    @pytest.mark.parametrize(
        "method",
        [
            pytest.param(
                "fork",
                marks=pytest.mark.skipif(
                    sys.platform in ("win32", "darwin"), reason="fork not supported on Windows or OSX"
                ),
            ),
            "spawn",
            pytest.param(
                "forkserver",
                marks=pytest.mark.skipif(
                    sys.platform == "win32", reason="forkserver not supported on Windows"
                ),
            ),
        ],
    )
    @pytest.mark.parametrize("store", ["local"], indirect=True)
    @pytest.mark.parametrize("shards", [None, (20,)])
    def test_multiprocessing(
        store: Store, method: Literal["fork", "spawn", "forkserver"], shards: tuple[int, ...] | None
    ) -> None:
        """
        Test that arrays can be pickled and indexed in child processes
        """
        data = np.arange(100)
        chunks: Literal["auto"] | tuple[int, ...]
        if shards is None:
            chunks = "auto"
        else:
            chunks = (1,)
        arr = zarr.create_array(store=store, data=data, shards=shards, chunks=chunks)
        ctx = mp.get_context(method)
>       with ctx.Pool() as pool:
             ^^^^^^^^^^

tests/test_array.py:1913: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib64/python3.15/multiprocessing/context.py:119: in Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,
/usr/lib64/python3.15/multiprocessing/pool.py:215: in __init__
    self._repopulate_pool()
/usr/lib64/python3.15/multiprocessing/pool.py:306: in _repopulate_pool
    return self._repopulate_pool_static(self._ctx, self.Process,
/usr/lib64/python3.15/multiprocessing/pool.py:329: in _repopulate_pool_static
    w.start()
/usr/lib64/python3.15/multiprocessing/process.py:121: in start
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
/usr/lib64/python3.15/multiprocessing/context.py:290: in _Popen
    return Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^
/usr/lib64/python3.15/multiprocessing/popen_fork.py:20: in __init__
    self._launch(process_obj)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <multiprocessing.popen_fork.Popen object at 0x7fdd72e31a90>
process_obj = <ForkProcess name='ForkPoolWorker-1' parent=1254 initial daemon>

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        # gh-146313: Tell the resource tracker's at-fork handler to keep
        # the inherited pipe fd so this child reuses the parent's tracker
        # (gh-80849) rather than closing it and launching its own.
        from .resource_tracker import _fork_intent
        _fork_intent.preserve_fd = True
        try:
>           self.pid = os.fork()
                       ^^^^^^^^^
E           DeprecationWarning: This process (pid=1254) is multi-threaded, use of fork() may lead to deadlocks in the child.

/usr/lib64/python3.15/multiprocessing/popen_fork.py:76: DeprecationWarning
___________________ test_multiprocessing[shards1-local-fork] ___________________

store = LocalStore('file:///tmp/pytest-of-mockbuild/pytest-0/test_multiprocessing_shards1_l0')
method = 'fork', shards = (20,)

    @pytest.mark.parametrize(
        "method",
        [
            pytest.param(
                "fork",
                marks=pytest.mark.skipif(
                    sys.platform in ("win32", "darwin"), reason="fork not supported on Windows or OSX"
                ),
            ),
            "spawn",
            pytest.param(
                "forkserver",
                marks=pytest.mark.skipif(
                    sys.platform == "win32", reason="forkserver not supported on Windows"
                ),
            ),
        ],
    )
    @pytest.mark.parametrize("store", ["local"], indirect=True)
    @pytest.mark.parametrize("shards", [None, (20,)])
    def test_multiprocessing(
        store: Store, method: Literal["fork", "spawn", "forkserver"], shards: tuple[int, ...] | None
    ) -> None:
        """
        Test that arrays can be pickled and indexed in child processes
        """
        data = np.arange(100)
        chunks: Literal["auto"] | tuple[int, ...]
        if shards is None:
            chunks = "auto"
        else:
            chunks = (1,)
        arr = zarr.create_array(store=store, data=data, shards=shards, chunks=chunks)
        ctx = mp.get_context(method)
>       with ctx.Pool() as pool:
             ^^^^^^^^^^

tests/test_array.py:1913: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib64/python3.15/multiprocessing/context.py:119: in Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,
/usr/lib64/python3.15/multiprocessing/pool.py:215: in __init__
    self._repopulate_pool()
/usr/lib64/python3.15/multiprocessing/pool.py:306: in _repopulate_pool
    return self._repopulate_pool_static(self._ctx, self.Process,
/usr/lib64/python3.15/multiprocessing/pool.py:329: in _repopulate_pool_static
    w.start()
/usr/lib64/python3.15/multiprocessing/process.py:121: in start
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
/usr/lib64/python3.15/multiprocessing/context.py:290: in _Popen
    return Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^
/usr/lib64/python3.15/multiprocessing/popen_fork.py:20: in __init__
    self._launch(process_obj)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <multiprocessing.popen_fork.Popen object at 0x7fdd749ed310>
process_obj = <ForkProcess name='ForkPoolWorker-10' parent=1254 initial daemon>

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        # gh-146313: Tell the resource tracker's at-fork handler to keep
        # the inherited pipe fd so this child reuses the parent's tracker
        # (gh-80849) rather than closing it and launching its own.
        from .resource_tracker import _fork_intent
        _fork_intent.preserve_fd = True
        try:
>           self.pid = os.fork()
                       ^^^^^^^^^
E           DeprecationWarning: This process (pid=1254) is multi-threaded, use of fork() may lead to deadlocks in the child.

/usr/lib64/python3.15/multiprocessing/popen_fork.py:76: DeprecationWarning

Steps to reproduce

$ pytest -n auto

Additional output

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugPotential issues with the zarr-python library

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions