Bug 2369159 - python-joblib fails to build with Python 3.13.3+: AssertionError: assert b"resource_tracker" not in err, err.decode()
Summary: python-joblib fails to build with Python 3.13.3+: AssertionError: assert b"re...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Fedora
Classification: Fedora
Component: python-joblib
Version: rawhide
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Sergio Pascual
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.14 F43FTBFS, RAWHIDEFTBFS
TreeView+ depends on / blocked
 
Reported: 2025-05-29 10:31 UTC by Karolina Surma
Modified: 2025-06-15 18:34 UTC (History)
8 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed: 2025-06-15 18:34:40 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Github joblib joblib issues 1708 0 None closed CI failing on main for latest bugfix releases on CPython 3.12 an 3.13 (3.12.10 and 3.13.3) 2025-05-29 10:44:53 UTC

Description Karolina Surma 2025-05-29 10:31:15 UTC
python-joblib fails to build with Python 3.14.0b2.

__________ test_permission_error_windows_memmap_sent_to_parent[loky] ___________

backend = 'loky'

    @with_numpy
    @with_multiprocessing
    @parametrize("backend", ["multiprocessing", "loky"])
    def test_permission_error_windows_memmap_sent_to_parent(backend):
        # Second non-regression test for:
        # https://github.com/joblib/joblib/issues/806
        # previously, child process would not convert temporary memmaps to numpy
        # arrays when sending the data back to the parent process. This would lead
        # to permission errors on windows when deleting joblib's temporary folder,
        # as the memmaped files handles would still opened in the parent process.
        cmd = '''if 1:
            import os
            import time
    
            import numpy as np
    
            from joblib import Parallel, delayed
            from testutils import return_slice_of_data
    
            data = np.ones(int(2e6))
    
            if __name__ == '__main__':
                # warm-up call to launch the workers and start the resource_tracker
                _ = Parallel(n_jobs=2, verbose=5, backend='{b}')(
                    delayed(id)(i) for i in range(20))
    
                time.sleep(0.5)
    
                slice_of_data = Parallel(n_jobs=2, verbose=5, backend='{b}')(
                    delayed(return_slice_of_data)(data, 0, 20) for _ in range(10))
        '''.format(b=backend)
    
        for _ in range(3):
            env = os.environ.copy()
            env['PYTHONPATH'] = os.path.dirname(__file__)
            p = subprocess.Popen([sys.executable, '-c', cmd],
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE, env=env)
            p.wait()
            out, err = p.communicate()
            assert p.returncode == 0, err
            assert out == b''
            if sys.version_info[:3] not in [(3, 8, 0), (3, 8, 1)]:
                # In early versions of Python 3.8, a reference leak
                # https://github.com/cloudpipe/cloudpickle/issues/327, holds
                # references to pickled objects, generating race condition during
                # cleanup finalizers of joblib and noisy resource_tracker outputs.
>               assert b'resource_tracker' not in err
E               assert b'resource_tracker' not in b'[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.\n[Parallel(n_jobs=2)]: Done  20 out of  2...14/multiprocessing/resource_tracker.py", line 116, in _stop_locked\nChildProcessError: [Errno 10] No child processes\n'

