Serialize cache downloads and uploads with the same model lock
This commit is contained in:
parent
38d222c825
commit
50075f5cfb
2 changed files with 50 additions and 4 deletions
|
|
@ -178,7 +178,10 @@ class LargeFileBase(ABC):
|
||||||
Args:
|
Args:
|
||||||
hide_progress: Do not show a progress update after each 10% of progress.
|
hide_progress: Do not show a progress update after each 10% of progress.
|
||||||
"""
|
"""
|
||||||
|
with self._model_lock():
|
||||||
|
return self._get_cached_file(hide_progress)
|
||||||
|
|
||||||
|
def _get_cached_file(self, hide_progress: bool) -> Path:
|
||||||
if not self._cache_only_mode:
|
if not self._cache_only_mode:
|
||||||
self._find_instances()
|
self._find_instances()
|
||||||
instance = next(
|
instance = next(
|
||||||
|
|
@ -233,12 +236,9 @@ class LargeFileBase(ABC):
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
path = Path(path)
|
path = Path(path)
|
||||||
|
|
||||||
lock_directory = self.cache_path / ".locks"
|
|
||||||
lock_directory.mkdir(parents=True, exist_ok=True)
|
|
||||||
lock_name = sha256(f"{self._cache_namespace}:{self._name}".encode()).hexdigest()
|
|
||||||
staging_directory = self.cache_path / ".staging"
|
staging_directory = self.cache_path / ".staging"
|
||||||
staging_directory.mkdir(parents=True, exist_ok=True)
|
staging_directory.mkdir(parents=True, exist_ok=True)
|
||||||
with FileLock(lock_directory / f"{lock_name}.lock"):
|
with self._model_lock():
|
||||||
# Allocate at commit time, after earlier local writers have finished.
|
# Allocate at commit time, after earlier local writers have finished.
|
||||||
self._find_instances()
|
self._find_instances()
|
||||||
self._version = self._instances[-1].version + 1 if self._instances else 0
|
self._version = self._instances[-1].version + 1 if self._instances else 0
|
||||||
|
|
@ -306,6 +306,12 @@ class LargeFileBase(ABC):
|
||||||
f"{type(self).__module__}.{type(self).__qualname__}".encode()
|
f"{type(self).__module__}.{type(self).__qualname__}".encode()
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
||||||
|
def _model_lock(self) -> FileLock:
|
||||||
|
directory = self.cache_path / ".locks"
|
||||||
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
key = sha256(f"{self._cache_namespace}:{self._name}".encode()).hexdigest()
|
||||||
|
return FileLock(directory / f"{key}.lock")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _cache_identity_path(self) -> Path:
|
def _cache_identity_path(self) -> Path:
|
||||||
key = sha256(self._cache_name.encode()).hexdigest()
|
key = sha256(self._cache_name.encode()).hexdigest()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
import io
|
||||||
|
import tarfile
|
||||||
|
import time
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
@ -44,3 +48,39 @@ def test_recreated_remote_version_invalidates_other_clients(tmp_path: Path) -> N
|
||||||
assert LargeFileS3("model").get().read_text() == "new"
|
assert LargeFileS3("model").get().read_text() == "new"
|
||||||
with patch.object(LargeFileS3, "cache_path", tmp_path / "reader"):
|
with patch.object(LargeFileS3, "cache_path", tmp_path / "reader"):
|
||||||
assert reader.get().read_text() == "new"
|
assert reader.get().read_text() == "new"
|
||||||
|
|
||||||
|
|
||||||
|
def test_concurrent_downloads_publish_one_directory(tmp_path: Path) -> None:
|
||||||
|
backend = MagicMock()
|
||||||
|
backend.list_objects_v2.return_value = {
|
||||||
|
"Contents": [{"Key": "model/0", "ETag": "one"}]
|
||||||
|
}
|
||||||
|
downloads = []
|
||||||
|
|
||||||
|
def download(
|
||||||
|
self: LargeFileS3, remote_path: Any, path: Path, hide_progress: bool
|
||||||
|
) -> None:
|
||||||
|
downloads.append(remote_path)
|
||||||
|
time.sleep(0.1)
|
||||||
|
with tarfile.open(path, "w:gz") as archive:
|
||||||
|
folder = tarfile.TarInfo("model-0")
|
||||||
|
folder.type = tarfile.DIRTYPE
|
||||||
|
folder.mode = 0o755
|
||||||
|
archive.addfile(folder)
|
||||||
|
file = tarfile.TarInfo("model-0/weights")
|
||||||
|
file.size = 7
|
||||||
|
file.mode = 0o644
|
||||||
|
archive.addfile(file, io.BytesIO(b"weights"))
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(LargeFileS3, "_client", backend),
|
||||||
|
patch.object(LargeFileS3, "_download", download),
|
||||||
|
patch.object(LargeFileS3, "cache_path", tmp_path / "cache"),
|
||||||
|
ThreadPoolExecutor(max_workers=2) as pool,
|
||||||
|
):
|
||||||
|
readers = [LargeFileS3("model") for _ in range(2)]
|
||||||
|
results = list(pool.map(lambda reader: reader.get(), readers))
|
||||||
|
assert results[0] == results[1]
|
||||||
|
assert list(results[0].iterdir()) == [results[0] / "weights"]
|
||||||
|
assert (results[0] / "weights").read_text() == "weights"
|
||||||
|
assert downloads == ["model/0"]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue