Fix deletion rules: runs deletable, cache items protected
- Run deletion: Handle legacy runs without activity records by checking L2 shared status directly (instead of failing) - Cache deletion: Check Redis runs in addition to activity store to prevent deleting inputs/outputs that belong to runs - Add find_runs_using_content() helper to check if content_hash is used as input or output of any run This fixes the inverted deletion logic where runs couldn't be deleted but their cache items could. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
60
server.py
60
server.py
@@ -74,6 +74,21 @@ def list_all_runs() -> list["RunStatus"]:
|
||||
runs.append(RunStatus.model_validate_json(data))
|
||||
return sorted(runs, key=lambda r: r.created_at, reverse=True)
|
||||
|
||||
|
||||
def find_runs_using_content(content_hash: str) -> list[tuple["RunStatus", str]]:
|
||||
"""Find all runs that use a content_hash as input or output.
|
||||
|
||||
Returns list of (run, role) tuples where role is 'input' or 'output'.
|
||||
"""
|
||||
results = []
|
||||
for run in list_all_runs():
|
||||
if run.inputs and content_hash in run.inputs:
|
||||
results.append((run, "input"))
|
||||
if run.output_hash == content_hash:
|
||||
results.append((run, "output"))
|
||||
return results
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Art DAG L1 Server",
|
||||
description="Distributed rendering server for Art DAG",
|
||||
@@ -369,15 +384,28 @@ async def discard_run(run_id: str, username: str = Depends(get_required_user)):
|
||||
if run.username not in (username, actor_id):
|
||||
raise HTTPException(403, "Access denied")
|
||||
|
||||
# Check if run can be discarded
|
||||
can_discard, reason = cache_manager.can_discard_activity(run_id)
|
||||
if not can_discard:
|
||||
raise HTTPException(400, f"Cannot discard run: {reason}")
|
||||
# Check if activity exists for this run
|
||||
activity = cache_manager.get_activity(run_id)
|
||||
|
||||
# Discard the activity (cleans up cache entries)
|
||||
success, msg = cache_manager.discard_activity(run_id)
|
||||
if not success:
|
||||
raise HTTPException(500, f"Failed to discard: {msg}")
|
||||
if activity:
|
||||
# Use activity manager deletion rules
|
||||
can_discard, reason = cache_manager.can_discard_activity(run_id)
|
||||
if not can_discard:
|
||||
raise HTTPException(400, f"Cannot discard run: {reason}")
|
||||
|
||||
# Discard the activity (cleans up cache entries)
|
||||
success, msg = cache_manager.discard_activity(run_id)
|
||||
if not success:
|
||||
raise HTTPException(500, f"Failed to discard: {msg}")
|
||||
else:
|
||||
# Legacy run without activity record - check L2 shared status manually
|
||||
items_to_check = list(run.inputs or [])
|
||||
if run.output_hash:
|
||||
items_to_check.append(run.output_hash)
|
||||
|
||||
for content_hash in items_to_check:
|
||||
if cache_manager.l2_checker.is_shared(content_hash):
|
||||
raise HTTPException(400, f"Cannot discard run: item {content_hash[:16]}... is published to L2")
|
||||
|
||||
# Remove from Redis
|
||||
redis_client.delete(f"{RUNS_KEY_PREFIX}{run_id}")
|
||||
@@ -1500,7 +1528,13 @@ async def discard_cache(content_hash: str, username: str = Depends(get_required_
|
||||
pin_reason = meta.get("pin_reason", "unknown")
|
||||
raise HTTPException(400, f"Cannot discard pinned item (reason: {pin_reason})")
|
||||
|
||||
# Check deletion rules via cache_manager
|
||||
# Check if used by any run (Redis runs, not just activity store)
|
||||
runs_using = find_runs_using_content(content_hash)
|
||||
if runs_using:
|
||||
run, role = runs_using[0]
|
||||
raise HTTPException(400, f"Cannot discard: item is {role} of run {run.run_id}")
|
||||
|
||||
# Check deletion rules via cache_manager (L2 shared status, activity store)
|
||||
can_delete, reason = cache_manager.can_delete(content_hash)
|
||||
if not can_delete:
|
||||
raise HTTPException(400, f"Cannot discard: {reason}")
|
||||
@@ -1546,7 +1580,13 @@ async def ui_discard_cache(content_hash: str, request: Request):
|
||||
pin_reason = meta.get("pin_reason", "unknown")
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: item is pinned ({pin_reason})</div>'
|
||||
|
||||
# Check deletion rules via cache_manager
|
||||
# Check if used by any run (Redis runs, not just activity store)
|
||||
runs_using = find_runs_using_content(content_hash)
|
||||
if runs_using:
|
||||
run, role = runs_using[0]
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: item is {role} of run {run.run_id}</div>'
|
||||
|
||||
# Check deletion rules via cache_manager (L2 shared status, activity store)
|
||||
can_delete, reason = cache_manager.can_delete(content_hash)
|
||||
if not can_delete:
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: {reason}</div>'
|
||||
|
||||
Reference in New Issue
Block a user