Bug 2466672

Summary: python-futurist fails to build with Python 3.15: 3 tests failures with FileNotFoundError: [Errno 2] No such file or directory
Product: [Fedora] Fedora Reporter: Karolina Surma <ksurma>
Component: python-futuristAssignee: Javier Peña <jpena>
Status: CLOSED ERRATA QA Contact: Fedora Extras Quality Assurance <extras-qa>
Severity: unspecified Docs Contact:
Priority: unspecified    
Version: rawhideCC: apevec, jpena, jruzicka.pkg, ksurma, mhroncok, openstack-sig, steve.traylen
Target Milestone: ---   
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: python-futurist-3.3.0-3.fc45 Doc Type: ---
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2026-05-18 19:35:14 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On:    
Bug Blocks: 2412434    

Description Karolina Surma 2026-05-05 09:02:07 UTC
python-futurist fails to build with Python 3.15.0a8.

3 tests fail:

{3} futurist.tests.test_executors.TestExecutors.test_run_one(process) [0.002874s] ... FAILED

Captured traceback:
~~~~~~~~~~~~~~~~~~~
    Traceback (most recent call last):

      File "/builddir/build/BUILD/python-futurist-3.3.0-build/futurist-3.3.0/futurist/tests/test_executors.py", line 111, in test_run_one
    fut = self.executor.submit(returns_one)

      File "/builddir/build/BUILD/python-futurist-3.3.0-build/futurist-3.3.0/futurist/_futures.py", line 494, in submit
    return self._gatherer.submit(fn, *args, **kwargs)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^

      File "/builddir/build/BUILD/python-futurist-3.3.0-build/futurist-3.3.0/futurist/_futures.py", line 119, in submit
    fut = self._submit_func(fn, *args, **kwargs)

      File "/usr/lib64/python3.15/concurrent/futures/process.py", line 830, in submit
    self._adjust_process_count()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^

      File "/usr/lib64/python3.15/concurrent/futures/process.py", line 789, in _adjust_process_count
    self._spawn_process()
    ~~~~~~~~~~~~~~~~~~~^^

      File "/usr/lib64/python3.15/concurrent/futures/process.py", line 807, in _spawn_process
    p.start()
    ~~~~~~~^^

      File "/usr/lib64/python3.15/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
                  ~~~~~~~~~~~^^^^^^

      File "/usr/lib64/python3.15/multiprocessing/context.py", line 309, in _Popen
    return Popen(process_obj)

      File "/usr/lib64/python3.15/multiprocessing/popen_forkserver.py", line 35, in __init__
    super().__init__(process_obj)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^

      File "/usr/lib64/python3.15/multiprocessing/popen_fork.py", line 20, in __init__
    self._launch(process_obj)
    ~~~~~~~~~~~~^^^^^^^^^^^^^

      File "/usr/lib64/python3.15/multiprocessing/popen_forkserver.py", line 51, in _launch
    self.sentinel, w = forkserver.connect_to_new_process(self._fds)
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^

      File "/usr/lib64/python3.15/multiprocessing/forkserver.py", line 106, in connect_to_new_process
    client.connect(self._forkserver_address)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^

    FileNotFoundError: [Errno 2] No such file or directory


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

For the build logs, see:
https://copr-be.cloud.fedoraproject.org/results/@python/python3.15/fedora-rawhide-x86_64/10398246-python-futurist/

For all our attempts to build python-futurist with Python 3.15, see:
https://copr.fedorainfracloud.org/coprs/g/python/python3.15/package/python-futurist/

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.15:
https://copr.fedorainfracloud.org/coprs/g/python/python3.15/

Let us know here if you have any questions.

Python 3.15 is planned to be included in Fedora 45.
To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.15.
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 Steve Traylen 2026-05-14 10:29:12 UTC
https://src.fedoraproject.org/rpms/python-futurist/pull-request/2

Really not sure about this...

Comment 2 Steve Traylen 2026-05-14 20:27:36 UTC
These tests have broken also 3.14.4 -> 3.15.5 as well.

https://github.com/python/cpython/issues/140734

feels related.

