Skip to content

Commit c2b3d4d

Browse files
feat: ensure that caching happens race condition free on shared network filesystems
1 parent 3f218e1 commit c2b3d4d

1 file changed

Lines changed: 20 additions & 2 deletions

File tree

  • snakemake_interface_software_deployment_plugins

snakemake_interface_software_deployment_plugins/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import tempfile
2+
import os
3+
14
__author__ = "Johannes Köster"
25
__copyright__ = "Copyright 2024, Johannes Köster"
36
__email__ = "johannes.koester@uni-due.de"
@@ -349,14 +352,29 @@ class CacheableEnvBase(EnvBase, ABC):
349352
async def get_cache_assets(self) -> Iterable[str]: ...
350353

351354
@abstractmethod
352-
async def cache_asset(self, asset: str) -> None:
355+
async def cache_asset(self, asset: str, to_path: Path) -> None:
353356
"""Retrieve/create and store given asset to self.cache_path."""
354357
...
355358

356359
async def managed_cache_asset(self, asset: str) -> None:
360+
# The naming scheme used here follows the same pattern as rsync.
361+
# This way, we benefit from rsync specific optimizations in network
362+
# filtesystems like GlusterFS (see
363+
# https://developers.redhat.com/blog/2018/08/14/improving-rsync-performance-with-glusterfs)
364+
_, tmp_cache_path = tempfile.mkstemp(
365+
prefix=asset, suffix=".part", dir=self.cache_path
366+
)
367+
tmp_cache_path = Path(tmp_cache_path)
368+
cache_path = self.cache_path / asset
357369
try:
358-
await self.cache_asset(asset)
370+
await self.cache_asset(asset, tmp_cache_path)
371+
os.replace(tmp_cache_path, cache_path)
359372
except Exception as e:
373+
if tmp_cache_path.exists():
374+
try:
375+
tmp_cache_path.unlink()
376+
except Exception:
377+
pass
360378
raise WorkflowError(f"Caching of {self.spec} failed: {e}") from e
361379

362380
@property

0 commit comments

Comments
 (0)