Files
Jyotisha/tests/test_heavy_compute_processes_20261028.py
T
Jesse_ChenandClaude Opus 5.5 8d1cb4a53b Heavy routes run in worker processes, one per heavy slot (issue #133, BUG-1493)
The single ThreadingHTTPServer ran pure-Python v5 score / block_scan / report
reference under one GIL, so the gate's two slots shared a core. start_server
now starts a spawn pool (workers = heavy concurrency, PYTHONHASHSEED=0, engine
pre-imported); those three routes call the same compute method in a worker.
Over HTTP: score alone 4.74 s, two at once 5.01 / 4.58 s (1.06x; in-process
1.56x). Results equal except per-process fingerprints; report markdown equal.
~165 MB extra. A crashed pool answers inline and restarts; off with
JYOTISH_HEAVY_COMPUTE_PROCESSES=0; tests calling compute methods unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:28:05 +08:00

82 lines
2.6 KiB
Python

"""Issue #133: heavy routes run in worker processes when the server starts the pool."""
from __future__ import annotations
import os
import time
import pytest
from scripts.api_process_pool import pool
from scripts.jyotish_api_server import REPO_ROOT, SCRIPTS_DIR, BadRequest, JyotishComputeService
from tests.test_rectification_v5_vedastro_validation import request as v5_request
NOISE = ("fingerprint", "feature_hash", "_hash", "elapsed", "duration", "_ms", "timing")
def _body() -> dict:
body = v5_request()
body.pop("candidate_times")
return body
def _strip(value):
if isinstance(value, dict):
return {k: _strip(v) for k, v in value.items() if not any(n in k for n in NOISE)}
if isinstance(value, list):
return [_strip(v) for v in value]
return value
@pytest.fixture
def started_pool():
assert pool.start(2, [REPO_ROOT, SCRIPTS_DIR])
yield
pool.stop()
def test_without_the_pool_routes_run_in_process():
assert not pool.active()
calls = []
assert pool.run("_compute_rectification_v5_score", {"x": 1}, lambda body: calls.append(body) or "inline") == "inline"
assert calls == [{"x": 1}]
def test_a_pooled_score_equals_the_in_process_one(started_pool):
inline = JyotishComputeService()._compute_rectification_v5_score
pooled = pool.run("_compute_rectification_v5_score", _body(), lambda body: pytest.fail("ran inline"))
assert pooled["success"] is True
# Fingerprints already vary per process (ERR-111); everything else is identical.
assert _strip(pooled) == _strip(inline(_body()))
def test_bad_input_is_still_a_bad_request(started_pool):
body = _body()
body["start_time"] = "not-a-time"
with pytest.raises((BadRequest, ValueError, KeyError)):
pool.run("_compute_rectification_v5_score", body, lambda _body: pytest.fail("ran inline"))
def test_routes_outside_the_list_never_leave_the_process(started_pool):
assert pool.run("_compute_chart", {}, lambda body: "inline") == "inline"
def test_a_crashed_pool_answers_in_process_and_comes_back(started_pool):
victim = next(iter(pool._executor._processes.values()))
os.kill(victim.pid, 9)
time.sleep(0.5)
calls = []
result = pool.run("_compute_rectification_v5_score", _body(), lambda body: calls.append(1) or "inline")
assert result == "inline" and calls == [1]
for _ in range(120):
if pool.active():
break
time.sleep(0.5)
assert pool.active(), "the pool restarts in the background"
def test_the_switch_turns_the_pool_off(monkeypatch):
monkeypatch.setenv(pool.ENV_PROCESSES, "0")
assert pool.start(2, [REPO_ROOT, SCRIPTS_DIR]) is False
assert not pool.active()