Skip to content

Release free-threaded thread-state attachment can clobber errno and cause spurious directory I/O EAGAIN #161769

Description

@Robolightning

Bug report

On a release free-threaded build, ordinary directory operations can raise
BlockingIOError: [Errno 11] Resource temporarily unavailable while other
threads in real subinterpreters collect garbage. A fresh FileFinder for an
existing .py file can fail in the same way. No third-party extension is
needed.

_PyThreadState_Attach() already saves and restores errno, but only under
Py_DEBUG. Making that existing preservation unconditional fixes the same
public regression on current 3.15 and main, without changing the IO functions
or their error checks. The native change removes four preprocessor lines.

The proposed regression runs os.listdir, os.scandir and a newly created
importlib.machinery.FileFinder concurrently with child-local gc.collect().
It creates and closes real concurrent.interpreters children. It does not
replace a loader, prime import caches, retry a failed operation, force the GIL,
or use a native fault injector. A local temporary directory contains a valid
fixture.py and 32 ordinary files throughout the run.

Reproduction

The attached source patch adds
test.test_free_threading.test_os.TestOs.test_directory_io_during_subinterpreter_gc.
Apply only its test-file hunk to the unmodified source, build with
--disable-gil --enable-shared and without --with-pydebug, then run:

./python -I -B -m unittest -v test.test_free_threading.test_os.TestOs.test_directory_io_during_subinterpreter_gc

Before the pystate.c change, the assertion reports real
BlockingIOError(11, 'Resource temporarily unavailable') entries. After
removing the two Py_DEBUG conditionals around the existing save/restore,
the identical test passes. The full standard-library test suites listed below
also pass. These are local native source builds, not a claim about released
binary distributions.

Official source Before After IO/GC/threading/import JUnit cases
3.15 be7dbf98e7d5227f14bc73d8b3f422f60c431d60 FAIL, real EAGAIN PASS 2100, 0 failures/errors, 163 existing skips
main 9d4a35c494cb1cfd9c77c619b22bd425695316c2 FAIL, real EAGAIN PASS 2120, 0 failures/errors, 167 existing skips

The original suites were test_free_threading.test_os, test_os, test_gc,
test_threading and test_importlib, including their collected submodules.
Linux musl x86_64, release free-threaded builds; Py_DEBUG=0,
Py_GIL_DISABLED=1, no PYTHON_GIL override, sys.flags.gil is None and the
GIL stays off. PYTHON_JIT=1 was requested; these builds have no JIT available.

A separate all-rounds public API probe recorded 20675 failed cases out of
65536 on the original 3.15.2 release core, and zero on the source-corrected
core. The regression above is also independently reproduced on current
official branch sources, so the report does not depend on that older core.

CPython versions tested on

3.15, CPython main branch (3.16)

Operating systems tested on

Linux (native musl x86_64)

Source patch and public regression

Native change and regression test. The change is proposed against main; the source/runtime evidence above includes an independent 3.15 backport.

AI assistance was used to investigate the native lifetime and prepare the patch, regression, and report. All reported before/after results were executed against real native source builds; no simulated results are included.

Linked PRs

Activity

  1. added 3 commits that reference this issue on Nov 10, 2026
  2. added
    type-bugAn unexpected behavior, bug, or error
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    on Nov 11, 2026
  3. added a commit that references this issue on Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions