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))
|
runs.append(RunStatus.model_validate_json(data))
|
||||||
return sorted(runs, key=lambda r: r.created_at, reverse=True)
|
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(
|
app = FastAPI(
|
||||||
title="Art DAG L1 Server",
|
title="Art DAG L1 Server",
|
||||||
description="Distributed rendering server for Art DAG",
|
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):
|
if run.username not in (username, actor_id):
|
||||||
raise HTTPException(403, "Access denied")
|
raise HTTPException(403, "Access denied")
|
||||||
|
|
||||||
# Check if run can be discarded
|
# Check if activity exists for this run
|
||||||
can_discard, reason = cache_manager.can_discard_activity(run_id)
|
activity = cache_manager.get_activity(run_id)
|
||||||
if not can_discard:
|
|
||||||
raise HTTPException(400, f"Cannot discard run: {reason}")
|
|
||||||
|
|
||||||
# Discard the activity (cleans up cache entries)
|
if activity:
|
||||||
success, msg = cache_manager.discard_activity(run_id)
|
# Use activity manager deletion rules
|
||||||
if not success:
|
can_discard, reason = cache_manager.can_discard_activity(run_id)
|
||||||
raise HTTPException(500, f"Failed to discard: {msg}")
|
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
|
# Remove from Redis
|
||||||
redis_client.delete(f"{RUNS_KEY_PREFIX}{run_id}")
|
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")
|
pin_reason = meta.get("pin_reason", "unknown")
|
||||||
raise HTTPException(400, f"Cannot discard pinned item (reason: {pin_reason})")
|
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)
|
can_delete, reason = cache_manager.can_delete(content_hash)
|
||||||
if not can_delete:
|
if not can_delete:
|
||||||
raise HTTPException(400, f"Cannot discard: {reason}")
|
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")
|
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>'
|
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)
|
can_delete, reason = cache_manager.can_delete(content_hash)
|
||||||
if not can_delete:
|
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>'
|
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