Skip to content

gh-81441: shutil.rmtree() FileNotFoundError race condition #14064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Dec 5, 2023

Conversation

websurfer5
Copy link
Contributor

@websurfer5 websurfer5 commented Jun 13, 2019

shutil.rmtree() is susceptible to a race condition that can needlessly raise OSError:

  1. os.scandir() returns the list of entries in a directory
  2. while iterating over the list, another thread or process deletes one or more of the entries not yet iterated
  3. os.unlink(), stat(), or scandir() raises OSError for the already deleted entry

The fix is to catch and ignore FileNotFoundError exceptions for all but the top-level path passed to shutil.rmtree(). This provides the same behavior as "rm -r".

I added an explicit check for path existence at the beginning of shutil.rmtree() so that it raises FileNotFoundError immediately to replace the previous implicit checks that are no longer considered errors.

https://bugs.python.org/issue37260

directory entries in shutil.rmtree(), with tests
@mangrisano
Copy link
Contributor

websurfer5 and others added 2 commits June 25, 2019 13:01
@hauntsaninja hauntsaninja changed the title bpo-37260: shutil.rmtree() FileNotFoundError race condition gh-81441: shutil.rmtree() FileNotFoundError race condition Feb 9, 2023
@hash-data
Copy link

hash-data commented Nov 27, 2023

Hi, are you guys planning to merge this?

@serhiy-storchaka
Copy link
Member

Tests did not work because the order of deletion is unspecified, and other file or directory was already deleted when the error handler was called. Now the error handler is called on the first attempt to delete, and it deletes at least one file and directory and keeps at least one file and directory, so many cases are covered.

@serhiy-storchaka serhiy-storchaka enabled auto-merge (squash) December 5, 2023 09:17
@serhiy-storchaka serhiy-storchaka merged commit 268415b into python:main Dec 5, 2023
aisk pushed a commit to aisk/cpython that referenced this pull request Feb 11, 2024
…honGH-14064)

Ignore missing files and directories while enumerating
directory entries in shutil.rmtree().

Co-authored-by: Serhiy Storchaka <[email protected]>
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Apr 16, 2024
Summary:
When the `daemon stop` command in called in the `fulcrum_service`'s tear-down, it returns before all the the threads are terminated. In particular the wallet files still need to be saved before the daemon process exits. This causes a race condition when the test fixture deletes the data directory while `WalletStorage._write` creates a tmp file then deletes it. See python/cpython#14064 for a description of how `shutil.rmtree` ends up raising a `FileNotFoundError`.

This issue will go away in Python 3.13 when `shutil.rmtree` no longer fails because it failed to delete an already deleted file. In the meantime ignore these failures.

I first tried a different approach: wait for the daemon process to actually terminate before deleting the data. However detecting the correct deamon process is not trivial, and if done wrong could cause more flakiness.

Test Plan: `pytest electrumabc/tests/regtest`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D15989
Fabcien pushed a commit to Bitcoin-ABC/ElectrumABC that referenced this pull request Apr 17, 2024
Summary:
When the `daemon stop` command in called in the `fulcrum_service`'s tear-down, it returns before all the the threads are terminated. In particular the wallet files still need to be saved before the daemon process exits. This causes a race condition when the test fixture deletes the data directory while `WalletStorage._write` creates a tmp file then deletes it. See python/cpython#14064 for a description of how `shutil.rmtree` ends up raising a `FileNotFoundError`.

This issue will go away in Python 3.13 when `shutil.rmtree` no longer fails because it failed to delete an already deleted file. In the meantime ignore these failures.

I first tried a different approach: wait for the daemon process to actually terminate before deleting the data. However detecting the correct deamon process is not trivial, and if done wrong could cause more flakiness.

Test Plan: `pytest electrumabc/tests/regtest`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D15989
github-cerc-io pushed a commit to cerc-io/stack-orchestrator that referenced this pull request Aug 28, 2024
github-cerc-io pushed a commit to cerc-io/stack-orchestrator that referenced this pull request Aug 28, 2024
Otherwise we sometimes see errors like:

```
cerc-webapp-deployer:   File "/root/.shiv/laconic-so_0f937aa98c2748ef9af8585d6f441dbc01546ace0d6660cbb159d1e5040aeddf/site-packages/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py", line 671, in command
cerc-webapp-deployer:     shutil.rmtree(tempdir)
cerc-webapp-deployer:   File "/usr/lib/python3.10/shutil.py", line 725, in rmtree
cerc-webapp-deployer:     _rmtree_safe_fd(fd, path, onerror)
cerc-webapp-deployer:   File "/usr/lib/python3.10/shutil.py", line 681, in _rmtree_safe_fd
cerc-webapp-deployer:     onerror(os.unlink, fullname, sys.exc_info())
cerc-webapp-deployer:   File "/usr/lib/python3.10/shutil.py", line 679, in _rmtree_safe_fd
cerc-webapp-deployer:     os.unlink(entry.name, dir_fd=topfd)
cerc-webapp-deployer: FileNotFoundError: [Errno 2] No such file or directory: 'S.gpg-agent.extra'
```

Reviewed-on: https://git.vdb.to/cerc-io/stack-orchestrator/pulls/941
Co-authored-by: Thomas E Lackey <[email protected]>
Co-committed-by: Thomas E Lackey <[email protected]>
Glyphack pushed a commit to Glyphack/cpython that referenced this pull request Sep 2, 2024
…honGH-14064)

Ignore missing files and directories while enumerating
directory entries in shutil.rmtree().

Co-authored-by: Serhiy Storchaka <[email protected]>
mped-oticon pushed a commit to ArtifactLabs/git-recycle-bin that referenced this pull request Nov 4, 2024
Python's shutil.rmtree() has a FileNotFoundError race condition, which
has only been recently fixed upstream as of 3.11.8; see [1].

We have run into this race during FLOW-219, so we must workaround it since
a very modern python can't be assumed.

The race seems to be occur about git's garbage collection lock file
since it only exists temporarily. rbgit is already ephemeral, so we
don't even need GC.

[1] python/cpython#14064
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.

7 participants