Spaces:
Sleeping
Sleeping
File size: 6,153 Bytes
b972454 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | from __future__ import annotations
import json
import os
import socket
import threading
import time
import urllib.error
import urllib.request
from pathlib import Path
# Quota-free test of the production concurrency bounds: the bounded HTTP server returns
# 503 over its worker cap, drops slowloris connections at the socket timeout, and the
# JobRunner returns 429 past MAX_INFLIGHT_JOBS. MOCK_MODE=1 python tools/sim_server_limits.py
os.environ.setdefault("MOCK_MODE", "1")
os.environ.setdefault("LOOM_LOG", "0")
os.environ["CRITIQUE_TOKEN"] = "test"
os.environ["MAX_WORKERS"] = "2"
os.environ["REQUEST_TIMEOUT_S"] = "2"
os.environ["MAX_INFLIGHT_JOBS"] = "3"
os.environ["CACHE_ENABLED"] = "0"
os.environ.pop("METRICS_HF_REPO", None)
os.environ.pop("JOBS_HF_REPO", None)
os.environ.pop("OPTIN_PROVIDERS", None)
for key in ("NVIDIA_API_KEY", "CF_API_TOKEN", "CF_ACCOUNT_ID", "OPENROUTER_API_KEY", "GITHUB_TOKEN"):
os.environ[key] = "mock"
# slow every mock call so sync requests stay in-flight long enough to saturate workers.
os.environ["MOCK_LATENCY_S"] = "1.5"
import sys # noqa: E402
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import critique_service as cs # noqa: E402
PORT = 7971
TOK = "test"
FAILS: list[str] = []
def check(name, cond, detail=""):
print(f" [{'PASS' if cond else 'FAIL'}] {name}" + (f" ({detail})" if detail else ""))
if not cond: FAILS.append(name)
def _serve():
panel = cs.Panel()
srv = cs.BoundedThreadingHTTPServer(("127.0.0.1", PORT), cs.Handler, max_workers=2)
srv.panel = panel
from jobs import JobRunner
srv.jobs = JobRunner(panel, judge_timeout_s=30)
threading.Thread(target=srv.serve_forever, daemon=True).start()
return srv
def _post(path, body, timeout=20):
req = urllib.request.Request(f"http://127.0.0.1:{PORT}{path}", data=json.dumps(body).encode(),
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"})
try:
r = urllib.request.urlopen(req, timeout=timeout)
return r.status, json.load(r), dict(r.headers)
except urllib.error.HTTPError as e:
return e.code, (json.load(e) if e.headers.get("Content-Type","").startswith("application/json") else {}), dict(e.headers)
def main() -> int:
srv = _serve()
time.sleep(1.5)
print("scenario: over MAX_WORKERS concurrent sync requests -> 503 + Retry-After")
results: list = []
def hit():
try:
results.append(_post("/api/panel", {"input": "hi", "role": "verifier",
"panel": ["llama-3.3-70b"], "effort": "low",
"privacy": "off"}))
except Exception as e: # noqa: BLE001
results.append(("EXC", repr(e)[:60], {}))
# 6 concurrent against max_workers=2 + slow (1.5s) mock -> some must be rejected.
threads = [threading.Thread(target=hit) for _ in range(6)]
for t in threads: t.start()
for t in threads: t.join()
codes = [r[0] for r in results]
n503 = sum(1 for c in codes if c == 503)
n200 = sum(1 for c in codes if c == 200)
check("some requests succeeded", n200 >= 1, f"codes={codes}")
check("excess requests got 503", n503 >= 1, f"codes={codes}")
ra = [h.get("Retry-After") for c, _b, h in results if c == 503]
check("503 carries Retry-After", any(ra), f"retry_after={ra}")
print("scenario: slowloris connection dropped at REQUEST_TIMEOUT_S")
time.sleep(3.0) # let the prior slow sync requests drain so workers are free
s = socket.create_connection(("127.0.0.1", PORT), timeout=10)
# valid auth so the handler proceeds to READ the body, then we send none -> the
# socket read must hit REQUEST_TIMEOUT_S and the worker is freed (not pinned).
s.sendall(b"POST /api/panel HTTP/1.0\r\n"
b"Authorization: Bearer " + TOK.encode() + b"\r\n"
b"Content-Type: application/json\r\n"
b"Content-Length: 1000\r\n\r\n") # promise 1000 bytes, send none
s.settimeout(8)
t0 = time.time()
try:
data = s.recv(100) # server should close (timeout) within ~REQUEST_TIMEOUT_S(2s)
# acceptable "not pinned" outcomes: connection closed (b""), a timeout/400 status,
# or an immediate 503 (capacity) - any means the worker isn't held for our 8s.
dropped = (data == b"" or any(code in data for code in (b"400", b"408", b"503")))
except (socket.timeout, ConnectionResetError, OSError):
dropped = True
elapsed = time.time() - t0
s.close()
check("slowloris dropped within the timeout (worker not pinned)", dropped and elapsed < 7,
f"elapsed={elapsed:.1f}s data={data[:40] if 'data' in dir() else 'exc'!r}")
print("scenario: async submits past MAX_INFLIGHT_JOBS -> 429 + Retry-After")
codes2 = []
for _ in range(6):
c, _b, h = _post("/api/panel", {"input": "x", "role": "verifier",
"panel": ["llama-3.3-70b"], "effort": "low",
"privacy": "off", "async": True})
codes2.append((c, h.get("Retry-After")))
n429 = sum(1 for c, _ra in codes2 if c == 429)
n202 = sum(1 for c, _ra in codes2 if c == 202)
check("some async jobs accepted (<=cap)", n202 >= 1, f"codes={codes2}")
check("async jobs past cap rejected with 429", n429 >= 1, f"codes={codes2}")
check("429 carries Retry-After", any(ra for c, ra in codes2 if c == 429), f"codes={codes2}")
print("scenario: /health exposes saturation counters")
req = urllib.request.Request(f"http://127.0.0.1:{PORT}/health")
h = json.load(urllib.request.urlopen(req, timeout=10))
check("/health has workers.max", (h.get("workers") or {}).get("max") == 2, str(h.get("workers")))
check("/health has jobs_inflight", "jobs_inflight" in h, str(h.get("jobs_inflight")))
srv.shutdown()
print()
if FAILS:
print(f"FAILED ({len(FAILS)}): {FAILS}"); return 1
print("ALL SERVER-LIMIT SCENARIOS PASSED (zero real API calls)"); return 0
if __name__ == "__main__":
raise SystemExit(main())
|