mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: format preservation, dispatcher stability, and health reporting
Closes #17, #18, #19, #31, #32, #33, #34 Format preservation (#17, #18, #19): - Add resolveOutputFormat to rotate, resize, text-overlay, watermark-text, border, replace-color, blur-faces, upscale, erase-object, restore-photo - Alpha-aware fallback: border with corner radius/shadow and replace-color with makeTransparent fall back to PNG for non-alpha formats (JPEG) - Python sidecar tools (blur-faces, upscale, erase-object) now convert PNG output back to input format, matching restore-photo/colorize pattern - Upscale and erase-object default to "auto" format detection instead of PNG Dispatcher stability (#31, #32): - Add gc.collect() and torch.cuda.empty_cache() after each dispatcher request - Add configurable max_requests (default 50) for periodic dispatcher restart - Add exponential backoff to dispatcher crash recovery in bridge.ts - Circuit breaker: 5 crashes within 60s permanently disables dispatcher - Reset crash counter on successful dispatcher startup Health & security (#33, #34): - Export getDispatcherStatus() from @snapotter/ai with running/ready/failed/ gpu/pid/consecutiveCrashes fields - Admin health endpoint now includes full dispatcher status - Add pip-audit job to CI workflow for Python dependency scanning
This commit is contained in:
@@ -12,6 +12,7 @@ Pre-imports heavy libraries at startup to eliminate cold-start latency.
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import gc
|
||||
import io
|
||||
import os
|
||||
import traceback
|
||||
@@ -198,6 +199,20 @@ def _run_script_main(script_name, args):
|
||||
# ── Main loop ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
MAX_REQUESTS = int(os.environ.get("DISPATCHER_MAX_REQUESTS", "50"))
|
||||
|
||||
|
||||
def _cleanup_after_request():
|
||||
"""Free unreferenced objects and GPU memory after each request."""
|
||||
gc.collect()
|
||||
try:
|
||||
import torch
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
# Signal readiness with GPU status
|
||||
gpu = False
|
||||
@@ -207,7 +222,9 @@ def main():
|
||||
except ImportError as e:
|
||||
print(f"[dispatcher] GPU detection failed: {e}", file=sys.stderr, flush=True)
|
||||
print(json.dumps({"ready": True, "gpu": gpu}), file=sys.stderr, flush=True)
|
||||
print(f"[dispatcher] Ready. GPU: {gpu}. Modules: {list(available_modules.keys())}", file=sys.stderr, flush=True)
|
||||
print(f"[dispatcher] Ready. GPU: {gpu}. Max requests: {MAX_REQUESTS}. Modules: {list(available_modules.keys())}", file=sys.stderr, flush=True)
|
||||
|
||||
request_count = 0
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
@@ -241,6 +258,14 @@ def main():
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
_cleanup_after_request()
|
||||
request_count += 1
|
||||
|
||||
if request_count >= MAX_REQUESTS:
|
||||
print(f"[dispatcher] Reached max requests ({MAX_REQUESTS}), shutting down for restart",
|
||||
file=sys.stderr, flush=True)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user