Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching of file-set hashes by local path and mtimes #700

Merged
merged 36 commits into from
Mar 17, 2024
Merged
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
45117ef
added code to handle "locally-persistent-ids"
tclose Feb 24, 2024
2b7ca50
implemented persistent hash cache to avoid rehashing files
tclose Feb 24, 2024
04b95ff
touched up persistent_hash_cache test
tclose Feb 24, 2024
0c865f4
replaced Cache({}) with Cache() to match new proper class
tclose Feb 24, 2024
3b3fdb7
upped resolution of mtime to nanoseconds
tclose Feb 24, 2024
81a5108
added sleep to various tests to ensure file mtimes are different
tclose Feb 24, 2024
0c4b179
added more sleeps to ensure mtimes of input files are different in tests
tclose Feb 24, 2024
615d590
debugged setting hash cache via env var and added clean up of directory
tclose Feb 24, 2024
55b660e
mock mtime writing instead of adding sleeps to ensure mtimes are diff…
tclose Feb 24, 2024
5d51736
undid overzealous black
tclose Feb 24, 2024
0421f85
added missing import
tclose Feb 24, 2024
a864b32
Adds platformdirs dependency and use it to store the hash cache within
tclose Feb 24, 2024
05ca695
added unittests to hit exceptions in persistentcache init
tclose Feb 24, 2024
52ef03f
added mkdir to location converter
tclose Feb 24, 2024
0216236
debugged mkdir of persistent cache
tclose Feb 24, 2024
bad261b
bug fixes in persistentcache location init
tclose Feb 24, 2024
2fbee2b
Revert "mock mtime writing instead of adding sleeps to ensure mtimes …
tclose Feb 24, 2024
91948f0
skip lock files in directory clean up
tclose Feb 24, 2024
e058408
fixed clean-up bug
tclose Feb 24, 2024
f1ded7a
added mock import
tclose Feb 24, 2024
bb11067
added another sleep to trigger atime change
tclose Feb 24, 2024
a031ea5
implementing @effigies suggestions
tclose Feb 29, 2024
f2f70a6
added comments and doc strings to explain the use of the persistent c…
tclose Feb 29, 2024
191aa9c
touched up comments
tclose Feb 29, 2024
3076fea
another comment touch up
tclose Feb 29, 2024
a094fbc
touch up comments again
tclose Feb 29, 2024
d27201f
Merge branch 'master' into local-cache-ids
tclose Mar 8, 2024
291f29f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 8, 2024
0a10f6c
added in @djarecka's test for moving file cache locations
tclose Mar 8, 2024
311e3dd
updated cache initialisation
tclose Mar 8, 2024
4827365
switched to use blake2b isntead of blake2s
tclose Mar 8, 2024
b6799b6
[skip ci] deleted already commented-out code
tclose Mar 8, 2024
2bb86fe
additional doc strings for hash cache objects
tclose Mar 8, 2024
1f601e1
added test to see that persistent cache is used in the running of tasks
tclose Mar 16, 2024
7e60c41
moved persistent hash cache within "hash_cache" subdirectory of the p…
tclose Mar 17, 2024
921979c
fixed import issue
tclose Mar 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions pydra/engine/tests/test_node_task.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import os
import shutil
import attr
import typing as ty
import numpy as np
import time
from unittest import mock
from pathlib import Path
import pytest
import time
from fileformats.generic import File
import pydra.mark

from .utils import (
fun_addtwo,
Expand Down Expand Up @@ -1599,3 +1604,62 @@ def test_task_files_cachelocations(plugin_dask_opt, tmp_path):
# checking if the second task didn't run the interface again
assert nn.output_dir.exists()
assert not nn2.output_dir.exists()


class OverriddenContentsFile(File):
"""A class for testing purposes, to that enables you to override the contents
of the file to allow you to check whether the persistent cache is used."""

def __init__(
self,
fspaths: ty.Iterator[Path],
contents: ty.Optional[bytes] = None,
metadata: ty.Dict[str, ty.Any] = None,
):
super().__init__(fspaths, metadata=metadata)
self._contents = contents

def byte_chunks(self, **kwargs) -> ty.Generator[ty.Tuple[str, bytes], None, None]:
if self._contents is not None:
yield (str(self.fspath), iter([self._contents]))
else:
yield from super().byte_chunks(**kwargs)

@property
def contents(self):
if self._contents is not None:
return self._contents
return super().contents


def test_task_files_persistentcache(tmp_path):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tclose - where are you setting the persistent cach path? i.e. PYDRA_HASH_CACHE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At @ghisvail's suggestion, the hash cache is stored in a system-dependent user cache directory using platformdirs.user_cache_dir by default (e.g. /Users/<username>/Library/Caches/pydra/<version-number> on MacOS).

I have just tweaked the code so that it is now put in a hashes subdirectory of that user cache dir (accessible in the pydra.utils.user_cache_dir variable) just in case any other cache data needs to be stored at some point in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, got it! Sorry I missed that. I've just realized that I made a typo when I was setting PYDRA_HASH_CACHE and that's why it was not saving the hashes there, and was confused where this is being saved..

"""
Two identical tasks with provided cache_dir that use file as an input;
the second task has cache_locations and should not recompute the results
"""
test_file_path = tmp_path / "test_file.txt"
test_file_path.write_bytes(b"foo")
cache_dir = tmp_path / "cache-dir"
cache_dir.mkdir()
test_file = OverriddenContentsFile(test_file_path)

@pydra.mark.task
def read_contents(x: OverriddenContentsFile) -> bytes:
return x.contents

assert (
read_contents(x=test_file, cache_dir=cache_dir)(plugin="serial").output.out
== b"foo"
)
test_file._contents = b"bar"
# should return result from the first run using the persistent cache
assert (
read_contents(x=test_file, cache_dir=cache_dir)(plugin="serial").output.out
== b"foo"
)
time.sleep(2) # Windows has a 2-second resolution for mtime
test_file_path.touch() # update the mtime to invalidate the persistent cache value
assert (
read_contents(x=test_file, cache_dir=cache_dir)(plugin="serial").output.out
== b"bar"
) # returns the overridden value
Loading