Skip to content

flutter test --platform chrome hangs forever when Chromium cannot bind its remote debugging port #192013

Closed

Description

@SaiWebApps

Steps to reproduce

ChromiumLauncher._spawnChromiumProcess waits for Chromium to print DevTools listening on stderr with no timeout, and its orElse fires only when stderr closes. A Chromium that starts, fails to bind its remote debugging port, and keeps running satisfies neither condition, so the await never returns and flutter test --platform chrome hangs forever with no verdict.

The port is chosen by OperatingSystemUtils.findFreePort, whose own doc comment states the race:

Returns an unused network port. […] The port returned by this function may become used before it is bound by its intended user.

To reproduce deterministically, put the launcher into the state that race produces — the chosen port already taken:

  1. Bind the port on both loopback stacks (this matters, see below):
import socket, time
port = 9555
s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s4.bind(("127.0.0.1", port)); s4.listen(5)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
s6.bind(("::1", port)); s6.listen(5)
time.sleep(600)
  1. Point CHROME_EXECUTABLE at a shim that rewrites the tool's --remote-debugging-port=<n> to that port and execs the real Chrome.

  2. flutter test --platform chrome --verbose in any project with at least one test.

Expected results

The run fails with a message naming the problem, or the existing 3-try retry in _spawnChromiumProcess engages.

Actual results

The run hangs indefinitely with no verdict. --verbose stops here and never advances (I left it over two minutes; a separate wild instance of the same class of hang sat unchanged for 14 minutes):

00:00 +0: loading <app>/test/models/some_test.dart
[+7814 ms] Loading test suite test/models/some_test.dart.
[        ] Serving tests at http://localhost:64122/static/index.html?managerUrl=...
[        ] Launching Chromium (url = ..., headless = true, skipCheck = false, debugPort = null)
[ +251 ms] [CHROME]: [ERROR:net/socket/socket_posix.cc:153] bind() failed: Address already in use (48)
[        ] [CHROME]: [ERROR:net/socket/socket_posix.cc:153] bind() failed: Address already in use (48)
[        ] [CHROME]: [ERROR:content/browser/devtools/devtools_http_handler.cc:313] Cannot start http server for devtools.

The dart process stays alive at 0% CPU. Chromium stays alive with stderr open. Nothing times out.

Why the existing retry does not save it

orElse runs only when the stderr stream closes. Chromium here does not exit, so the stream never closes, so the retry >= kMaxRetries / throwToolExit path in orElse is unreachable.

Verified Chrome behaviour (Chrome for Testing 145.0.7632.6, macOS arm64)

  • Debug port occupied on IPv4 only → Chrome falls back to IPv6, prints DevTools listening on ws://[::1]:<port>/..., and the tool proceeds. No hang. (It may then be talking to the wrong server, but that is a separate matter.)
  • Debug port occupied on both stacks → Chrome prints Cannot start http server for devtools., stays alive with stderr open, and never prints the marker. This is the hang.

Suggested fix

Bounding the wait feeds the retry logic that is already written. In packages/flutter_tools/lib/src/web/chrome.dart, _spawnChromiumProcess:

final Future<String> devToolsLine = process.stderr
    .transform(utf8LineDecoder)
    .map(...)
    .firstWhere((String line) => line.startsWith('DevTools listening'), orElse: ...);

var timedOut = false;
try {
  await devToolsLine.timeout(const Duration(seconds: 30));
} on TimeoutException {
  timedOut = true;
  shouldRetry = true;
}

if (!hitGlibcBug && !shouldRetry) {
  return process;
}
if (timedOut && retry >= kMaxRetries) {
  errors.forEach(_logger.printError);
  throwToolExit(
    'Chromium started but never reported "DevTools listening" within 30 seconds, '
    '$kMaxRetries times over. Its debugging port is most likely already in use.',
  );
}
retry += 1;

I applied exactly this to a local 3.41.9 checkout, rebuilt flutter_tools.snapshot, and re-ran the reproduction: the infinite hang became Failed to launch browser after 3 tries. in 41 seconds. I have since reverted the patch; my checkout is clean.

One caveat on that patch: launch() picks the port once, before the retry loop, so all three retries reuse the same occupied port. Bounding the wait is still a strict improvement (a loud, bounded failure instead of an infinite hang), but re-picking the port per retry would let it actually recover.

flutter doctor -v

[✓] Flutter (Channel stable, 3.41.9, on macOS 26.5.2 25F84 darwin-arm64, locale en-US)
    • Flutter version 3.41.9 on channel stable at /opt/homebrew/share/flutter
    • Framework revision 00b0c91f06 (2026-04-29), Engine revision 42d3d75a56
    • Dart version 3.11.5, DevTools version 2.54.2
[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
[✗] Chrome - develop for the web (no /Applications Chrome; CHROME_EXECUTABLE points at
    Chrome for Testing 145.0.7632.6)
[✓] Network resources

Activity

  1. SaiWebApps commented on Aug 29, 2026

    @SaiWebApps
    Author

    Possibly related, filed at the same time: #192014 — a different, intermittent way for the same command to hang with no verdict, in the engine (a CanvasKit init promise that never resolves) rather than the launcher. Distinct root cause; both are unbounded waits on the path to loading a web test suite.

  2. added
    platform-webWeb applications specifically
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    team-webOwned by Web platform team
    on Aug 29, 2026
  3. mbcorona commented on Aug 29, 2026

    @mbcorona
    Member

    Hi @SaiWebApps, thanks for the detailed report. Routing to the web team.

    Confirmed: _spawnChromiumProcess awaits devToolsListening.future with no timeout, and it only completes when the marker prints or the process exits.

  4. mbcorona commented on Sep 1, 2026

    @mbcorona
    Member

    Note --platform chrome is deprecated (use -d chrome or integration_test), but this same launcher backs flutter run -d chrome, so the missing timeout is worth fixing regardless.

  5. SaiWebApps commented on Sep 3, 2026

    @SaiWebApps
    Author

    Draft reply — #192013 (ChromiumLauncher waits without timeout for "DevTools listening")

    You asked us to update Flutter and our dependencies and re-test. Done, and
    this one still reproduces.

    Setup: Flutter 3.47.2 stable (d3b14c8), full flutter pub upgrade, macOS,
    Chrome for Testing via Playwright. Method: 80 consecutive runs of our full
    flutter test --platform chrome suite (46 files / 327 tests) with our stall
    detector recording instead of retrying (a stall = 48 s of no output while the
    process tree burns ~0 CPU).

    Result: 2 of 80 runs stalled, both with this issue's exact signature —
    zero tests completed, the reporter frozen at its first loading line, and
    lsof showing the launched Chromium itself LISTENing on the debugging port
    flutter_tools assigned it:

    Chromium was told to use debugging port 61530. Holder(s) of that port:
    COMMAND    PID   USER   FD   TYPE   NODE NAME
    Google    8478   …      55u  IPv4   TCP 127.0.0.1:61530 (LISTEN)
    

    …while ChromiumLauncher._spawnChromiumProcess sat in its no-timeout
    firstWhere((l) => l.startsWith('DevTools listening')) on stderr. The rate
    (2/80) is statistically indistinguishable from what we measured on 3.41.9
    (1/27).

    Happy to attach the two full diagnosis logs (process tree, port holders, last
    reporter line) if useful. A bounded wait — or treating "Cannot start http
    server for devtools" on stderr as terminal — would turn this from a
    never-exits hang into an ordinary retryable failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

assigned for triageissue is assigned to a domain expert for further triagehas reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-webWeb applications specificallyteam-webOwned by Web platform team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions