This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 11:57:16 +02:00
1 parent 6b937aaf85
commit 87c8393d86
79 files changed
+6727 -297

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Finite live HTTP concurrency correctness stress; no throughput/latency claim."""
from __future__ import annotations
import argparse
from concurrent.futures import ThreadPoolExecutor
import hashlib
import json
from pathlib import Path
import threading
import time
import traceback
from common import Fixture, http, seal_results, sha256, write_json
from resources import process_stats
PAYLOADS = ["small.html", "large.bin", "huge.bin"]
def active_backends(rust):
status, _, body = rust.admin("/api/status")
if status != 200:
return {"error": f"HTTP {status}"}
values = []
def walk(value):
if isinstance(value, dict):
if "id" in value and "active" in value and "status" in value and "requests" in value:
values.append({key: value[key] for key in ("id", "active", "status", "requests")})
for child in value.values():
walk(child)
elif isinstance(value, list):
for child in value:
walk(child)
walk(json.loads(body))
return values
def batch(rust, port, name, expected, concurrency):
count = max(32, concurrency * 2)
expected_sha = hashlib.sha256(expected).hexdigest()
ready = threading.Barrier(concurrency, timeout=15)
lock = threading.Lock()
running = peak = 0
def fetch(index):
nonlocal running, peak
with lock:
running += 1
peak = max(peak, running)
try:
if index < concurrency:
ready.wait()
status, headers, body = http(port, "/" + name, timeout=15)
digest = hashlib.sha256(body).hexdigest()
return {"index": index, "status": status, "bytes": len(body), "sha256": digest,
"passed": status == 200 and len(body) == len(expected) and digest == expected_sha}
except Exception as error:
return {"index": index, "passed": False, "error": str(error)}
finally:
with lock:
running -= 1
before = process_stats(rust.process.pid)
started = time.monotonic()
with ThreadPoolExecutor(max_workers=concurrency) as executor:
responses = list(executor.map(fetch, range(count)))
elapsed = time.monotonic() - started
# Completion means every client consumed or failed its full response. Give
# asynchronous server bookkeeping a bounded interval to release its leases.
for _ in range(40):
active = active_backends(rust)
if isinstance(active, list) and active and all(row["active"] == 0 for row in active):
break
time.sleep(.05)
after = process_stats(rust.process.pid)
return {"configured_concurrent_clients": concurrency, "peak_running_client_tasks": peak,
"client_tasks_include_barrier": True, "requests": count, "elapsed_seconds": elapsed,
"expected_bytes": len(expected), "expected_sha256": expected_sha,
"response_failures": sum(not response["passed"] for response in responses),
"passed": all(response["passed"] for response in responses)
and isinstance(active, list) and bool(active) and all(row["active"] == 0 for row in active),
"responses": responses, "active_backends_after_drain": active,
"server_before": before, "server_after_drain": after}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--binary", action="append", required=True, metavar="LABEL=/ABS/PATH")
parser.add_argument("--nginx", type=Path, required=True)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
if args.out.exists() and any(args.out.iterdir()):
parser.error("--out must be new or empty")
reports = {}
for specification in args.binary:
label, binary = specification.split("=", 1)
cases, error = [], None
try:
with Fixture(args.out / label, workers=4, origin_workers=2) as fixture:
fixture.nginx(args.nginx, frontend=False)
rust = fixture.rust(Path(binary), label)
rust.login()
for source, port in [("proxy", rust.reverse_port), ("static", rust.static_port)]:
for name in PAYLOADS:
expected = (fixture.site / name).read_bytes()
for concurrency in [1, 16, 128]:
result = batch(rust, port, name, expected, concurrency)
case = {"case": f"concurrency.{source}.{name}.c{concurrency}", **result}
cases.append(case)
write_json(args.out / label / "cases" / f"{source}-{name}-c{concurrency}.json", case)
print(f"{label} {'PASS' if case['passed'] else 'FAIL'} {case['case']}", flush=True)
if rust.process.poll() is not None:
raise RuntimeError("RustProxy exited during concurrency regression")
except Exception as exc:
error = {"error": str(exc), "traceback": traceback.format_exc()}
write_json(args.out / label / "failure.json", error)
report = {"kind": "live HTTP/1.1 finite concurrency functional stress", "simulation": False,
"binary_sha256": sha256(Path(binary)), "throughput_measurement": False,
"passed_cases": sum(case["passed"] for case in cases),
"failed_cases": sum(not case["passed"] for case in cases),
"requests": sum(case["requests"] for case in cases),
"response_failures": sum(case["response_failures"] for case in cases),
"completed": error is None and len(cases) == 18,
"cases": [{key: case[key] for key in ("case", "passed", "requests", "response_failures")} for case in cases],
"limitations": ["configured client concurrency is not proof of simultaneous active server exchanges",
"Python client is not a throughput or latency benchmark",
"FD/RSS are before/after snapshots, not peaks or a long-term leak test"]}
reports[label] = report
write_json(args.out / label / "report.json", report)
write_json(args.out / "comparison.json", reports)
seal_results(args.out)
return 0 if all(report["completed"] and not report["failed_cases"] for report in reports.values()) else 1
if __name__ == "__main__":
raise SystemExit(main())