Skip to content

Phase 0.3: network simulator, implemented from scratch#55

Merged
ptesavol merged 1 commit into
mainfrom
claude/phase-0.3-simulator
Jul 6, 2026
Merged

Phase 0.3: network simulator, implemented from scratch#55
ptesavol merged 1 commit into
mainfrom
claude/phase-0.3-simulator

Conversation

@ptesavol

@ptesavol ptesavol commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Final piece of milestone 0 (plan phase 0.3): the in-process network simulator, written from scratch for the threaded C++ runtime behind the TS-compatible API surface (plan decision 3.1, option b), so simulator-based TS integration tests port mechanically from here on.

The simulator (packages/streamr-dht/modules/connection/simulator/)

  • Simulator — single dispatcher thread owns all delivery; operations ordered by (deadline, sequence) in a priority queue; per-association FIFO enforced with the same deadline clamp as TS; the simulator mutex is never held across call-outs (deadlock hygiene with the connection layer's own locks). LatencyType NONE / RANDOM / REAL / FIXED.
  • RegionPings — the 16×16 AWS-region ping matrix taken verbatim from TS pings.ts (data only).
  • SimulatorInterfaces — breaks the Simulator↔Connection↔Connector import cycle the TS originals have (C++ module imports must be acyclic).
  • SimulatorConnection, SimulatorConnector (tracks handshakers exactly like the websocket connectors — C++ design wins), SimulatorConnectorFacade, SimulatorTransport.
  • Unit suite first (SimulatorTest.cpp, 10 tests: delivery, ordering, latency, close semantics, error paths) — stress-verified with 500 consecutive executions, zero failures.

Ported integration tests

  • SimultaneousConnectionsTest.cpp — TS "simultanous simulated connection" (both nodes connect at once → endpoint tie-break path). The TS file's websocket/WebRTC sections are milestone B scope.
  • ConnectionManagerIntegrationTest.cpp — TS "Connects and disconnects over simulated connections" (disconnect adapted to the public API: TS reached a private method via @ts-expect-error).

Three real bugs found by the ported tests, fixed here

  1. ConnectingEndpointState discarded its HandlerTokens, so removeEventHandlers() detached nothing — a pending connection replaced by the simultaneous-connect tiebreak kept driving a possibly-destroyed Endpoint (crash, reproducible before the fix).
  2. Handshakers registered connection handlers in constructors with raw this captures. A once handler already dequeued for invocation survives a concurrent off(), so a stopped-and-destroyed handshaker could still be invoked. Handlers now register in newInstance() (after the shared_ptr exists) and hold weak references.
  3. waitForCondition's Poller lived in the coroutine frame while its poll callback (raw this) ran on the folly scheduler thread — and the timeout path never stopped polling at all (segfaults under stress, and the historical test flakiness test.sh's comment describes). The poller is now owned by its own callback via shared_ptr and stopped on every path.

Known remaining issue → plan phase A0 (scheduled, chip filed)

Under --gtest_repeat stress (not single runs, which are stable and how CI executes), two pre-existing ABBA lock-order inversions deadlock: Endpoint::mutex ↔ endpoint-state mutexes (main-thread send vs. dispatcher connected-handler) and endpointsMutexEndpoint::mutex (acceptNewConnection/setConnecting vs. handleDisconnect → container removal). Full diagnosis with thread dumps is in the plan's new phase A0 — a locking-discipline pass (one mutex per endpoint state machine, no call-outs under these locks, TSAN leg), prerequisite for scaling simulator tests in milestone A. TypeScript never hits any of this — it is single-threaded.

Lint note

The three new test TUs join lint.sh's existing owner-approved clangd exclusion list (same preamble/BMI std-type unification false positive as PendingConnectionTest.cpp; the compiler accepts and runs them on every platform, clang-format still checks them).

Test plan

  • Full monorepo build (macOS arm64, Debug)
  • ./test.sh — 343/343 tests pass (10 simulator unit tests + 2 ported integration tests new)
  • Simulator unit suite stress: --gtest_repeat=50 × 10 tests, zero failures
  • ./lint.sh — clean

🤖 Generated with Claude Code

The last piece of milestone 0 (trackerless-network-completion-plan.md):
an in-process network simulator with the TS-compatible API surface, so
ported integration tests translate mechanically, but designed for the
threaded C++ runtime (plan decision 3.1, option b):

- modules/connection/simulator/: RegionPings (latency DATA taken
  verbatim from TS pings.ts), SimulatorInterfaces (breaks the TS import
  cycle), Simulator (single dispatcher thread, operations ordered by
  (deadline, seq) in a priority queue, per-association FIFO clamp, the
  simulator mutex is never held across call-outs), SimulatorConnection,
  SimulatorConnector (handshaker-tracking like the websocket connectors),
  SimulatorConnectorFacade + SimulatorTransport.
- SimulatorTest.cpp: 10-test unit suite pinning delivery, ordering,
  latency, close semantics (stress-verified: 500 executions green).
- Ported TS integration tests: SimultaneousConnections 'simultaneous
  simulated connection' and ConnectionManager 'connects and disconnects
  over simulated connections' (the TS files' websocket/WebRTC cases are
  ported in milestone B).

The ported simultaneous-connect test found three real bugs, fixed here:
- ConnectingEndpointState discarded the HandlerTokens of its pending-
  connection handlers, so a tiebreak-replaced pending connection kept
  driving a possibly-destroyed Endpoint (crash).
- The handshakers registered connection event handlers in their
  constructors with raw `this` captures; a once-handler already
  dequeued for invocation survives a concurrent off(), so a stopped and
  destroyed handshaker could still be invoked (crash). Handlers are now
  registered in newInstance() after construction and hold weak
  references.
- waitForCondition kept its Poller in the coroutine frame while the
  poll callback (raw `this`) ran on the folly scheduler thread, and
  never stopped polling on the timeout path (segfaults under stress).
  The poller is now shared_ptr-owned by its own callback and stopped on
  every path.

Remaining known issue, diagnosed and scheduled as plan phase A0 (not a
regression; single runs are stable, only --gtest_repeat stress trips
it): two pre-existing ABBA lock-order inversions between
Endpoint::mutex, the endpoint state mutexes and
ConnectionManager::endpointsMutex. Thread dumps and repro in the plan.

The three new test TUs join lint.sh's owner-approved clangd exclusion
list (same preamble/BMI std-type unification false positive as
PendingConnectionTest; the compiler accepts and runs them everywhere).

Verified: full build, 343/343 tests, lint clean (macOS host).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@ptesavol ptesavol merged commit 45eb671 into main Jul 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant