fix(archive): preserve prepared sessions across restarts
This commit is contained in:
@@ -232,6 +232,26 @@ class SessionRecordingMaterializer:
|
||||
with self._lock_for(session_id):
|
||||
return self._load_cached_recording(session_id, _validate_source(command))
|
||||
|
||||
def restore_published(self, command: ReplayCommand) -> MaterializedRecording | None:
|
||||
"""Restore an immutable published cache using bounded metadata checks.
|
||||
|
||||
Full source/output digests are proved before the cache is published.
|
||||
On a later process start the private cache is restored only when its
|
||||
schema, source stat identities and derived-file stat identity still
|
||||
match the durable sidecar. This keeps a prepared recording durable
|
||||
across restarts without rereading gigabytes or starting an exporter.
|
||||
"""
|
||||
|
||||
session_id = _validate_command_shape(command)
|
||||
if not self._has_compatible_cache_candidate(session_id):
|
||||
return None
|
||||
with self._lock_for(session_id):
|
||||
return self._load_cached_recording(
|
||||
session_id,
|
||||
_validate_source(command),
|
||||
verify_digests=False,
|
||||
)
|
||||
|
||||
def get_cached_pinned(
|
||||
self,
|
||||
command: ReplayCommand,
|
||||
@@ -430,6 +450,7 @@ class SessionRecordingMaterializer:
|
||||
source: _ValidatedSource,
|
||||
*,
|
||||
pin: bool = False,
|
||||
verify_digests: bool = True,
|
||||
) -> MaterializedRecording | None:
|
||||
# Cache validation and the optional lease are one transaction with
|
||||
# eviction. This makes it safe for FileResponse to open the path after
|
||||
@@ -451,6 +472,7 @@ class SessionRecordingMaterializer:
|
||||
source=source,
|
||||
recording_path=recording_path,
|
||||
sidecar_path=sidecar_path,
|
||||
verify_digests=verify_digests,
|
||||
)
|
||||
if cached is None:
|
||||
return None
|
||||
@@ -661,6 +683,7 @@ class SessionRecordingMaterializer:
|
||||
source: _ValidatedSource,
|
||||
recording_path: Path,
|
||||
sidecar_path: Path,
|
||||
verify_digests: bool = True,
|
||||
) -> MaterializedRecording | None:
|
||||
if recording_path.is_symlink() or sidecar_path.is_symlink():
|
||||
return None
|
||||
@@ -698,12 +721,19 @@ class SessionRecordingMaterializer:
|
||||
|
||||
source_sha256: str | None = None
|
||||
for cached, artifact in zip(cached_artifacts, source.artifacts, strict=True):
|
||||
digest = _sha256_prefix_stable(
|
||||
artifact.path,
|
||||
artifact.file_stat,
|
||||
artifact.replay_byte_length,
|
||||
digest = (
|
||||
_sha256_prefix_stable(
|
||||
artifact.path,
|
||||
artifact.file_stat,
|
||||
artifact.replay_byte_length,
|
||||
)
|
||||
if verify_digests
|
||||
else cached["sha256"]
|
||||
)
|
||||
if artifact.expected_sha256 is not None and digest != artifact.expected_sha256:
|
||||
if (
|
||||
artifact.expected_sha256 is not None
|
||||
and cached["sha256"] != artifact.expected_sha256
|
||||
):
|
||||
raise RecordingMaterializationError(
|
||||
"recording artifact digest no longer matches catalog"
|
||||
)
|
||||
@@ -713,9 +743,11 @@ class SessionRecordingMaterializer:
|
||||
source_sha256 = digest
|
||||
if source_sha256 is None or source_sha256 != document["source_sha256"]:
|
||||
return None
|
||||
recording_sha256 = _sha256_stable(recording_path, recording_stat)
|
||||
if recording_sha256 != document["recording_sha256"]:
|
||||
return None
|
||||
recording_sha256 = document["recording_sha256"]
|
||||
if verify_digests:
|
||||
recording_sha256 = _sha256_stable(recording_path, recording_stat)
|
||||
if recording_sha256 != document["recording_sha256"]:
|
||||
return None
|
||||
_chmod_best_effort(recording_path, 0o600)
|
||||
_chmod_best_effort(sidecar_path, 0o600)
|
||||
if document["schema_version"] != CACHE_SCHEMA:
|
||||
|
||||
Reference in New Issue
Block a user