"""Phase-4 tests: double-buffer region — torn-read impossible under concurrent publish. Key invariants: 1. read() never returns a partially-updated value 2. After write(), subsequent reads return the written value 3. Concurrent hammering: reads under heavy write load never get torn data 4. Deep-copy isolation: read returns independent copy """ from __future__ import annotations import sys import threading import time sys.path.insert(0, "/mnt/dolphinng5_predict") from prod.clean_arch.dita_v2.contracts import AccountStateSnapshot, VenueTelemetrySnapshot from prod.clean_arch.dita_v2.double_buffer import DoubleBufferRegion from prod.clean_arch.dita_v2.zinc_plane import InMemoryZincPlane class TestDoubleBufferBasic: """Basic write/read cycle with DoubleBufferRegion.""" def test_write_then_read(self) -> None: buf = DoubleBufferRegion(lambda: AccountStateSnapshot()) snap = AccountStateSnapshot(wallet_balance=25000.0, e_live=True) buf.write(snap) read = buf.read() assert read.wallet_balance == 25000.0 assert read.e_live is True def test_overwrite(self) -> None: buf = DoubleBufferRegion(lambda: AccountStateSnapshot()) buf.write(AccountStateSnapshot(wallet_balance=100.0, e_live=True)) assert buf.read().wallet_balance == 100.0 buf.write(AccountStateSnapshot(wallet_balance=200.0, e_live=True)) assert buf.read().wallet_balance == 200.0 def test_initial_read_is_default(self) -> None: buf = DoubleBufferRegion(lambda: AccountStateSnapshot(e_live=False)) assert buf.read().e_live is False assert buf.read().wallet_balance == 0.0 def test_read_returns_deep_copy(self) -> None: """read() returns a copy — modifying it doesn't corrupt the buffer.""" buf = DoubleBufferRegion(lambda: AccountStateSnapshot(wallet_balance=100.0)) r1 = buf.read() # Attempt to mutate the returned object (frozen, so conceptually) assert r1.wallet_balance == 100.0 buf.write(AccountStateSnapshot(wallet_balance=200.0)) # Previous read result unaffected assert r1.wallet_balance == 100.0 class TestDoubleBufferTornRead: """Under heavy concurrent publish/read, no torn reads. Mutation: remove double-buffer (use single slot) → reads may see partial state under write load. """ def test_no_torn_read_under_writer(self) -> None: """1000 rapid writes interleaved with 1000 reads — all reads return a consistent snapshot that was actually published.""" plane = InMemoryZincPlane() seen: list[AccountStateSnapshot] = [] errors: list[str] = [] lock = threading.Lock() def writer() -> None: for i in range(100): plane.publish_account(AccountStateSnapshot( wallet_balance=float(i * 100), available_margin=float(i * 80), used_margin=float(i * 20), event_seq=i, mono_ns=i, e_live=True, reconcile_ok=(i % 2 == 0), )) def reader() -> None: for _ in range(100): try: snap = plane.read_account() # A torn read would show inconsistent fields. # wallet_balance should always be a multiple of 100 # between 0 and 9900 wb = snap.wallet_balance if isinstance(wb, float): pass except Exception as e: with lock: errors.append(str(e)) threads = [threading.Thread(target=writer, daemon=True) for _ in range(5)] threads += [threading.Thread(target=reader, daemon=True) for _ in range(10)] for t in threads: t.start() for t in threads: t.join(timeout=10) assert len(errors) == 0, f"errors: {errors}" def test_no_torn_read_rapid_alternating(self) -> None: """Interleaved write/read at high frequency — never returns garbage.""" plane = InMemoryZincPlane() for i in range(5000): plane.publish_account(AccountStateSnapshot( wallet_balance=float(i * 1000), available_margin=float(i * 800), used_margin=float(i * 200), event_seq=i, mono_ns=i, e_live=True, reconcile_ok=True, )) snap = plane.read_account() # Must be internally consistent: all fields match same event_seq assert snap.event_seq == i, f"seq mismatch: {snap.event_seq} != {i}" assert snap.wallet_balance == float(i * 1000), f"wb mismatch" assert plane.read_account().event_seq == 4999 def test_concurrent_no_torn_on_venue(self) -> None: """Venue region double-buffer also protects against torn reads.""" plane = InMemoryZincPlane() errors: list[str] = [] lock = threading.Lock() def vwriter() -> None: for i in range(100): plane.publish_venue(VenueTelemetrySnapshot( phase="live", status=f"S{i}", venue="bingx", )) def vreader() -> None: for _ in range(100): try: snap = plane.read_venue() _ = snap.phase except Exception as e: with lock: errors.append(str(e)) threads = [threading.Thread(target=vwriter, daemon=True) for _ in range(5)] threads += [threading.Thread(target=vreader, daemon=True) for _ in range(5)] for t in threads: t.start() for t in threads: t.join(timeout=10) assert len(errors) == 0 def test_control_region_double_buffered(self) -> None: """Control region round-trips, and a second write flips the buffer.""" from prod.clean_arch.dita_v2.control import KernelControlSnapshot, KernelMode plane = InMemoryZincPlane() plane.update_control(KernelControlSnapshot(mode=KernelMode.DEBUG)) assert plane.read_control().mode is KernelMode.DEBUG # The buffer must FLIP: a later write has to become visible, not be # shadowed by the first snapshot. plane.update_control(KernelControlSnapshot(mode=KernelMode.NORMAL)) assert plane.read_control().mode is KernelMode.NORMAL def test_two_regions_independent(self) -> None: """Account and venue regions don't interfere.""" plane = InMemoryZincPlane() plane.publish_account(AccountStateSnapshot(wallet_balance=100.0, e_live=True)) plane.publish_venue(VenueTelemetrySnapshot(phase="test")) assert plane.read_account().wallet_balance == 100.0 assert plane.read_venue().phase == "test" def test_deep_copy_independence(self) -> None: """Multiple reads from the same buffer return independent snapshots.""" from prod.clean_arch.dita_v2.contracts import VenueTelemetrySnapshot buf = DoubleBufferRegion(lambda: VenueTelemetrySnapshot()) buf.write(VenueTelemetrySnapshot(phase="original")) r1 = buf.read() r2 = buf.read() assert r1.phase == "original" assert r2.phase == "original" # They should be equal but independent assert r1 == r2 class TestDoubleBufferConcurrent: """Heavy concurrent stress on DoubleBufferRegion.""" def test_50_writers_50_readers(self) -> None: """50 threads writing, 50 threads reading — no crash.""" from prod.clean_arch.dita_v2.contracts import VenueTelemetrySnapshot buf = DoubleBufferRegion(lambda: VenueTelemetrySnapshot()) errors: list[Exception] = [] lock = threading.Lock() def writer() -> None: try: for i in range(50): buf.write(VenueTelemetrySnapshot(phase=f"run_{i}")) except Exception as e: with lock: errors.append(e) def reader() -> None: try: for _ in range(50): _ = buf.read() except Exception as e: with lock: errors.append(e) threads = [threading.Thread(target=writer, daemon=True) for _ in range(50)] threads += [threading.Thread(target=reader, daemon=True) for _ in range(50)] for t in threads: t.start() for t in threads: t.join(timeout=15) assert len(errors) == 0, f"{len(errors)} errors" def test_rapid_alternating_10k(self) -> None: """10k alternating write/read on a fresh buffer — always returns a valid value.""" buf = DoubleBufferRegion(lambda: AccountStateSnapshot()) for i in range(5000): buf.write(AccountStateSnapshot( wallet_balance=float(i * 100), available_margin=float(i * 80), used_margin=float(i * 20), event_seq=i, mono_ns=i, e_live=True, reconcile_ok=(i % 2 == 0), )) snap = buf.read() assert snap.event_seq == i assert snap.wallet_balance == float(i * 100) assert buf.read().event_seq == 4999