Skip to content

[playwright-browser-tunnel] Fix stopAsync hanging while waiting for a connection - #5935

Open
Pham Manh Luc (MLuc24) wants to merge 1 commit into
microsoft:mainfrom
MLuc24:fix/playwright-tunnel-stop-while-waiting
Open

[playwright-browser-tunnel] Fix stopAsync hanging while waiting for a connection#5935
Pham Manh Luc (MLuc24) wants to merge 1 commit into
microsoft:mainfrom
MLuc24:fix/playwright-tunnel-stop-while-waiting

Conversation

@MLuc24

Copy link
Copy Markdown

Summary

Fixes #5853

PlaywrightTunnel.stopAsync() never completes when the tunnel is stopped in poll-connection mode before any client has connected. The tunnel is left in waiting-for-connection and the caller waits forever.

Details

_pollConnectionAsync() returns a promise that is only resolved from inside the polling interval, when _tryConnectAsync() finally succeeds — its reject is never called. startAsync() stores that promise as _initWsPromise, and stopAsync() clears the interval and then awaits it. Clearing the interval stops the polling but leaves the promise pending, so the await has nothing to wait for.

The fix records a canceller while the poll is in flight and settles it during teardown:

  • _pollConnectionAsync() registers _cancelPollConnection, which clears the poll state and rejects the pending wait; it is dropped as soon as a client connects.
  • stopAsync() invokes it before awaiting _initWsPromise, swallows that expected rejection, clears _initWsPromise, and sets the status to stopped.
  • The startAsync() loop treats such a cancellation as an ordinary shutdown, so stopping the tunnel does not surface as a failure to whoever awaited startAsync().

This is the case JM (@justinTM)'s #5851 explicitly does not cover — that PR fixes the Start/Stop race in the VS Code extension, while this one is in the tunnel itself, so the two do not touch the same files.

How it was tested

Against the published @rushstack/playwright-browser-tunnel 0.3.27, driving a tunnel at an endpoint where nothing is listening, so it stays in waiting-for-connection:

$ node repro.cjs
  status -> waiting-for-connection
Waiting for WebSocket connection
calling stopAsync() while status = waiting-for-connection
stopAsync STILL HANGING after 8005ms          # 8s race guard, it never settles

With this change applied to the package's compiled output, the same script:

  status -> waiting-for-connection
Waiting for WebSocket connection
calling stopAsync() while status = waiting-for-connection
  startAsync resolved cleanly
  status -> stopped
stopAsync RESOLVED after 0ms

The package currently has no unit tests, so I did not add one rather than introduce a test harness in a bug-fix PR — glad to add one if you would like it here. I also could not compile the monorepo locally, so that is worth confirming in CI.

… connection

In poll-connection mode the init promise only settles once a client connects.
stopAsync() cleared the polling interval but still awaited that promise, so
stopping the tunnel before any client arrived never completed.

The pending wait is now settled as part of the teardown, and a stop during the
wait is treated as an ordinary shutdown by the start loop rather than an error.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +90 to +93
/**
* Thrown internally to settle a connection wait that was still pending when the tunnel was stopped.
*/
class TunnelStoppedError extends Error {
Comment on lines +311 to +317
this._cancelPollConnection = (error: Error): void => {
if (this._pollInterval) {
clearInterval(this._pollInterval);
this._pollInterval = undefined;
}
this._pendingConnectionAttempt = undefined;
reject(error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

Playwright tunnel stop can hang while waiting for connection

2 participants