2020import abc
2121import collections .abc
2222import contextlib
23+ import errno
2324import hashlib
2425import os
2526import pickle
@@ -1110,8 +1111,14 @@ def _stat_and_read_with_sharing_retry(path: Path) -> tuple[os.stat_result, bytes
11101111 transient contention from being mistaken for a real read failure.
11111112
11121113 Raises ``FileNotFoundError`` on miss or after exhausting the Windows
1113- sharing-retry budget. Other ``PermissionError`` (real ACL/permission
1114- issues) and any non-Windows ``PermissionError`` propagate.
1114+ sharing-retry budget. Non-Windows ``PermissionError`` propagates.
1115+
1116+ On Windows, EACCES (errno 13) is treated as transient too: ``io.open``
1117+ sometimes surfaces a pending-delete or share-mode mismatch as bare
1118+ EACCES with no ``winerror`` attribute, indistinguishable here from
1119+ a true sharing violation. Real ACL problems on a path the cache owns
1120+ would surface consistently; the bounded retry budget keeps the cost
1121+ of treating them as transient negligible.
11151122 """
11161123 last_exc : BaseException | None = None
11171124 for delay in _REPLACE_RETRY_DELAYS :
@@ -1122,7 +1129,10 @@ def _stat_and_read_with_sharing_retry(path: Path) -> tuple[os.stat_result, bytes
11221129 except FileNotFoundError :
11231130 raise
11241131 except PermissionError as exc :
1125- if not _IS_WINDOWS or getattr (exc , "winerror" , None ) not in _SHARING_VIOLATION_WINERRORS :
1132+ if not _IS_WINDOWS :
1133+ raise
1134+ winerror = getattr (exc , "winerror" , None )
1135+ if winerror not in _SHARING_VIOLATION_WINERRORS and exc .errno != errno .EACCES :
11261136 raise
11271137 last_exc = exc
11281138 raise FileNotFoundError (path ) from last_exc
0 commit comments