Repository navigation
gh-152754: Fix crash when an os.scandir iterator is shared between threads - #153462
Conversation
…een threads Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
This race is not free-threading specific.
This makes it not safe to share iterators between multiple threads so document that.
Documentation build overview
34 files changed ·
|
|
Note, I revised this PR to no longer try to be thread-safe with multiple threads using the same iterator. That seems a pretty unlikely usage scenario. Instead, this avoids crashing and/or memory corruption. |
|
Thanks @nascheme for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14. |
|
Thanks @nascheme for the PR 🌮🎉.. I'm working now to backport this PR to: 3.15. |
|
Sorry, @nascheme, I could not cleanly backport this to Please backport manually with cherry_picker, see the devguide for more information. |
|
GH-159365 is a backport of this pull request to the 3.15 branch. |
|
GH-159381 is a backport of this pull request to the 3.14 branch. |
The issue was a race between
ScandirIterator_iternext()andScandirIterator.close(): one thread could be insidereaddir()orFindNextFileW()while another closed the underlying directory stream or Windows handle.Changes
PyMutexand atomic closed state toScandirIteratorin all builds.close()mark the iterator closed immediately without waiting for an in-progress directory read. The reading thread performs the native close afterward when necessary.Py_BEGIN_ALLOW_THREADSaroundreaddir(),FindNextFileW(),closedir(), andFindClose().ResourceWarningwhen opening the directory fails.close(), and failed iterator creation.scandir()iterator between threads.Threading semantics
Sharing an
os.scandir()iterator between threads will no longer corrupt the iterator or crash. Concurrent calls may consume entries in any order, so which thread receives each entry remains unspecified. Callingclose()while another thread is iterating marks the iterator closed immediately and ends iteration early.A
PyMutexis used rather than a critical section because the protection must remain active acrossPy_BEGIN_ALLOW_THREADSwhile the native directory operation runs without the GIL.os.scandiriterator'sclose()and__next__()on thedirphandle #152754