Comment 3 Steve Traylen 2026-05-14 20:28:44 UTC
(In reply to Steve Traylen from comment #2)
> These tests have broken also 3.14.4 -> 3.15.5 as well.
> 

That should have read.

These tests have broken also 3.14.4 -> 3.14.5 as well.


> https://github.com/python/cpython/issues/140734
> 
> feels related.

Comment 4 Steve Traylen 2026-05-14 21:15:23 UTC
Test case, this now fails with 3.14.5 as well as the new 3.15.

Certainly it does run okay for F44 python 3.14 version.

#!/usr/bin/env python3
import concurrent.futures

def returns_one():
    return 1

if __name__ == '__main__':
    for i in range(10):
        print(f"Run {i}...")
        with concurrent.futures.ProcessPoolExecutor() as executor:
            fut = executor.submit(returns_one)
            assert fut.result() == 1
        print(f"Run {i} ok")

    print("All done")

Comment 5 Steve Traylen 2026-05-14 21:25:59 UTC
This is better.


#!/usr/bin/env python3
"""
Test case for CPython forkserver race condition.
Each iteration shuts down the executor, killing the forkserver,
forcing ensure_running() to restart it for the next iteration.
"""
import concurrent.futures

def returns_one():
    return 1

for i in range(10):
    print(f"Run {i}...")
    with concurrent.futures.ProcessPoolExecutor() as executor:
        fut = executor.submit(returns_one)
        assert fut.result() == 1
    print(f"Run {i} ok")

print("All done")

Comment 6 Steve Traylen 2026-05-14 21:34:45 UTC
This script:

* F45 script fails.
* F44 script runs.



#!/usr/bin/env python3
"""
Test case for CPython forkserver race condition.
Each iteration shuts down the executor, killing the forkserver,
forcing ensure_running() to restart it for the next iteration.
"""
import concurrent.futures

def returns_one():
    return 1

for i in range(10):
    print(f"Run {i}...")
    with concurrent.futures.ProcessPoolExecutor() as executor:
        fut = executor.submit(returns_one)
        assert fut.result() == 1
    print(f"Run {i} ok")

print("All done")

Comment 7 Miro Hrončok 2026-05-15 10:14:45 UTC
Python 3.15.0a8 and 3.15.0b1:

  File "/usr/lib64/python3.15/multiprocessing/spawn.py", line 140, in _check_not_importing_main
    raise RuntimeError('''
    ...<16 lines>...
    ''')
RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        
concurrent.futures.process._RemoteTraceback: 
'''
Process 1203351 terminated abruptly with exit code 1'''

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/churchyard/tmp/script-concurrent.py", line 15, in <module>
    assert fut.result() == 1
           ~~~~~~~~~~^^
  File "/usr/lib64/python3.15/concurrent/futures/_base.py", line 450, in result
    return self.__get_result()
           ~~~~~~~~~~~~~~~~~^^
  File "/usr/lib64/python3.15/concurrent/futures/_base.py", line 395, in __get_result
    raise self._exception
concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.








Python 3.14.4 and 3.14.5:

  File "/usr/lib64/python3.14/multiprocessing/spawn.py", line 140, in _check_not_importing_main
    raise RuntimeError('''
    ...<16 lines>...
    ''')
RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        
Traceback (most recent call last):
  File "/home/churchyard/tmp/script-concurrent.py", line 14, in <module>
    fut = executor.submit(returns_one)
  File "/usr/lib64/python3.14/concurrent/futures/process.py", line 816, in submit
    self._adjust_process_count()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib64/python3.14/concurrent/futures/process.py", line 775, in _adjust_process_count
    self._spawn_process()
    ~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib64/python3.14/concurrent/futures/process.py", line 793, in _spawn_process
    p.start()
    ~~~~~~~^^
  File "/usr/lib64/python3.14/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
                  ~~~~~~~~~~~^^^^^^
  File "/usr/lib64/python3.14/multiprocessing/context.py", line 306, in _Popen
    return Popen(process_obj)
  File "/usr/lib64/python3.14/multiprocessing/popen_forkserver.py", line 35, in __init__
    super().__init__(process_obj)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/multiprocessing/popen_fork.py", line 20, in __init__
    self._launch(process_obj)
    ~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/multiprocessing/popen_forkserver.py", line 51, in _launch
    self.sentinel, w = forkserver.connect_to_new_process(self._fds)
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/usr/lib64/python3.14/multiprocessing/forkserver.py", line 106, in connect_to_new_process
    connection.answer_challenge(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
            wrapped_client, self._forkserver_authkey)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/multiprocessing/connection.py", line 992, in answer_challenge
    message = connection.recv_bytes(256)         # reject large message
  File "/usr/lib64/python3.14/multiprocessing/connection.py", line 223, in recv_bytes
    buf = self._recv_bytes(maxlength)
  File "/usr/lib64/python3.14/multiprocessing/connection.py", line 448, in _recv_bytes
    buf = self._recv(4)
  File "/usr/lib64/python3.14/multiprocessing/connection.py", line 413, in _recv
    chunk = read(handle, to_read)
ConnectionResetError: [Errno 104] Connection reset by peer


Python 3.13.13: all ok.



Adjustment to the script:

import concurrent.futures

def returns_one():
    return 1

if __name__ == '__main__':
    for i in range(10):
        print(f"Run {i}...")
        with concurrent.futures.ProcessPoolExecutor() as executor:
            fut = executor.submit(returns_one)
            assert fut.result() == 1
        print(f"Run {i} ok")




Makes it run on all 3.13, 3.14, 3.15.

Comment 8 Steve Traylen 2026-05-16 21:47:13 UTC
https://src.fedoraproject.org/rpms/python-futurist/pull-request/3 is another fix which is probably not correct still, at least its easy to understand.

Comment 9 Steve Traylen 2026-05-17 13:43:11 UTC
Too much I don't understand here.

So there's something in https://github.com/testing-cabal/fixtures/blob/master/fixtures/_fixtures/tempdir.py which is messing up TMPDIR 

As for the following script  it does produce exactly the same error message but I'm not sure if I have just constructed something obscure
that produces the same error. It certainly seems like something to avoid in the first place.

good on 3.12 and 3.13
fails on 3.14 and 3.15 , 



"""
Reproducer for forkserver stale address bug.

When a forkserver is started in a subprocess and that subprocess exits,
the forkserver dies but its socket address can be inherited by another
process. If that other process then tries to use ProcessPoolExecutor
with the default forkserver start method, it fails with FileNotFoundError
because it tries to connect to the stale socket address rather than
starting a new forkserver.
"""
import multiprocessing
import multiprocessing.forkserver
from concurrent.futures import ProcessPoolExecutor


def returns_one():
    return 1


def start_forkserver_and_exit():
    """Start a forkserver in this process then exit, leaving address in /tmp/fs_addr."""
    ctx = multiprocessing.get_context('forkserver')
    p = ctx.Process(target=returns_one)
    p.start()
    p.join()
    with open('/tmp/fs_addr', 'w') as f:
        f.write(multiprocessing.forkserver._forkserver._forkserver_address)


if __name__ == '__main__':
    # Start forkserver in a child process, then let that process exit
    p = multiprocessing.Process(target=start_forkserver_and_exit)
    p.start()
    p.join()

    # Simulate inheriting the stale forkserver address (as stestr workers do)
    with open('/tmp/fs_addr') as f:
        addr = f.read()
    print(f"Injecting stale forkserver address: {addr}")
    multiprocessing.forkserver._forkserver._forkserver_address = addr

    # Fails with FileNotFoundError - should restart forkserver instead
    with ProcessPoolExecutor() as e:
        print(e.submit(returns_one).result())

Comment 10 Steve Traylen 2026-05-17 17:55:25 UTC
# Is a closer match to what's going on.

import multiprocessing
import multiprocessing.forkserver
import tempfile
import shutil
from concurrent.futures import ProcessPoolExecutor


def returns_one():
    return 1


if __name__ == '__main__':
    # Simulate what NestedTempfile does - redirect tempfile to a temp dir
    tmpdir = tempfile.mkdtemp()
    tempfile.tempdir = tmpdir

    # Start the forkserver - it creates its socket inside tmpdir
    with ProcessPoolExecutor() as e:
        assert e.submit(returns_one).result() == 1

    # Simulate NestedTempfile teardown - delete the temp dir
    tempfile.tempdir = None
    shutil.rmtree(tmpdir)

    # Now try to use ProcessPoolExecutor again - forkserver address is stale
    with ProcessPoolExecutor() as e:
        print(e.submit(returns_one).result())

Comment 11 Fedora Update System 2026-05-18 19:30:23 UTC
FEDORA-2026-2fa7e69931 (python-futurist-3.3.0-3.fc45) has been submitted as an update to Fedora 45.
https://bodhi.fedoraproject.org/updates/FEDORA-2026-2fa7e69931

Comment 12 Fedora Update System 2026-05-18 19:35:14 UTC
FEDORA-2026-2fa7e69931 (python-futurist-3.3.0-3.fc45) has been pushed to the Fedora 45 stable repository.
If problem still persists, please make note of it in this bug report.