Skip to content

Track Go Wireless and handoff together so a second request cannot run alongside - #51

Merged
ba6ul merged 1 commit into
mainfrom
fix/19-in-flight-guard
Oct 26, 2026
Merged

ba6ul merged 1 commit into
mainfrom
fix/19-in-flight-guard

Conversation

@ba6ul

@ba6ul ba6ul commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Refs #19

What

Go Wireless and handoff now share one in-flight tracker per USB serial. A second request for a phone that already has one running is turned away with a clear reason, and the reconnector leaves the phone alone while either runs.

Why

Only handoff was tracked (handoffInFlight), and only after its model lookup. Go Wireless, which waits up to 3 minutes, was not tracked at all, and neither flow checked for another. Two flows for one phone sent tcpip twice (each restarts adbd) and raced to launch a mirror, and the reconnector could probe the same address Go Wireless was already retrying.

How

  • InFlightOperations (small class, lib/src/in_flight_operations.dart): tryStart(serial, operation) returns null if it reserved the phone, or the name of the operation already running; finish(serial) releases it; serials is a copy for the reconnector.
  • bin/daemon.dart: startGoWireless / startHandoff reserve the phone as their first synchronous step (nothing awaits between the check and the reservation, so two commands arriving back to back cannot both pass) and release it in a finally. They return why a request was turned away, or null.
  • A turned-away request: the IPC reply is {started: false, reason} instead of {started: true}; a wirelessStatus / handoffStatus event with the new stage alreadyRunning and the reason is broadcast so a UI can show it (the UI displays stage names as received); the daemon logs it. The stdin keys log it too. Go Wireless vs Go Wireless, handoff vs handoff, and one against the other are all rejected, for the same phone only.
  • The reconnector gets inFlight.serials in both places it is used (the periodic tick and the manual retry), so it skips a phone while Go Wireless or handoff runs.
  • Auto-launch suppression stays handoff-only, as before: the issue lists auto-launch rules as out of scope, and holding it back during a 3-minute Go Wireless would stop the USB mirror coming back after the adbd restart.
  • Handoff now reserves the phone before its model lookup and settle wait, not after, so the earlier steps are covered too.
  • Protocol docs in messages.dart (comments only) describe the new reply shape and stage.
  • Not changed: queueing instead of rejecting; the UI (it ignores the reply and shows the status event).

Verified by

  • in_flight_operations_test.dart (7 new tests): first start, same-operation rejection, Go Wireless vs handoff in both directions, different phones independent, release and double release, serials is a copy, and the reconnector skips a phone during Go Wireless and resumes after.
  • Two process-level tests against the real daemon and the fake adb (using the harness from Honour ANDROID_ADB_SERVER_PORT and test the daemon failure paths against a fake adb #49): a second Go Wireless is rejected while the first waits for the hotspot (started: false, reason "already in progress"), a handoff is rejected too (reason "Go Wireless is in progress"), the status event alreadyRunning arrives, and the running flow never reports a failure; and after an operation ends (unknown serial fails at once) the same phone accepts a new request.
  • With the guard disabled the first process test fails (the second request is accepted); with it restored it passes.
  • dart analyze clean in core, ipc, daemon. Tests: core 27, ipc 26, daemon 97 (88 on main) pass locally on Windows. CI results are in the checks below; this is the first PR under the new branch protection.
  • Not verified: with a real phone; the UI rendering of the alreadyRunning stage.

Risk / follow-ups

  • The process tests run route print inside the daemon (Go Wireless looks for gateways); it is read-only, and on Ubuntu it simply finds none.
  • A flow that never ends keeps its phone reserved: Go Wireless is bounded by its 3-minute limit and handoff by its own timeouts, and finally releases it on any exit, but a hang inside an unbounded adb call (A stalled adb answer hangs its caller because the adb client has no read timeout #50) would hold the reservation until the call returns.
  • Pressing Go Wireless twice in the UI now shows alreadyRunning text but no dialog.

Only handoff was tracked, and only late. Both flows now reserve the phone in a
shared InFlightOperations before doing anything, release it in a finally, and
reject a second Go Wireless or handoff for the same phone with a reason (IPC
reply and an alreadyRunning status event). The reconnector skips a phone while
either runs; auto-launch suppression stays handoff-only.
@ba6ul
ba6ul merged commit 87dcab5 into main Oct 26, 2026
4 checks passed
@ba6ul
ba6ul deleted the fix/19-in-flight-guard branch October 26, 2026 21:49
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