-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathwin_retry.py
More file actions
41 lines (34 loc) · 1.59 KB
/
Copy pathwin_retry.py
File metadata and controls
41 lines (34 loc) · 1.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"""Retry helper for Windows sharing-violation races.
Windows raises ``PermissionError`` (not a silent success, unlike POSIX) when a
file operation collides with another process briefly holding a handle open --
AV, the Search indexer, or BlueStacks itself. This is a real, expected
transient state, not a hard failure, so operations that can hit it (an
``os.replace`` over a config file another process might glance at, a VHD
attach right after killing BlueStacks) should retry with backoff rather than
fail on the first collision.
"""
from __future__ import annotations
import logging
import time
from typing import Callable, TypeVar
logger = logging.getLogger(__name__)
T = TypeVar("T")
def retry_on_sharing_violation(fn: Callable[[], T], *, attempts: int = 5,
base_delay: float = 0.1, label: str = "operation") -> T:
"""Call ``fn()``, retrying on ``PermissionError`` with linear backoff.
Raises the last ``PermissionError`` if every attempt fails. Any other
exception from ``fn`` propagates immediately, unretried -- this only
absorbs the specific "someone else briefly has a handle open" case.
"""
last_exc: PermissionError | None = None
for attempt in range(1, attempts + 1):
try:
return fn()
except PermissionError as exc:
last_exc = exc
if attempt < attempts:
delay = base_delay * attempt
logger.debug("%s: sharing violation (attempt %d/%d), retrying in %.2fs",
label, attempt, attempts, delay)
time.sleep(delay)
raise last_exc