Skip to content

Give Go Wireless a 5 s tcpip timeout and settle the phone before retrying - #48

Merged
ba6ul merged 3 commits into
mainfrom
fix/45-gowireless-tcpip-timeout
Oct 26, 2026
Merged

ba6ul merged 3 commits into
mainfrom
fix/45-gowireless-tcpip-timeout

Conversation

@ba6ul

@ba6ul ba6ul commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Refs #45

What

Go Wireless now gives the phone 5 seconds to acknowledge the switch to TCP mode, fails explicitly if it does not, and makes the next Go Wireless or handoff check the phone's real state before sending anything again.

Why

WirelessManager.goWireless waited for tcpip with no time limit (AdbClient has no read timeout), so a phone or adb that stopped answering hung the flow before its 3-minute connect loop began; handoff already limits the same call to 5 s. A timeout also does not prove the switch did not happen: the phone can still switch to TCP mode late, which restarts adbd and briefly drops the USB connection.

How

Decisions from the owner (2026-10-27): 5 s, explicit failure, preserve the USB mirror, account for late completion.

  • tcpip is wrapped in .timeout(5 s) (tcpipTimeout, injectable). On timeout the flow reports failed with a plain reason ("did not answer ... It may still switch; wait for it to reconnect, then try again. The USB mirror was left as it is.") and returns null. It does not advance to tcpipEnabled or the connect loop, so a timeout is never treated as success. WirelessManager has no access to the launcher, so the USB mirror cannot be stopped by this path.
  • The serial is remembered as uncertain. The original request keeps running; if it completes (or fails) later, that is only logged, not treated as the phone being ready.
  • New settle(serial): a no-op unless the serial is uncertain. Otherwise it polls adb devices until the phone is listed ready on two consecutive checks (so an adbd restart still in progress does not count), up to 15 s, and returns false if that does not happen. goWireless calls it first and fails with "the phone has not come back since the last attempt..." without sending another tcpip. The daemon's runHandoff calls it too before starting a handoff.
  • A definite failure (unknown serial, adb server gone) is not uncertain and does not trigger settling.
  • The ip route read before tcpip also gets the 5 s limit, since a wedged transport would otherwise hang there first; a timeout there just means no cached WiFi IP, as before.
  • FakeAdbServer.stallTcpipReplies(bool) holds tcpip requests and answers them when released, and tcpipRequests counts them.
  • Visible wait. settle now reports waitingForPhone (with the reason and the limit) through wirelessStatus / handoffStatus and logs it, before anything else, so the up to 15 s wait is not silent; if it ends in failure the existing failed status carries the reason. Protocol docs in messages.dart list the new stage (comment only; the UI shows stage names as received).
  • Handoff after an unanswered tcpip. Handoff still carries on to the connect step after its 5 s timeout (by design, Real "connected" signal from scrcpy stdout, with retry on early exit #20), but it was reporting tcpipEnabled for something nothing had confirmed, and if the connect step then failed nothing remembered that the phone may still switch late. Now it reports tcpipUnconfirmed ("did not acknowledge ... trying to connect anyway"), and if it ends without a wireless connection after an unanswered tcpip it calls onTcpipUnconfirmed, which the daemon wires to markUncertain, so the next Go Wireless or handoff settles the phone first. A confirmed wireless connection settles the question, so a successful handoff is not flagged, and neither is a failed connect after an acknowledged tcpip. The handoff tcpip timeout is now a parameter (default 5 s) so it can be tested.
  • Not changed: the missing read timeout in AdbClient (broader than this PR, tracked in A stalled adb answer hangs its caller because the adb client has no read timeout #50); the in-flight guard (goWireless is not tracked in handoffInFlight; double start is possible #19).

Verified by

  • 9 new tests in wireless_test.dart (6 for the timeout and retry, 3 for the visible wait and markUncertain) and 3 in handoff_test.dart (unconfirmed stage and no tcpipEnabled, flagged when the handoff then fails with the USB mirror left running, not flagged after an acknowledged tcpip or a successful connection). The waitingForPhone test checks it appears before any other step while the wait lasts. 6 of these are the original tests: fails within the timeout with the explicit reason and never advances (and sends exactly one tcpip); after a late completion a retry succeeds through the hotspot gateway; a retry waits while the phone is listed offline and sends nothing until it is ready; a retry fails without sending anything when the phone never returns; settle is immediate with nothing outstanding; an outright failure does not leave the serial unsettled.
  • With the timeout removed the new tests hang or fail (per-test limit 4 s); they pass with it.
  • dart analyze clean in core, ipc, daemon. Tests: core 27, ipc 26, daemon 88 (76 on main) pass locally on Windows. main (with Honour ANDROID_ADB_SERVER_PORT and test the daemon failure paths against a fake adb #49) was merged into the branch first. CI results are in the checks below.
  • Not verified: against a real phone, and the daemon wiring in runHandoff (covered only through settle's own tests).

Risk / follow-ups

  • With a dead adb server the connection normally fails (3 s connect limit) before the 5 s timeout, giving "tcpip failed: ..." rather than the timeout message; an adb server that accepts the connection and then stalls gives the timeout message.
  • A hung request keeps its adb socket open until the phone or adb drops it: the AdbClient read-timeout gap, tracked in A stalled adb answer hangs its caller because the adb client has no read timeout #50.
  • The settle wait polls adb devices every 500 ms for up to 15 s; the UI shows the new stage text but has no special treatment for it.

ba6ul added 3 commits October 27, 2026 02:42
goWireless waited for the tcpip reply with no limit, so a phone that stopped
answering hung the flow. It now fails explicitly after 5 s, like handoff's
limit, and leaves the USB mirror alone. Because the switch can still complete
late, the serial is remembered and the next Go Wireless or handoff first waits
for the phone to be back and ready. The fake adb server can stall tcpip.
…iled handoff

settle() now reports waitingForPhone (and logs) while it waits, so the up to
15 s wait is not silent. A handoff whose tcpip went unanswered reports
tcpipUnconfirmed instead of the untrue tcpipEnabled, and if it then ends
without a wireless connection it marks the phone uncertain so the next Go
Wireless or handoff settles it first.
@ba6ul
ba6ul merged commit 07dfaa9 into main Oct 26, 2026
4 checks passed
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