VIOLET V0: reactor clock primitives + latency harness (gate PASSED)
clock.py: mono_ns single timebase; LatencyHistogram (raw reservoir, exact nearest-rank percentiles); PlaneClock per-plane seq clocks with strict staleness budgets (age==budget not stale — MHS FIX-9 lesson); DeadlineScheduler — single-driver timer heap with EARLY-WAKE on earlier-than-head insert (the jitter-budget mechanism), isolated callbacks. harness.py: seeded deterministic event storms (sequence-hash asserted) driving the REAL Rust ExecutionKernel via the MOCK bundle; reaction latency measured producer-stamp→post-fold across the queue hop exactly as the production account stream consumes; ACCOUNT_UPDATE wallet sentinel tracks kernel k_capital so synthetic storms never trip capital_frozen; sustained throughput reported alongside the gate. V0 GATE (prod host, 50k events, 5k concurrent deadlines, burst 8/12ms ≈ 667 ev/s offered): venue_event_reaction p99 7.19ms (<10ms budget), deadline_jitter p99 4.86ms (<25ms), zero early fires. Capacity artifacts: the 32/1ms and 16/12ms storms (archived reports) show intra-burst queueing dominating beyond ~1.3k ev/s offered at ~0.33ms/fold. 17 unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
112
prod/clean_arch/violet/test_violet_deadline_scheduler.py
Normal file
112
prod/clean_arch/violet/test_violet_deadline_scheduler.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""V0: DeadlineScheduler — ordering, cancellation, early-wake, jitter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/mnt/dolphinng5_predict")
|
||||
|
||||
import pytest
|
||||
|
||||
from prod.clean_arch.violet.clock import DeadlineScheduler, LatencyHistogram, mono_ns
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deadlines_fire_in_due_order_after_shuffled_insert():
|
||||
fired: list[int] = []
|
||||
sched = DeadlineScheduler()
|
||||
sched.start()
|
||||
base = mono_ns()
|
||||
order = list(range(1_000))
|
||||
random.Random(7).shuffle(order)
|
||||
for k in order:
|
||||
due = base + (k + 1) * 1_000_000 # 1ms apart, shuffled insertion
|
||||
sched.schedule_at(due, (lambda kk=k: fired.append(kk)))
|
||||
await asyncio.sleep(1.3)
|
||||
await sched.stop()
|
||||
assert len(fired) == 1_000
|
||||
assert fired == sorted(fired)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancellation_prevents_fire():
|
||||
fired: list[str] = []
|
||||
sched = DeadlineScheduler()
|
||||
sched.start()
|
||||
keep = sched.schedule_in(30, lambda: fired.append("keep"))
|
||||
kill = sched.schedule_in(30, lambda: fired.append("kill"))
|
||||
kill.cancel()
|
||||
await asyncio.sleep(0.12)
|
||||
await sched.stop()
|
||||
assert fired == ["keep"]
|
||||
assert kill.cancelled and not kill.fired
|
||||
assert keep.fired
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_early_wake_on_earlier_insert():
|
||||
"""A deadline inserted BELOW the current head must fire on time even when
|
||||
the driver was sleeping toward a much later head — the early-wake path."""
|
||||
sched = DeadlineScheduler(max_sleep_ms=5_000) # tick alone would be too slow
|
||||
jit = LatencyHistogram("j")
|
||||
sched.jitter_hist = jit
|
||||
sched.start()
|
||||
fired_at: list[int] = []
|
||||
sched.schedule_in(4_000, lambda: None) # far head; driver sleeps
|
||||
await asyncio.sleep(0.05) # driver now parked
|
||||
due = mono_ns() + 20_000_000 # 20ms from now
|
||||
sched.schedule_at(due, lambda: fired_at.append(mono_ns()))
|
||||
await asyncio.sleep(0.15)
|
||||
await sched.stop()
|
||||
assert fired_at, "early deadline never fired — early-wake broken"
|
||||
lateness_ms = (fired_at[0] - due) / 1e6
|
||||
assert lateness_ms < 25.0, f"early-wake too late: {lateness_ms:.1f}ms"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_early_fires_and_jitter_budget_under_cpu_noise():
|
||||
jit = LatencyHistogram("jitter")
|
||||
sched = DeadlineScheduler(jitter_hist=jit)
|
||||
sched.start()
|
||||
early = [0]
|
||||
rng = random.Random(11)
|
||||
dues = []
|
||||
for _ in range(400):
|
||||
due = mono_ns() + int(rng.uniform(10, 800) * 1e6)
|
||||
dues.append(due)
|
||||
sched.schedule_at(due, (lambda d=due: early.__setitem__(0, early[0] + 1) if mono_ns() < d else None))
|
||||
|
||||
async def cpu_noise():
|
||||
end = mono_ns() + int(0.9e9)
|
||||
while mono_ns() < end:
|
||||
sum(i * i for i in range(2_000))
|
||||
await asyncio.sleep(0)
|
||||
|
||||
noise = asyncio.create_task(cpu_noise())
|
||||
await asyncio.sleep(1.0)
|
||||
noise.cancel()
|
||||
await sched.stop()
|
||||
assert early[0] == 0, "deadline fired EARLY"
|
||||
assert sched.fired_count == 400
|
||||
p99_ms = jit.percentile_ns(0.99) / 1e6
|
||||
assert p99_ms < 25.0, f"jitter p99 {p99_ms:.2f}ms exceeds 25ms budget"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_callback_exception_does_not_kill_driver():
|
||||
errors: list[str] = []
|
||||
sched = DeadlineScheduler(on_error=lambda dl, e: errors.append(str(e)))
|
||||
sched.start()
|
||||
fired: list[str] = []
|
||||
sched.schedule_in(10, lambda: (_ for _ in ()).throw(RuntimeError("boom")))
|
||||
sched.schedule_in(40, lambda: fired.append("after"))
|
||||
await asyncio.sleep(0.12)
|
||||
await sched.stop()
|
||||
assert errors and "boom" in errors[0]
|
||||
assert fired == ["after"], "driver died after callback exception"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(pytest.main([__file__, "-v"]))
|
||||
Reference in New Issue
Block a user