beaupreda's picture
Upload sensAI-Generic-Object-Detection with upload_repo.py
13170f7 verified
Raw
History Blame Contribute Delete
3.02 kB
"""Idle-session reaper for Gradio demo apps.
Gradio's ``demo.unload()`` callback is JavaScript-based and won't fire on
browser crash, network drop, or mobile tab kill. This module runs a
background thread that closes sessions after a configurable period of
inactivity, preventing orphaned sessions from inflating metrics.
"""
import threading
import time
from collections.abc import Callable
class SessionReaper:
"""Track per-session activity and reap idle sessions.
Args:
timeout_s: Seconds of inactivity before a session is reaped.
on_expired: Called (outside any lock) with the session hash of each
reaped session. The callback is responsible for any cleanup
(decrementing counters, logging ``session_end``, etc.).
check_interval_s: How often the reaper thread wakes up to scan.
"""
def __init__(
self,
timeout_s: float,
on_expired: Callable[[str], None],
check_interval_s: float = 30.0,
) -> None:
self._timeout_s = timeout_s
self._on_expired = on_expired
self._check_interval_s = check_interval_s
self._sessions: dict[str, float] = {}
self._lock = threading.Lock()
self._shutting_down = False
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def add(self, session_hash: str) -> None:
"""Register a new session and record its initial activity."""
with self._lock:
self._sessions[session_hash] = time.monotonic()
def remove(self, session_hash: str) -> bool:
"""Unregister a session. Returns ``True`` if it was tracked."""
with self._lock:
return self._sessions.pop(session_hash, None) is not None
def touch(self, session_hash: str) -> bool:
"""Update last-activity timestamp. Returns ``False`` if unknown."""
with self._lock:
if session_hash not in self._sessions:
return False
self._sessions[session_hash] = time.monotonic()
return True
def is_active(self, session_hash: str) -> bool:
with self._lock:
return session_hash in self._sessions
@property
def active_count(self) -> int:
with self._lock:
return len(self._sessions)
def shutdown(self) -> None:
"""Signal the reaper thread to stop."""
self._shutting_down = True
def _run(self) -> None:
while not self._shutting_down:
time.sleep(self._check_interval_s)
now = time.monotonic()
with self._lock:
stale = [
sh
for sh, ts in self._sessions.items()
if now - ts >= self._timeout_s
]
for sh in stale:
del self._sessions[sh]
# Callbacks outside the lock to avoid deadlock with the caller.
for sh in stale:
self._on_expired(sh)