Spaces:
Sleeping
Sleeping
Return session deletion before storage cleanup
Browse files
app.py
CHANGED
|
@@ -26,11 +26,11 @@ import cv2
|
|
| 26 |
from email.message import Message
|
| 27 |
from email.utils import collapse_rfc2231_value
|
| 28 |
from dotenv import load_dotenv
|
| 29 |
-
from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile
|
| 30 |
from fastapi.responses import FileResponse, JSONResponse
|
| 31 |
from huggingface_hub import HfApi, hf_hub_download, snapshot_download
|
| 32 |
from huggingface_hub.errors import RemoteEntryNotFoundError
|
| 33 |
-
from storage_listing import session_files
|
| 34 |
|
| 35 |
load_dotenv(Path(__file__).with_name(".env"))
|
| 36 |
jobs = {}
|
|
@@ -731,15 +731,38 @@ def cancel_session_job(session_id: UUID):
|
|
| 731 |
job["done"].wait(timeout=300)
|
| 732 |
if not job["done"].is_set():
|
| 733 |
raise HTTPException(504, "The analysis upload did not stop in time; deletion was not started")
|
| 734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
if work_path:
|
| 736 |
shutil.rmtree(work_path, ignore_errors=True)
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 743 |
|
| 744 |
|
| 745 |
def session_row(session_id, user_id, is_admin=False):
|
|
@@ -858,22 +881,19 @@ def update_action(session_id: UUID, action_id: UUID, payload: dict, request: Req
|
|
| 858 |
|
| 859 |
|
| 860 |
@app.delete("/api/sessions/{session_id}")
|
| 861 |
-
def delete_session(session_id: UUID, request: Request):
|
| 862 |
-
cancel_session_job(session_id)
|
| 863 |
row = owned_session_row(session_id, request.state.user_id, request.state.is_admin)
|
| 864 |
expected_path = f"sessions/{session_id}"
|
| 865 |
storage_path = row.get("path")
|
| 866 |
if storage_path is not None and storage_path != expected_path:
|
| 867 |
raise HTTPException(409, "Session uses a different storage layout")
|
| 868 |
|
| 869 |
-
|
| 870 |
-
if session_folder_exists(hub(), os.environ["STORAGE_REPO_ID"], storage_path):
|
| 871 |
-
hub().delete_folder(
|
| 872 |
-
repo_id=os.environ["STORAGE_REPO_ID"], repo_type="dataset",
|
| 873 |
-
path_in_repo=storage_path, commit_message=f"Delete session {session_id}",
|
| 874 |
-
)
|
| 875 |
-
|
| 876 |
backend(f"/{session_id}", request.state.user_id, "DELETE", is_admin=request.state.is_admin)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 877 |
return {"ok": True}
|
| 878 |
|
| 879 |
|
|
|
|
| 26 |
from email.message import Message
|
| 27 |
from email.utils import collapse_rfc2231_value
|
| 28 |
from dotenv import load_dotenv
|
| 29 |
+
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, Request, UploadFile
|
| 30 |
from fastapi.responses import FileResponse, JSONResponse
|
| 31 |
from huggingface_hub import HfApi, hf_hub_download, snapshot_download
|
| 32 |
from huggingface_hub.errors import RemoteEntryNotFoundError
|
| 33 |
+
from storage_listing import session_files
|
| 34 |
|
| 35 |
load_dotenv(Path(__file__).with_name(".env"))
|
| 36 |
jobs = {}
|
|
|
|
| 731 |
job["done"].wait(timeout=300)
|
| 732 |
if not job["done"].is_set():
|
| 733 |
raise HTTPException(504, "The analysis upload did not stop in time; deletion was not started")
|
| 734 |
+
return [(client_id, job.get("workPath")) for client_id, job in matching]
|
| 735 |
+
|
| 736 |
+
|
| 737 |
+
def cleanup_deleted_session_files(session_id, storage_path, job_paths):
|
| 738 |
+
for _, work_path in job_paths:
|
| 739 |
if work_path:
|
| 740 |
shutil.rmtree(work_path, ignore_errors=True)
|
| 741 |
+
folders = ([storage_path] if storage_path else []) + [
|
| 742 |
+
path for client_id, _ in job_paths
|
| 743 |
+
for path in (f"colab-staging/{client_id}", f"colab-results/{client_id}")
|
| 744 |
+
]
|
| 745 |
+
api = hub()
|
| 746 |
+
repo_id = os.environ["STORAGE_REPO_ID"]
|
| 747 |
+
failed_folders = []
|
| 748 |
+
for folder in folders:
|
| 749 |
+
for attempt in range(3):
|
| 750 |
+
try:
|
| 751 |
+
api.delete_folder(repo_id=repo_id, repo_type="dataset", path_in_repo=folder,
|
| 752 |
+
commit_message=f"Delete session {session_id} files")
|
| 753 |
+
break
|
| 754 |
+
except RemoteEntryNotFoundError:
|
| 755 |
+
break
|
| 756 |
+
except Exception:
|
| 757 |
+
if attempt == 2:
|
| 758 |
+
logger.exception("Unable to delete session storage folder %s", folder)
|
| 759 |
+
failed_folders.append(folder)
|
| 760 |
+
else:
|
| 761 |
+
time.sleep(2 ** attempt)
|
| 762 |
+
if failed_folders:
|
| 763 |
+
logger.error("Background storage cleanup incomplete for session %s: %s", session_id, failed_folders)
|
| 764 |
+
else:
|
| 765 |
+
logger.info("Finished background storage cleanup for session %s", session_id)
|
| 766 |
|
| 767 |
|
| 768 |
def session_row(session_id, user_id, is_admin=False):
|
|
|
|
| 881 |
|
| 882 |
|
| 883 |
@app.delete("/api/sessions/{session_id}")
|
| 884 |
+
def delete_session(session_id: UUID, request: Request, background_tasks: BackgroundTasks):
|
|
|
|
| 885 |
row = owned_session_row(session_id, request.state.user_id, request.state.is_admin)
|
| 886 |
expected_path = f"sessions/{session_id}"
|
| 887 |
storage_path = row.get("path")
|
| 888 |
if storage_path is not None and storage_path != expected_path:
|
| 889 |
raise HTTPException(409, "Session uses a different storage layout")
|
| 890 |
|
| 891 |
+
job_paths = cancel_session_job(session_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 892 |
backend(f"/{session_id}", request.state.user_id, "DELETE", is_admin=request.state.is_admin)
|
| 893 |
+
for client_id, _ in job_paths:
|
| 894 |
+
jobs.pop(client_id, None)
|
| 895 |
+
# The database deletion makes the session disappear before Hugging Face finishes deleting its files.
|
| 896 |
+
background_tasks.add_task(cleanup_deleted_session_files, session_id, storage_path, job_paths)
|
| 897 |
return {"ok": True}
|
| 898 |
|
| 899 |
|