Skip to content

Make use_original thread-local to fix cross-thread races#1318

Open
jfindlay wants to merge 2 commits into
pytest-dev:mainfrom
jfindlay:thread-local-use-original
Open

Make use_original thread-local to fix cross-thread races#1318
jfindlay wants to merge 2 commits into
pytest-dev:mainfrom
jfindlay:thread-local-use-original

Conversation

@jfindlay
Copy link
Copy Markdown
Contributor

[This bug was found, isolated, fixed, and reported by Claude Opus 4.7, with some minor edits by me. You are welcome to update or adapt as needed.]

Fixes #1317.

Describe the changes

The use_original flag on FakeOsModule was a process-global class attribute toggled by the use_original_os() context manager. When one thread entered the context manager while another thread was in the middle of a faked filesystem operation, the faked call could observe use_original=True and dispatch to the real os module, failing against paths that only exist in the fake filesystem.

Store use_original in a threading.local and have use_original_os() save and restore the previous thread-local value so nested uses continue to work. The class attribute becomes an instance property, preserving the existing instance.use_original get/set API.

A regression test in fake_filesystem_unittest_test exercises the concurrent-worker pattern with explicit hammer threads entering use_original_os() alongside asyncio.to_thread workers; without the fix it fails reliably with PermissionError against the fake root.

Tasks

  • Unit tests added that reproduce the issue or prove feature is working
  • Fix or feature added
  • Entry to release notes added
  • Pre-commit CI shows no errors
  • Unit tests passing
  • [-] For documentation changes: The Read the Docs preview builds and looks as expected

The use_original flag on FakeOsModule was a process-global class
attribute toggled by the use_original_os() context manager. When one
thread entered the context manager while another thread was in the
middle of a faked filesystem operation, the faked call could observe
use_original=True and dispatch to the real os module, failing against
paths that only exist in the fake filesystem.

Store use_original in a threading.local and have use_original_os()
save and restore the previous thread-local value so nested uses
continue to work.  The class attribute becomes an instance property,
preserving the existing instance.use_original get/set API.

A regression test in fake_filesystem_unittest_test exercises the
concurrent-worker pattern with explicit hammer threads entering
use_original_os() alongside asyncio.to_thread workers; without the
fix it fails reliably with PermissionError against the fake root.

Fixes pytest-dev#1317.
@jfindlay jfindlay force-pushed the thread-local-use-original branch from 5a0202f to 0bb1cff Compare April 23, 2026 04:50
@mrbean-bremen
Copy link
Copy Markdown
Member

"Unit tests passing" is not completely true - the new test is failing in one build. I haven't looked into it yet, may have a closer look tonight...

@mrbean-bremen
Copy link
Copy Markdown
Member

Ok, I can reproduce the test failure locally under WSL/ubuntu with pypy 3.10, and it looks like this is another threading issue related to the use of heapq. Probably one of the things you had already found...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FakeOsModule.use_original is not thread-safe under asyncio.to_thread workloads

2 participants