Skip to content

Commit

Permalink
Re-created patch python-poetry#1032.
Browse files Browse the repository at this point in the history
When deleting temporary directories on Windows try a few times so we get around a Windows error, see issue python-poetry#1031 for more details.
  • Loading branch information
eblis committed Feb 9, 2022
1 parent 1196923 commit 2b820cc
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
26 changes: 24 additions & 2 deletions src/poetry/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
import stat
import tempfile
import time

from collections.abc import Mapping
from contextlib import contextmanager
Expand Down Expand Up @@ -45,7 +46,7 @@ def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]:

yield name

shutil.rmtree(name, onerror=_del_ro)
robust_rmtree(name, onerror=_del_ro)


def get_cert(config: "Config", repository_name: str) -> Optional[Path]:
Expand All @@ -72,11 +73,32 @@ def _on_rm_error(func: Callable, path: str, exc_info: Exception) -> None:
func(path)


def robust_rmtree(path: str, onerror=None, max_timeout=1) -> None:
"""
Robustly tries to delete paths.
Retries several times if an OSError occurs.
If the final attempt fails, the Exception is propagated
to the caller.
"""
timeout = 0.001
while timeout < max_timeout:
try:
shutil.rmtree(path)
return # Only hits this on success
except OSError:
# Increase the timeout and try again
time.sleep(timeout)
timeout *= 2

# Final attempt, pass any Exceptions up to caller.
shutil.rmtree(path, onerror=onerror)


def safe_rmtree(path: str) -> None:
if Path(path).is_symlink():
return os.unlink(str(path))

shutil.rmtree(path, onerror=_on_rm_error)
shutil.rmtree(path, onerror=_on_rm_error) # maybe we could call robust_rmtree here just in case ?


def merge_dicts(d1: Dict, d2: Dict) -> None:
Expand Down
20 changes: 20 additions & 0 deletions tests/utils/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from poetry.core.utils.helpers import parse_requires
from src.poetry.utils.helpers import robust_rmtree

from poetry.utils.helpers import canonicalize_name
from poetry.utils.helpers import get_cert
Expand All @@ -14,6 +16,24 @@
from tests.conftest import Config


def test_robust_rmtree(mocker):
mocked_rmtree = mocker.patch('shutil.rmtree')

# this should work after an initial exception
name = tempfile.mkdtemp()
mocked_rmtree.side_effect = [OSError("Couldn't delete file yet, waiting for references to clear", "mocked path"), None]
robust_rmtree(name)

# this should give up after retrying multiple times
name = tempfile.mkdtemp()
mocked_rmtree.side_effect = OSError("Couldn't delete file yet, this error won't go away after first attempt")
with pytest.raises(OSError):
robust_rmtree(name, max_timeout=0.04)

# clear the side effect (breaks the tear-down otherwise)
mocked_rmtree.side_effect = None


def test_parse_requires():
requires = """\
jsonschema>=2.6.0.0,<3.0.0.0
Expand Down

0 comments on commit 2b820cc

Please sign in to comment.