mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 04:02:22 +02:00
229 lines
7.3 KiB
Python
229 lines
7.3 KiB
Python
"""The free engines race; a dead one must not stand between the user and an answer.
|
|
|
|
The bug these pin is a cold-start bug, so it hides from any average taken over a
|
|
long-lived process. Measured on a fresh backend against a DuckDuckGo that had
|
|
stopped answering TCP: q1 8,812ms, q2 8,825ms, q3 8,934ms, then 585ms. Breaker
|
|
state is per-process and the desktop app starts a backend on every launch, so
|
|
every user's first three searches after opening the app paid full price to
|
|
rediscover what the last process already knew.
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from backend.apps.web.keyless_race import KeylessEngine, race_keyless
|
|
from backend.apps.web.tier_breaker import (
|
|
FIRST_COOLDOWN_SECONDS,
|
|
reset_tier_health,
|
|
tier_cooldown_left,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def p_clean():
|
|
reset_tier_health()
|
|
yield
|
|
reset_tier_health()
|
|
|
|
|
|
def p_engine(name, fn):
|
|
return KeylessEngine(name=name, run=fn)
|
|
|
|
|
|
def p_answers(name, after=0.0, calls=None):
|
|
async def run():
|
|
if calls is not None:
|
|
calls.append(name)
|
|
if after:
|
|
await asyncio.sleep(after)
|
|
return {"backend": name}
|
|
return run
|
|
|
|
|
|
def p_silent(name, calls=None):
|
|
async def run():
|
|
if calls is not None:
|
|
calls.append(name)
|
|
await asyncio.sleep(30)
|
|
return run
|
|
|
|
|
|
def p_raises(name, calls=None):
|
|
async def run():
|
|
if calls is not None:
|
|
calls.append(name)
|
|
raise RuntimeError(f"{name} served its bot challenge")
|
|
return run
|
|
|
|
|
|
def p_empty(name, calls=None):
|
|
async def run():
|
|
if calls is not None:
|
|
calls.append(name)
|
|
return None
|
|
return run
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_healthy_leader_wins_alone_and_sends_no_extra_traffic():
|
|
"""The whole cost case for racing rests on this: a healthy engine must not double our requests."""
|
|
calls = []
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_answers("ddg", 0.01, calls)),
|
|
p_engine("startpage", p_answers("startpage", 0.01, calls))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert out.result == {"backend": "ddg"}
|
|
assert calls == ["ddg"], "the second engine must not be dispatched when the first is fast"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_silent_leader_costs_the_hedge_not_the_budget():
|
|
"""This is the cold-start fix: a blackholed engine used to cost its full 8s tier budget."""
|
|
loop = asyncio.get_running_loop()
|
|
t0 = loop.time()
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg")),
|
|
p_engine("startpage", p_answers("startpage", 0.01))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
elapsed = loop.time() - t0
|
|
assert out.result == {"backend": "startpage"}
|
|
assert elapsed < 1.0, f"a dead leader should cost about the hedge delay, took {elapsed:.2f}s"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_fast_failure_starts_the_next_engine_immediately():
|
|
"""An engine that refuses outright should not make us wait out the hedge as well."""
|
|
loop = asyncio.get_running_loop()
|
|
t0 = loop.time()
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_raises("ddg")),
|
|
p_engine("startpage", p_answers("startpage", 0.01))],
|
|
budget=5.0, hedge_after=2.0,
|
|
)
|
|
assert out.result == {"backend": "startpage"}
|
|
assert loop.time() - t0 < 1.0
|
|
assert any("bot challenge" in e for e in out.errors)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_silence_shuts_the_engine_after_one_query():
|
|
"""One conclusive silence, not three, so the tax is one slow search rather than three."""
|
|
await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg")),
|
|
p_engine("startpage", p_answers("startpage", 0.01))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert tier_cooldown_left("ddg") > 0
|
|
assert tier_cooldown_left("startpage") == 0.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_cooling_engine_is_not_dispatched_at_all():
|
|
calls = []
|
|
for _ in range(2):
|
|
await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg", calls)),
|
|
p_engine("startpage", p_answers("startpage", 0.01, calls))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert calls.count("ddg") == 1, "the second query must not re-probe a conclusively dead engine"
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg", calls)),
|
|
p_engine("startpage", p_answers("startpage", 0.01, calls))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert any("skipped, still failing" in e for e in out.errors)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_recovered_engine_is_used_again(monkeypatch):
|
|
"""The fix must not quietly blacklist an engine that comes back."""
|
|
import backend.apps.web.tier_breaker as TB
|
|
calls = []
|
|
await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg", calls)),
|
|
p_engine("startpage", p_answers("startpage", 0.01, calls))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert tier_cooldown_left("ddg") > 0
|
|
|
|
# Jump past the cooldown rather than sleep through it.
|
|
real_monotonic = TB.time.monotonic
|
|
monkeypatch.setattr(TB.time, "monotonic",
|
|
lambda: real_monotonic() + FIRST_COOLDOWN_SECONDS + 1)
|
|
calls.clear()
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_answers("ddg", 0.01, calls)),
|
|
p_engine("startpage", p_answers("startpage", 0.01, calls))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert out.result == {"backend": "ddg"}, "a healthy engine must be used again after the cooldown"
|
|
assert tier_cooldown_left("ddg") == 0.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_both_empty_reports_no_hits_not_a_failure():
|
|
"""A nonsense query must not slowly cool down two perfectly healthy engines."""
|
|
for _ in range(5):
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_empty("ddg")), p_engine("startpage", p_empty("startpage"))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert out.result is None
|
|
assert tier_cooldown_left("ddg") == 0.0
|
|
assert tier_cooldown_left("startpage") == 0.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_an_empty_leader_still_lets_the_other_answer():
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_empty("ddg")),
|
|
p_engine("startpage", p_answers("startpage", 0.01))],
|
|
budget=5.0, hedge_after=2.0,
|
|
)
|
|
assert out.result == {"backend": "startpage"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_every_engine_closed_returns_honestly():
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_raises("ddg")), p_engine("startpage", p_raises("startpage"))],
|
|
budget=5.0, hedge_after=0.3,
|
|
)
|
|
assert out.result is None
|
|
assert len(out.errors) == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_budget_bounds_the_whole_race():
|
|
loop = asyncio.get_running_loop()
|
|
t0 = loop.time()
|
|
out = await race_keyless(
|
|
[p_engine("ddg", p_silent("ddg")), p_engine("startpage", p_silent("startpage"))],
|
|
budget=0.6, hedge_after=0.2,
|
|
)
|
|
assert out.result is None
|
|
assert loop.time() - t0 < 2.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_losing_engines_do_not_outlive_the_race():
|
|
"""A cancelled request must not keep running and surprise the user's network later."""
|
|
live = {"n": 0}
|
|
|
|
async def clingy():
|
|
live["n"] += 1
|
|
try:
|
|
await asyncio.sleep(30)
|
|
finally:
|
|
live["n"] -= 1
|
|
|
|
await race_keyless(
|
|
[p_engine("ddg", clingy), p_engine("startpage", p_answers("startpage", 0.01))],
|
|
budget=5.0, hedge_after=0.2,
|
|
)
|
|
assert live["n"] == 0
|