mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-14 12:48:03 +00:00
Pause background tasks while Odysseus is active
This commit is contained in:
@@ -595,6 +595,14 @@ webhook_manager = WebhookManager(api_key_manager=api_key_manager)
|
|||||||
auth_router = setup_auth_routes(auth_manager)
|
auth_router = setup_auth_routes(auth_manager)
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/activity/heartbeat")
|
||||||
|
async def activity_heartbeat():
|
||||||
|
from src.interactive_gate import mark_browser_activity
|
||||||
|
await mark_browser_activity()
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
# Uploads
|
# Uploads
|
||||||
from routes.upload_routes import setup_upload_routes
|
from routes.upload_routes import setup_upload_routes
|
||||||
upload_router, upload_cleanup_func = setup_upload_routes(upload_handler)
|
upload_router, upload_cleanup_func = setup_upload_routes(upload_handler)
|
||||||
|
|||||||
+31
-2
@@ -15,6 +15,7 @@ import time
|
|||||||
|
|
||||||
_ACTIVE_REQUESTS = 0
|
_ACTIVE_REQUESTS = 0
|
||||||
_LAST_ACTIVITY = 0.0
|
_LAST_ACTIVITY = 0.0
|
||||||
|
_LAST_BROWSER_ACTIVITY = 0.0
|
||||||
_COND: asyncio.Condition | None = None
|
_COND: asyncio.Condition | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +38,14 @@ def _max_wait_seconds() -> float:
|
|||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _browser_active_seconds() -> float:
|
||||||
|
"""How long a visible Odysseus browser heartbeat blocks background tasks."""
|
||||||
|
try:
|
||||||
|
return max(0.0, float(os.getenv("BACKGROUND_TASK_BROWSER_ACTIVE_SECONDS", "45")))
|
||||||
|
except Exception:
|
||||||
|
return 45.0
|
||||||
|
|
||||||
|
|
||||||
def _condition() -> asyncio.Condition:
|
def _condition() -> asyncio.Condition:
|
||||||
global _COND
|
global _COND
|
||||||
if _COND is None:
|
if _COND is None:
|
||||||
@@ -45,6 +54,7 @@ def _condition() -> asyncio.Condition:
|
|||||||
|
|
||||||
|
|
||||||
_PASSIVE_EXACT_PATHS = {
|
_PASSIVE_EXACT_PATHS = {
|
||||||
|
"/api/activity/heartbeat",
|
||||||
"/api/tasks/notifications",
|
"/api/tasks/notifications",
|
||||||
"/api/research/active",
|
"/api/research/active",
|
||||||
"/api/email/urgency-state",
|
"/api/email/urgency-state",
|
||||||
@@ -69,6 +79,24 @@ def should_track_interactive_request(path: str, method: str = "GET") -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def mark_browser_activity() -> None:
|
||||||
|
"""Record that an authenticated browser tab is visibly using Odysseus."""
|
||||||
|
global _LAST_BROWSER_ACTIVITY
|
||||||
|
if not _enabled():
|
||||||
|
return
|
||||||
|
cond = _condition()
|
||||||
|
async with cond:
|
||||||
|
_LAST_BROWSER_ACTIVITY = time.monotonic()
|
||||||
|
cond.notify_all()
|
||||||
|
|
||||||
|
|
||||||
|
def _has_recent_browser_activity(now: float | None = None) -> bool:
|
||||||
|
ttl = _browser_active_seconds()
|
||||||
|
if ttl <= 0 or _LAST_BROWSER_ACTIVITY <= 0:
|
||||||
|
return False
|
||||||
|
return ((now if now is not None else time.monotonic()) - _LAST_BROWSER_ACTIVITY) < ttl
|
||||||
|
|
||||||
|
|
||||||
def _has_active_chat_stream() -> bool:
|
def _has_active_chat_stream() -> bool:
|
||||||
"""Best-effort check for foreground model work that outlives HTTP requests.
|
"""Best-effort check for foreground model work that outlives HTTP requests.
|
||||||
|
|
||||||
@@ -133,11 +161,12 @@ async def wait_for_interactive_quiet(label: str = "") -> bool:
|
|||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
quiet_remaining = quiet - (now - _LAST_ACTIVITY)
|
quiet_remaining = quiet - (now - _LAST_ACTIVITY)
|
||||||
active_stream = _has_active_chat_stream()
|
active_stream = _has_active_chat_stream()
|
||||||
if _ACTIVE_REQUESTS <= 0 and quiet_remaining <= 0 and not active_stream:
|
browser_active = _has_recent_browser_activity(now)
|
||||||
|
if _ACTIVE_REQUESTS <= 0 and quiet_remaining <= 0 and not active_stream and not browser_active:
|
||||||
return waited
|
return waited
|
||||||
|
|
||||||
waited = True
|
waited = True
|
||||||
timeout = 0.25 if (_ACTIVE_REQUESTS > 0 or active_stream) else min(max(quiet_remaining, 0.05), 0.5)
|
timeout = 0.25 if (_ACTIVE_REQUESTS > 0 or active_stream or browser_active) else min(max(quiet_remaining, 0.05), 0.5)
|
||||||
if deadline is not None:
|
if deadline is not None:
|
||||||
remaining = deadline - now
|
remaining = deadline - now
|
||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
|
|||||||
@@ -53,6 +53,40 @@ window.uiModule = uiModule;
|
|||||||
window.adminModule = adminModule;
|
window.adminModule = adminModule;
|
||||||
window.cookbookModule = cookbookModule;
|
window.cookbookModule = cookbookModule;
|
||||||
|
|
||||||
|
function initForegroundActivityHeartbeat() {
|
||||||
|
let lastSent = 0;
|
||||||
|
const minGapMs = 12000;
|
||||||
|
const send = (force = false) => {
|
||||||
|
if (document.visibilityState === 'hidden') return;
|
||||||
|
const now = Date.now();
|
||||||
|
if (!force && now - lastSent < minGapMs) return;
|
||||||
|
lastSent = now;
|
||||||
|
try {
|
||||||
|
if (navigator.sendBeacon) {
|
||||||
|
const body = new Blob(['{}'], { type: 'application/json' });
|
||||||
|
if (navigator.sendBeacon('/api/activity/heartbeat', body)) return;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
fetch('/api/activity/heartbeat', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
keepalive: true,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: '{}',
|
||||||
|
}).catch(() => {});
|
||||||
|
};
|
||||||
|
send(true);
|
||||||
|
window.addEventListener('focus', () => send(true));
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState !== 'hidden') send(true);
|
||||||
|
});
|
||||||
|
['pointerdown', 'keydown', 'touchstart', 'scroll'].forEach(type => {
|
||||||
|
window.addEventListener(type, () => send(false), { passive: true, capture: true });
|
||||||
|
});
|
||||||
|
setInterval(() => send(false), 15000);
|
||||||
|
}
|
||||||
|
initForegroundActivityHeartbeat();
|
||||||
|
|
||||||
// Redirect to login on 401 from any fetch
|
// Redirect to login on 401 from any fetch
|
||||||
const _origFetch = window.fetch;
|
const _origFetch = window.fetch;
|
||||||
window.fetch = async function(...args) {
|
window.fetch = async function(...args) {
|
||||||
|
|||||||
Reference in New Issue
Block a user