joblib/test/test_memmapping.py:441: AssertionError
_______ test_multithreaded_parallel_termination_resource_tracker_silent ________

    @with_numpy
    @with_multiprocessing
    def test_multithreaded_parallel_termination_resource_tracker_silent():
        # test that concurrent termination attempts of a same executor does not
        # emit any spurious error from the resource_tracker. We test various
        # situations making 0, 1 or both parallel call sending a task that will
        # make the worker (and thus the whole Parallel call) error out.
        cmd = '''if 1:
            import os
            import numpy as np
            from joblib import Parallel, delayed
            from joblib.externals.loky.backend import resource_tracker
            from concurrent.futures import ThreadPoolExecutor, wait
    
            resource_tracker.VERBOSE = 0
    
            array = np.arange(int(1e2))
    
            temp_dirs_thread_1 = set()
            temp_dirs_thread_2 = set()
    
    
            def raise_error(array):
                raise ValueError
    
    
            def parallel_get_filename(array, temp_dirs):
                with Parallel(backend="loky", n_jobs=2, max_nbytes=10) as p:
                    for i in range(10):
                        [filename] = p(
                            delayed(getattr)(array, "filename") for _ in range(1)
                        )
                        temp_dirs.add(os.path.dirname(filename))
    
    
            def parallel_raise(array, temp_dirs):
                with Parallel(backend="loky", n_jobs=2, max_nbytes=10) as p:
                    for i in range(10):
                        [filename] = p(
                            delayed(raise_error)(array) for _ in range(1)
                        )
                        temp_dirs.add(os.path.dirname(filename))
    
    
            executor = ThreadPoolExecutor(max_workers=2)
    
            # both function calls will use the same loky executor, but with a
            # different Parallel object.
            future_1 = executor.submit({f1}, array, temp_dirs_thread_1)
            future_2 = executor.submit({f2}, array, temp_dirs_thread_2)
    
            # Wait for both threads to terminate their backend
            wait([future_1, future_2])
    
            future_1.result()
            future_2.result()
        '''
        functions_and_returncodes = [
            ("parallel_get_filename", "parallel_get_filename", 0),
            ("parallel_get_filename", "parallel_raise", 1),
            ("parallel_raise", "parallel_raise", 1)
        ]
    
        for f1, f2, returncode in functions_and_returncodes:
            p = subprocess.Popen([sys.executable, '-c', cmd.format(f1=f1, f2=f2)],
                                 stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            p.wait()
            out, err = p.communicate()
            assert p.returncode == returncode, out.decode()
>           assert b"resource_tracker" not in err, err.decode()
E           AssertionError: Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7f3d56ca2f00>:
E             Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7fd5458bef00>:
E             Traceback (most recent call last):
E             Traceback (most recent call last):
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 82, in __del__
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 82, in __del__
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 91, in _stop
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 91, in _stop
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 116, in _stop_locked
E               File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 116, in _stop_locked
E             ChildProcessError: [Errno 10] No child processes
E             ChildProcessError: [Errno 10] No child processes
E             
E           assert b'resource_tracker' not in b'Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7f3d56ca2f00>:\nException ignored... in _stop_locked\nChildProcessError: [Errno 10] No child processes\nChildProcessError: [Errno 10] No child processes\n'

joblib/test/test_memmapping.py:585: AssertionError
________________ test_many_parallel_calls_on_same_object[loky] _________________

backend = 'loky'

    @with_numpy
    @with_multiprocessing
    @parametrize("backend", ["multiprocessing", "loky"])
    def test_many_parallel_calls_on_same_object(backend):
        # After #966 got merged, consecutive Parallel objects were sharing temp
        # folder, which would lead to race conditions happening during the
        # temporary resources management with the resource_tracker. This is a
        # non-regression test that makes sure that consecutive Parallel operations
        # on the same object do not error out.
        cmd = '''if 1:
            import os
            import time
    
            import numpy as np
    
            from joblib import Parallel, delayed
            from testutils import return_slice_of_data
    
            data = np.ones(100)
    
            if __name__ == '__main__':
                for i in range(5):
                    slice_of_data = Parallel(
                        n_jobs=2, max_nbytes=1, backend='{b}')(
                            delayed(return_slice_of_data)(data, 0, 20)
                            for _ in range(10)
                        )
        '''.format(b=backend)
        env = os.environ.copy()
        env['PYTHONPATH'] = os.path.dirname(__file__)
        p = subprocess.Popen(
            [sys.executable, '-c', cmd],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            env=env,
        )
        p.wait()
        out, err = p.communicate()
        assert p.returncode == 0, err
        assert out == b''
        if sys.version_info[:3] not in [(3, 8, 0), (3, 8, 1)]:
            # In early versions of Python 3.8, a reference leak
            # https://github.com/cloudpipe/cloudpickle/issues/327, holds
            # references to pickled objects, generating race condition during
            # cleanup finalizers of joblib and noisy resource_tracker outputs.
>           assert b'resource_tracker' not in err
E           assert b'resource_tracker' not in b'Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7f80a71caf00>:\nTraceback (most r...14/multiprocessing/resource_tracker.py", line 116, in _stop_locked\nChildProcessError: [Errno 10] No child processes\n'

joblib/test/test_memmapping.py:633: AssertionError
___________ test_resource_tracker_silent_when_reference_cycles[loky] ___________

backend = 'loky'

    @with_numpy
    @with_multiprocessing
    @parametrize("backend", ["multiprocessing", "loky"])
    def test_resource_tracker_silent_when_reference_cycles(backend):
        # There is a variety of reasons that can make joblib with loky backend
        # output noisy warnings when a reference cycle is preventing a memmap from
        # being garbage collected. Especially, joblib's main process finalizer
        # deletes the temporary folder if it was not done before, which can
        # interact badly with the resource_tracker. We don't risk leaking any
        # resources, but this will likely make joblib output a lot of low-level
        # confusing messages.
        #
        # This test makes sure that the resource_tracker is silent when a reference
        # has been collected concurrently on non-Windows platforms.
        #
        # Note that the script in ``cmd`` is the exact same script as in
        # test_permission_error_windows_reference_cycle.
        if backend == "loky" and sys.platform.startswith('win'):
            # XXX: on Windows, reference cycles can delay timely garbage collection
            # and make it impossible to properly delete the temporary folder in the
            # main process because of permission errors.
            pytest.xfail(
                "The temporary folder cannot be deleted on Windows in the "
                "presence of a reference cycle"
            )
    
        cmd = """if 1:
            import numpy as np
            from joblib import Parallel, delayed
    
    
            data = np.random.rand(int(2e6)).reshape((int(1e6), 2))
    
            # Build a complex cyclic reference that is likely to delay garbage
            # collection of the memmapped array in the worker processes.
            first_list = current_list = [data]
            for i in range(10):
                current_list = [current_list]
            first_list.append(current_list)
    
            if __name__ == "__main__":
                results = Parallel(n_jobs=2, backend="{b}")(
                    delayed(len)(current_list) for i in range(10))
                assert results == [1] * 10
        """.format(b=backend)
        p = subprocess.Popen([sys.executable, '-c', cmd], stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE)
        p.wait()
        out, err = p.communicate()
        out = out.decode()
        err = err.decode()
        assert p.returncode == 0, out + "\n\n" + err
>       assert "resource_tracker" not in err, err
E       AssertionError: Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7fde61daaf00>:
E         Traceback (most recent call last):
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 82, in __del__
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 91, in _stop
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 116, in _stop_locked
E         ChildProcessError: [Errno 10] No child processes
E         Exception ignored while calling deallocator <function ResourceTracker.__del__ at 0x7f72d40cef00>:
E         Traceback (most recent call last):
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 82, in __del__
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 91, in _stop
E           File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 116, in _stop_locked
E         ChildProcessError: [Errno 10] No child processes
E         
E       assert 'resource_tracker' not in 'Exception i... processes\n'
E         
E         'resource_tracker' is contained here:
E           rocessing/resource_tracker.py", line 82, in __del__
E         ?           ++++++++++++++++
E             File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 91, in _stop
E             File "/usr/lib64/python3.14/multiprocessing/resource_tracker.py", line 116, in _stop_locked
E           ChildProcessError: [Errno 10] No child processes...
E         
E         ...Full output truncated (6 lines hidden), use '-vv' to show

https://docs.python.org/3.14/whatsnew/3.14.html

For the build logs, see:
https://copr-be.cloud.fedoraproject.org/results/@python/python3.14-b1/fedora-rawhide-x86_64/09100660-python-joblib/

For all our attempts to build python-joblib with Python 3.14, see:
https://copr.fedorainfracloud.org/coprs/g/python/python3.14-b1/package/python-joblib/

Testing and mass rebuild of packages is happening in copr.
You can follow these instructions to test locally in mock if your package builds with Python 3.14:
https://copr.fedorainfracloud.org/coprs/g/python/python3.14-b1/

Let us know here if you have any questions.

Python 3.14 is planned to be included in Fedora 43.
To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.14.
A build failure prevents us from testing all dependent packages (transitive [Build]Requires),
so if this package is required a lot, it's important for us to get it fixed soon.

We'd appreciate help from the people who know this package best,
but if you don't want to work on this now, let us know so we can try to work around it on our side.

Comment 1 Karolina Surma 2025-05-29 10:32:48 UTC
This, in fact, is not only related to Python 3.14 - this happens also with Python 3.13.3, see koschei: https://koschei.fedoraproject.org/package/python-joblib


Note You need to log in before you can comment on or make changes to this bug.