beeware / briefcase

Tools to support converting a Python project into a standalone native application.
https://briefcase.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
2.61k stars 366 forks source link

On Windows, renaming a directory that was just created can fail with `PermissionError: [WinError 5] Access denied` #1780

Open rmartin16 opened 5 months ago

rmartin16 commented 5 months ago

Describe the bug

Using os.rename() (or pathlib.Path.rename()) can fail on Windows if Briefcase does not have exclusive access to all files in the directory.

A common pattern Briefcase uses is to run shutil.unpack_archive() and rename the unpacked directory; for instance, Android cmdline-tools and Java's JDK. In between these two operations, a background process may acquire a handle on a file; for instance, a Windows file indexer or an antivirus scanner.

Modern Linux file systems are much more resilient to these types of conflicts; so, I would only expect to see this on Windows.

Steps to reproduce

import os
from pathlib import Path

Path("orig-dir-1").mkdir()
Path("orig-dir-1/orig-file").touch()
Path("orig-dir-1").rename("new-dir-1")
os.listdir("new-dir-1")

Path("orig-dir-2").mkdir()
Path("orig-dir-2/orig-file").touch()
Path("orig-dir-2/orig-file").open()
Path("orig-dir-2").rename("new-dir-2")
❯ python
Python 3.11.3 (tags/v3.11.3:f3909b8, Apr  4 2023, 23:49:59) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> from pathlib import Path
>>>
>>> Path("orig-dir-1").mkdir()
>>> Path("orig-dir-1/orig-file").touch()
>>> Path("orig-dir-1").rename("new-dir-1")
WindowsPath('new-dir-1')
>>> os.listdir("new-dir-1")
['orig-file']
>>>
>>> Path("orig-dir-2").mkdir()
>>> Path("orig-dir-2/orig-file").touch()
>>> Path("orig-dir-2/orig-file").open()
<_io.TextIOWrapper name='orig-dir-2\\orig-file' mode='r' encoding='cp1252'>
>>> Path("orig-dir-2").rename("new-dir-2")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\user\.pyenv\pyenv-win\versions\3.11.3\Lib\pathlib.py", line 1175, in rename
    os.rename(self, target)
PermissionError: [WinError 5] Access is denied: 'orig-dir-2' -> 'new-dir-2'
>>>

Expected behavior

Renaming these directories in the Briefcase cache should be successful.

Screenshots

No response

Environment

Logs

No response

Additional context

Origin: https://github.com/beeware/beeware/pull/348

freakboy3742 commented 4 months ago

See also #394 for a tangentially related issue.

mhsmith commented 4 months ago

Unfortunately this is a common issue on WIndows, and I don't think there's any workaround except putting all renames and deletions in a retry loop. There are packages such as tenacity which make this easier.

spa51273 commented 4 months ago

pycon24, taking a look at this. Thanks!

Deepak6546kumar commented 4 months ago

Path("orig-dir-1").mkdir() Path("orig-dir-1/orig-file").touch() Path("orig-dir-1").rename("new-dir-1") os.listdir("new-dir-1")

Path("orig-dir-2").mkdir() Path("orig-dir-2/orig-file").touch() Path("orig-dir-2/orig-file").open() Path("orig-dir-2").rename("new-dir-2")

Path("orig-dir-2").mkdir() Path("orig-dir-2/orig-file").touch() Path("orig-dir-2/orig-file").open() Path("orig-dir-2").rename("new-dir-3")

Path("orig-dir-2").mkdir() Path("orig-dir-2/orig-file").touch() Path("orig-dir-2/orig-file").open() Path("orig-dir-2").rename("new-dir-4")

freakboy3742 commented 4 months ago

@Deepak6546kumar It's not clear what you're trying to communicate with this code sample. We already had a replication case; what extra information are you providing here?