feat(space): background prune stale tmp_gradio session dirs by age
Browse files
app.py
CHANGED
|
@@ -34,6 +34,7 @@ print(
|
|
| 34 |
|
| 35 |
import shutil
|
| 36 |
import threading
|
|
|
|
| 37 |
from pathlib import Path
|
| 38 |
from typing import Any, Dict, Optional
|
| 39 |
|
|
@@ -162,6 +163,79 @@ def end_session(req: gr.Request):
|
|
| 162 |
shutil.rmtree(d, ignore_errors=True)
|
| 163 |
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
def get_file_path(file_obj: Any) -> Optional[str]:
|
| 166 |
if file_obj is None:
|
| 167 |
return None
|
|
@@ -1110,6 +1184,7 @@ def build_app() -> gr.Blocks:
|
|
| 1110 |
|
| 1111 |
demo = build_app()
|
| 1112 |
demo.queue(max_size=8)
|
|
|
|
| 1113 |
|
| 1114 |
# ---------------------------------------------------------------------------
|
| 1115 |
# Entry point
|
|
|
|
| 34 |
|
| 35 |
import shutil
|
| 36 |
import threading
|
| 37 |
+
import time
|
| 38 |
from pathlib import Path
|
| 39 |
from typing import Any, Dict, Optional
|
| 40 |
|
|
|
|
| 163 |
shutil.rmtree(d, ignore_errors=True)
|
| 164 |
|
| 165 |
|
| 166 |
+
def _session_dir_latest_mtime(path: Path) -> float:
|
| 167 |
+
"""Latest mtime among path and all nested files (best-effort for 'last activity')."""
|
| 168 |
+
|
| 169 |
+
try:
|
| 170 |
+
latest = path.stat().st_mtime
|
| 171 |
+
except OSError:
|
| 172 |
+
return 0.0
|
| 173 |
+
try:
|
| 174 |
+
for child in path.rglob("*"):
|
| 175 |
+
if child.is_file():
|
| 176 |
+
try:
|
| 177 |
+
latest = max(latest, child.stat().st_mtime)
|
| 178 |
+
except OSError:
|
| 179 |
+
pass
|
| 180 |
+
except OSError:
|
| 181 |
+
pass
|
| 182 |
+
return latest
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def prune_tmp_gradio_sessions(max_age_s: int) -> int:
|
| 186 |
+
"""Delete CACHE_DIR/<session> trees with no nested file activity for max_age_s. Returns removed count."""
|
| 187 |
+
|
| 188 |
+
if max_age_s <= 0 or not CACHE_DIR.is_dir():
|
| 189 |
+
return 0
|
| 190 |
+
now = time.time()
|
| 191 |
+
removed = 0
|
| 192 |
+
try:
|
| 193 |
+
entries = list(CACHE_DIR.iterdir())
|
| 194 |
+
except OSError:
|
| 195 |
+
return 0
|
| 196 |
+
for p in entries:
|
| 197 |
+
if not p.is_dir():
|
| 198 |
+
continue
|
| 199 |
+
try:
|
| 200 |
+
if now - _session_dir_latest_mtime(p) < max_age_s:
|
| 201 |
+
continue
|
| 202 |
+
shutil.rmtree(p, ignore_errors=True)
|
| 203 |
+
removed += 1
|
| 204 |
+
except OSError:
|
| 205 |
+
continue
|
| 206 |
+
return removed
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
_tmp_pruner_started = False
|
| 210 |
+
_tmp_pruner_lock = threading.Lock()
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def start_tmp_gradio_pruner() -> None:
|
| 214 |
+
"""Background prune of tmp_gradio session dirs (disk, not RAM). Configurable via env."""
|
| 215 |
+
|
| 216 |
+
global _tmp_pruner_started
|
| 217 |
+
interval = int(os.environ.get("NEAR_TMP_PRUNE_INTERVAL_S", "900"))
|
| 218 |
+
if interval <= 0:
|
| 219 |
+
return
|
| 220 |
+
with _tmp_pruner_lock:
|
| 221 |
+
if _tmp_pruner_started:
|
| 222 |
+
return
|
| 223 |
+
_tmp_pruner_started = True
|
| 224 |
+
max_age = int(os.environ.get("NEAR_TMP_GRADIO_MAX_AGE_S", str(6 * 3600)))
|
| 225 |
+
|
| 226 |
+
def _loop() -> None:
|
| 227 |
+
while True:
|
| 228 |
+
try:
|
| 229 |
+
n = prune_tmp_gradio_sessions(max_age)
|
| 230 |
+
if n:
|
| 231 |
+
print(f"[NeAR] tmp_gradio prune: removed {n} stale session dir(s)", flush=True)
|
| 232 |
+
except Exception as exc:
|
| 233 |
+
print(f"[NeAR] tmp_gradio prune error: {exc}", flush=True)
|
| 234 |
+
time.sleep(interval)
|
| 235 |
+
|
| 236 |
+
threading.Thread(target=_loop, daemon=True, name="near-tmp-prune").start()
|
| 237 |
+
|
| 238 |
+
|
| 239 |
def get_file_path(file_obj: Any) -> Optional[str]:
|
| 240 |
if file_obj is None:
|
| 241 |
return None
|
|
|
|
| 1184 |
|
| 1185 |
demo = build_app()
|
| 1186 |
demo.queue(max_size=8)
|
| 1187 |
+
start_tmp_gradio_pruner()
|
| 1188 |
|
| 1189 |
# ---------------------------------------------------------------------------
|
| 1190 |
# Entry point
|