Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 10 additions & 10 deletions src/cachew/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,16 +553,6 @@ def cachew_wrapper[**P, ItemT](

logger.debug('hash mismatch: computing data and writing to db')

if synthetic_key is not None:
missing_synthetic_values = _synthetic.missing_synthetic_key_values_for_hashes(
old_hash=old_hash,
new_hash_d=new_hash_d,
)
if missing_synthetic_values is not None:
# can reuse cache
kwargs[_synthetic.CACHEW_CACHED] = session.cached_items() # ty: ignore[invalid-assignment]
kwargs[synthetic_key] = missing_synthetic_values # ty: ignore[invalid-assignment]

got_write = backend.get_exclusive_write()
if not got_write:
# NOTE: this is the bit we really have to watch out for and not put in a helper function
Expand All @@ -573,6 +563,16 @@ def cachew_wrapper[**P, ItemT](
running_uncached = False
return

if synthetic_key is not None:
missing_synthetic_values = _synthetic.missing_synthetic_key_values_for_hashes(
old_hash=old_hash,
new_hash_d=new_hash_d,
)
if missing_synthetic_values is not None:
# can reuse cache
kwargs[_synthetic.CACHEW_CACHED] = session.cached_items() # ty: ignore[invalid-assignment]
kwargs[synthetic_key] = missing_synthetic_values # ty: ignore[invalid-assignment]

# at this point we're guaranteed to have an exclusive write transaction
try:
yield from session.write_to_cache(func(*args, **kwargs))
Expand Down
42 changes: 42 additions & 0 deletions src/cachew/tests/test_cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,48 @@ def fun() -> Iterator[int]:
assert calls == 1


def test_synthetic_lock_lost_runs_uncached_with_original_args(
tmp_path: Path,
restore_settings,
) -> None:
"""
If synthetic cachew loses the write lock, it should run uncached with the original arguments.
"""
settings.THROW_ON_ERROR = False

cache_path = tmp_path / 'cache'
recomputed: list[str] = []
consumed_cached = False

@cachew(cache_path, force_file=True, synthetic_key='keys')
def fun(keys: Sequence[str], *, cachew_cached: Iterable[str] = ()) -> Iterator[str]:
nonlocal consumed_cached

for item in cachew_cached:
consumed_cached = True
yield item

for key in keys:
recomputed.append(key)
yield key

assert list(fun(keys=['a'])) == ['a']
assert recomputed == ['a']

recomputed.clear()
backend_cls = {
'file': FileBackend,
'sqlite': SqliteBackend,
}[settings.DEFAULT_BACKEND]

with backend_cls(cache_path=cache_path, logger=logger) as backend:
assert backend.get_exclusive_write()
assert list(fun(keys=['a', 'b'])) == ['a', 'b']

assert recomputed == ['a', 'b']
assert consumed_cached is False


@pytest.mark.parametrize('throw', [False, True])
def test_bad_annotation(*, tmp_path: Path, throw: bool) -> None:
"""
Expand Down