Skip to content

fix(daemon): survive USB disconnect without crashing and re-attach cleanly - #9

Open
prodzaha wants to merge 1 commit into
HelloPrincePal:mainfrom
prodzaha:fix/disconnect-crash
Open

prodzaha wants to merge 1 commit into
HelloPrincePal:mainfrom
prodzaha:fix/disconnect-crash

Conversation

@prodzaha

Copy link
Copy Markdown

Problem

Unplugging the phone while the daemon is running can take the whole process down:

  • the USB watcher releases the *gousb.Device handle after the attach callback returns, while relay goroutines may still be inside gousb transfers;
  • a forced dev.Close() from a watcher goroutine races with in-flight cgo transfers (gousb._Cfunc_submit), producing SIGSEGV: signal arrived during cgo execution.

Observed twice on macOS 26 (Apple Silicon) + vivo X200 Pro mini: Relay session ended: USB device disconnected is followed by a SIGSEGV dump, launchd restarts the daemon, and the session is stuck until it does.

Changes

  • Run all bulk transfers through ReadContext/WriteContext so they observe relay cancellation and never race with a closed handle.
  • Track the USB-facing goroutines with a sync.WaitGroup; Relay.Stop waits for them to finish (2s cap) before the device handle is released.
  • Drop the watcher goroutine that force-closed the device; context cancellation is enough to unblock the reads.
  • Treat LIBUSB_ERROR_NO_DEVICE/NOT_FOUND (and LIBUSB_TRANSFER_NO_DEVICE) as fatal for the session so a disconnect ends the relay instead of looping forever, letting the USB watcher re-attach when the phone comes back.
  • Call relay.Stop() from the daemon's session-end path so the handle is only released once no transfers are in flight.

Testing

  • go vet ./..., go build ./... clean.
  • Runtime (macOS 26.6, Apple Silicon, vivo X200 Pro mini), physical cable unplug/replug:
    • before: SIGSEGV dump, process restart;
    • after: the disconnect is logged, the same process re-attaches, DHCP completes and traffic resumes — no crash line in the log.

Note: this branch alone does not make the vivo X200 Pro mini work end to end — the companion PR fixes the framing/filter side.

Unplugging the phone while the relay is running can drop the device
handle while the relay goroutines are still submitting USB transfers.
gousb then crashes inside cgo (SIGSEGV in _Cfunc_submit), the daemon
dies and the watcher never gets a chance to re-attach.

- run all bulk transfers through ReadContext/WriteContext so they
  observe relay cancellation and never race with a closed handle
- track the USB-facing goroutines with a WaitGroup and wait for them in
  Relay.Stop before the device handle is released
- stop force-closing the device from a watcher goroutine; context
  cancellation is enough to unblock the reads
- treat LIBUSB_ERROR_NO_DEVICE/NOT_FOUND as fatal for the session so a
  disconnect ends the relay instead of looping forever, which lets the
  USB watcher re-attach when the phone comes back
- call relay.Stop from the attach callback error path

Reproduced on macOS 26 (Apple Silicon) + vivo X200 Pro mini: pulling the
cable crashed the daemon twice with SIGSEGV during cgo. With this change
the disconnect is logged, the session ends gracefully and re-attaching
works without restarting the daemon.
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