|
| 1 | +import contextlib |
1 | 2 | import hashlib
|
2 | 3 | import inspect
|
3 | 4 | import logging
|
4 | 5 | import os
|
5 | 6 | import pickle
|
6 | 7 | import tempfile
|
7 | 8 | import time
|
8 |
| -from shutil import move, rmtree |
| 9 | +from shutil import rmtree |
9 | 10 |
|
10 | 11 | from fsspec import AbstractFileSystem, filesystem
|
11 | 12 | from fsspec.callbacks import _DEFAULT_CALLBACK
|
@@ -184,11 +185,9 @@ def save_cache(self):
|
184 | 185 | for c in cache.values():
|
185 | 186 | if isinstance(c["blocks"], set):
|
186 | 187 | c["blocks"] = list(c["blocks"])
|
187 |
| - fd2, fn2 = tempfile.mkstemp() |
188 |
| - with open(fd2, "wb") as f: |
189 |
| - pickle.dump(cache, f) |
190 | 188 | self._mkcache()
|
191 |
| - move(fn2, fn) |
| 189 | + with atomic_write(fn) as f: |
| 190 | + pickle.dump(cache, f) |
192 | 191 | self.cached_files[-1] = cached_files
|
193 | 192 | self.last_cache = time.time()
|
194 | 193 |
|
@@ -264,7 +263,7 @@ def clear_expired_cache(self, expiry_time=None):
|
264 | 263 |
|
265 | 264 | if self.cached_files[-1]:
|
266 | 265 | cache_path = os.path.join(self.storage[-1], "cache")
|
267 |
| - with open(cache_path, "wb") as fc: |
| 266 | + with atomic_write(cache_path) as fc: |
268 | 267 | pickle.dump(self.cached_files[-1], fc)
|
269 | 268 | else:
|
270 | 269 | rmtree(self.storage[-1])
|
@@ -834,3 +833,24 @@ def hash_name(path, same_name):
|
834 | 833 | else:
|
835 | 834 | hash = hashlib.sha256(path.encode()).hexdigest()
|
836 | 835 | return hash
|
| 836 | + |
| 837 | + |
| 838 | +@contextlib.contextmanager |
| 839 | +def atomic_write(path, mode="wb"): |
| 840 | + """ |
| 841 | + A context manager that opens a temporary file next to `path` and, on exit, |
| 842 | + replaces `path` with the temporary file, thereby updating `path` |
| 843 | + atomically. |
| 844 | + """ |
| 845 | + fd, fn = tempfile.mkstemp( |
| 846 | + dir=os.path.dirname(path), prefix=os.path.basename(path) + "-" |
| 847 | + ) |
| 848 | + try: |
| 849 | + with open(fd, mode) as fp: |
| 850 | + yield fp |
| 851 | + except BaseException: |
| 852 | + with contextlib.suppress(FileNotFoundError): |
| 853 | + os.unlink(fn) |
| 854 | + raise |
| 855 | + else: |
| 856 | + os.replace(fn, path) |
0 commit comments