|
58 | 58 | import os |
59 | 59 | import shutil |
60 | 60 | import tempfile |
61 | | -import time |
62 | 61 | from pathlib import Path |
63 | | -from typing import IO, TYPE_CHECKING, cast |
| 62 | +from typing import TYPE_CHECKING, cast |
64 | 63 | from urllib import parse |
65 | 64 |
|
66 | 65 | from tuf.api import exceptions |
67 | 66 | from tuf.api.metadata import Root, Snapshot, TargetFile, Targets, Timestamp |
| 67 | +from tuf.ngclient._internal.file_lock import lock_file |
68 | 68 | from tuf.ngclient._internal.trusted_metadata_set import TrustedMetadataSet |
69 | 69 | from tuf.ngclient.config import EnvelopeType, UpdaterConfig |
70 | 70 | from tuf.ngclient.urllib3_fetcher import Urllib3Fetcher |
|
76 | 76 |
|
77 | 77 | logger = logging.getLogger(__name__) |
78 | 78 |
|
79 | | -try: |
80 | | - # advisory file locking for posix |
81 | | - import fcntl |
82 | | - |
83 | | - @contextlib.contextmanager |
84 | | - def _lock_file(path: str) -> Iterator[IO]: |
85 | | - with open(path, "wb") as f: |
86 | | - fcntl.lockf(f, fcntl.LOCK_EX) |
87 | | - yield f |
88 | | - |
89 | | -except ModuleNotFoundError: |
90 | | - # Windows file locking, in belt-and-suspenders-from-Temu style: |
91 | | - # Use a loop that tries to open the lockfile for 30 secs, but also |
92 | | - # use msvcrt.locking(). |
93 | | - # * since open() usually just fails when another process has the file open |
94 | | - # msvcrt.locking() almost never gets called when there is a lock. open() |
95 | | - # sometimes succeeds for multiple processes though |
96 | | - # * msvcrt.locking() does not even block until file is available: it just |
97 | | - # tries once per second in a non-blocking manner for 10 seconds. So if |
98 | | - # another process keeps opening the file it's unlikely that we actually |
99 | | - # get the lock |
100 | | - import msvcrt |
101 | | - |
102 | | - @contextlib.contextmanager |
103 | | - def _lock_file(path: str) -> Iterator[IO]: |
104 | | - err = None |
105 | | - locked = False |
106 | | - for _ in range(100): |
107 | | - try: |
108 | | - with open(path, "wb") as f: |
109 | | - msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, 1) |
110 | | - locked = True |
111 | | - yield f |
112 | | - return |
113 | | - except FileNotFoundError: |
114 | | - # could be from yield or from open() -- either way we bail |
115 | | - raise |
116 | | - except OSError as e: |
117 | | - if locked: |
118 | | - # yield has raised, let's not continue loop |
119 | | - raise e |
120 | | - err = e |
121 | | - logger.warning("Unsuccessful lock attempt for %s: %s", path, e) |
122 | | - time.sleep(0.3) |
123 | | - |
124 | | - # raise the last failure if we never got a lock |
125 | | - if err is not None: |
126 | | - raise err |
127 | | - |
128 | 79 |
|
129 | 80 | class Updater: |
130 | 81 | """Creates a new ``Updater`` instance and loads trusted root metadata. |
@@ -204,7 +155,7 @@ def _lock_metadata(self) -> Iterator[None]: |
204 | 155 | """Context manager for locking the metadata directory.""" |
205 | 156 |
|
206 | 157 | logger.debug("Getting metadata lock...") |
207 | | - with _lock_file(os.path.join(self._dir, ".lock")): |
| 158 | + with lock_file(os.path.join(self._dir, ".lock")): |
208 | 159 | yield |
209 | 160 | logger.debug("Released metadata lock") |
210 | 161 |
|
@@ -274,7 +225,7 @@ def get_targetinfo(self, target_path: str) -> TargetFile | None: |
274 | 225 |
|
275 | 226 | with self._lock_metadata(): |
276 | 227 | if Targets.type not in self._trusted_set: |
277 | | - # refresh |
| 228 | + # implicit refresh |
278 | 229 | self._load_root() |
279 | 230 | self._load_timestamp() |
280 | 231 | self._load_snapshot() |
@@ -367,7 +318,7 @@ def download_target( |
367 | 318 | targetinfo.verify_length_and_hashes(target_file) |
368 | 319 |
|
369 | 320 | target_file.seek(0) |
370 | | - with _lock_file(filepath) as destination_file: |
| 321 | + with lock_file(filepath) as destination_file: |
371 | 322 | shutil.copyfileobj(target_file, destination_file) |
372 | 323 |
|
373 | 324 | logger.debug("Downloaded target %s", targetinfo.path) |
|
0 commit comments