Skip to content

Fix intermittent segfault in the Becker-port TCP server - #8

Open
peacedudes wants to merge 2 commits into
DrPitre:mainfrom
peacedudes:fix/tcp-server-host-thread-confinement
Open

Fix intermittent segfault in the Becker-port TCP server#8
peacedudes wants to merge 2 commits into
DrPitre:mainfrom
peacedudes:fix/tcp-server-host-thread-confinement

Conversation

@peacedudes

Copy link
Copy Markdown

drivewire-cli --tcp-port crashes intermittently while serving an XRoar/NitrOS-9 guest: EXC_BAD_ACCESS in Dictionary._Variant.lookup, under DriveWireHost.pollVirtualSerial(). We hit it five times in one day driving automated NitrOS-9 sessions through inetd virtual-serial channels.

Cause. DriveWireHost is effectively a main-queue object: accepted tcp listen connections append guest input and refresh channel status via DispatchQueue.main.async, and the idle watchdog is a run-loop Timer. DriveWireTCPServerDriver, though, delivered its Network callbacks on a private queue, so host.send(data:) read the same dictionaries the main queue was mutating. DriveWireTCPDriver already hops to the main queue before calling host.send(data:) for exactly this reason — the Becker-port listener was the one path that didn't.

Fix. One line: the driver's callback queue becomes DispatchQueue.main, so the listener, the guest connection, and host.send(data:) all run where the host expects. The CLI's RunLoop.current.run() services the main queue.

Evidence.

  • Thread Sanitizer, running drivewire-cli against a live XRoar/NitrOS-9 boot plus repeated inetd login sessions: 2 distinct Swift access races before (both reaching pollVirtualSerial state), 0 after.
  • New regression test testGuestTrafficReachesTheHostOnTheMainQueue fails on main"The host was driven on DriveWireTCPServer instead of the main queue" — and passes with the fix. A sanity test (testOpTimeAnswersOverTheListener) proves the listener answers at all, so a harness fault can't masquerade as the regression.
  • Full DriveWireTests suite passes, three consecutive runs.

Also in the tests commit

  • DriveWireTCPServerDriver.swift is added to the DriveWireTests target so the new test can reach it.
  • testDWINIT expected 0x00, but OP_DWINIT deliberately answers non-zero (0xFF) — its comment notes that a zero makes the NitrOS-9 driver treat the host as a DW3 server and disable the DW4 extensions — so that test currently fails on main. Updated the expectation to 0xFF. Happy to drop this hunk if you'd rather handle it separately.

🤖 Generated with Claude Code

peacedudes and others added 2 commits July 28, 2026 22:36
DriveWireHost is written against the main queue: accepted tcp-listen
connections append guest input and refresh channel status via
DispatchQueue.main, and its idle watchdog is a Timer, which needs a running
run loop. The Becker-port listener delivered its callbacks on a private
queue instead, so host.send(data:) read the same dictionaries the main
queue was mutating. Thread Sanitizer flags the race; in practice
drivewire-cli segfaults inside pollVirtualSerial(). Deliver every callback
on the main queue -- the contract DriveWireTCPDriver already keeps -- which
also keeps this driver's own connection property on a single queue.

Co-Authored-By: Claude Fable 5 <[email protected]>
…le 0x00

Add DriveWireTCPServerDriverTests: a QueueRecorder stands in as the host's
delegate and asserts guest traffic reaches DriveWireHost on the main queue,
where the host's own virtual-serial paths run. Against the previous driver
it fails with the host driven on DriveWireTCPServer; the fix turns it
green. A sanity test proves the listener answers OP_TIME at all, so a
harness fault cannot masquerade as the regression.

Also correct testDWINIT: it still expected 0x00 from OP_DWINIT, which
deliberately answers 0xFF -- its own comment records that a zero makes
NitrOS-9 treat the host as a DW3 server and disable every DW4 extension.

Co-Authored-By: Claude Fable 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant