Skip to content
Merged
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
67 changes: 0 additions & 67 deletions cuda_core/tests/test_program_cache_multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,70 +123,3 @@ def test_concurrent_reader_never_sees_torn_file(tmp_path):
# mismatches.
assert hits == 200
assert mismatches == 0


def _worker_size_cap_writer(root: str, prefix: bytes, payload: bytes, count: int, max_size_bytes: int) -> None:
"""Hammer a small-cap cache with churning writes so eviction fires often."""
from cuda.core.utils import FileStreamProgramCache

with FileStreamProgramCache(root, max_size_bytes=max_size_bytes) as cache:
for i in range(count):
cache[prefix + str(i).encode()] = payload


def _worker_size_cap_rewriter(root: str, key: bytes, payload: bytes, max_size_bytes: int, done_event) -> None:
"""Repeatedly rewrite ``key`` with a fresh value until ``done_event`` fires."""
from cuda.core.utils import FileStreamProgramCache

with FileStreamProgramCache(root, max_size_bytes=max_size_bytes) as cache:
i = 0
while not done_event.is_set():
cache[key] = payload + str(i).encode()
i += 1


def test_concurrent_eviction_does_not_delete_replaced_file(tmp_path):
"""Eviction is stat-guarded: while one process is evicting an entry to
bring the cache under its size cap, another process may have already
``os.replace``-d a fresh value into the same path. The evictor must
refuse to unlink in that case, otherwise the racing rewriter's
just-committed entry vanishes.

The rewriter does NO post-race uncontested write -- the entry's
survival has to come from the in-race rewrites being preserved by
the stat-guarded eviction path."""
from cuda.core.utils import FileStreamProgramCache

root = str(tmp_path / "fc")
payload = b"X" * 2000
cap = 5000 # fits 2 raw 2000B entries; the third write triggers eviction

ctx = _mp.get_context("spawn")
done_event = ctx.Event()
rewriter = ctx.Process(
target=_worker_size_cap_rewriter,
args=(root, b"survivor", payload, cap, done_event),
)
# Churning writer creates new keys faster than eviction can drain them,
# forcing _enforce_size_cap to consider 'survivor' for eviction many times.
churner = ctx.Process(
target=_worker_size_cap_writer,
args=(root, b"churn-", payload, 80, cap),
)
rewriter.start()
churner.start()
churner.join(timeout=60)
done_event.set()
rewriter.join(timeout=60)
assert rewriter.exitcode == 0
assert churner.exitcode == 0

# If eviction blindly unlinked replaced files, the rewriter's last
# in-race write would be gone -- there is no post-race write to mask
# the failure. The value must also start with ``payload`` (not a
# ``churn-`` value) because each rewriter iteration writes
# ``payload + str(i).encode()``.
with FileStreamProgramCache(root, max_size_bytes=cap) as cache:
got = cache.get(b"survivor")
assert got is not None, "rewriter's entry was evicted by racing churner"
assert got.startswith(payload), f"survivor value not from rewriter: {got[:32]!r}..."
Loading