Skip to content

Implemented hot restart over websocket #2666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

jyameo
Copy link
Contributor

@jyameo jyameo commented Aug 11, 2025

  • implemented hot restart over websocket
  • fixed refresh race condition bug

related to #2605

@jyameo jyameo requested review from biggs0125, srujzs and nshahan August 11, 2025 15:43
Copy link
Contributor

@nshahan nshahan left a comment

Choose a reason for hiding this comment

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

Do we already have tests configured in this repo that perform a hot restart using the websockets channel? We should have something to make sure we can get an early signal on breakages here.

Comment on lines 31 to 34
/// Grace period before destroying isolate when no clients are detected.
/// This handles the race condition during page refresh where the old connection
/// closes before the new connection is established, preventing premature isolate destruction.
const _isolateDestructionGracePeriod = Duration(seconds: 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is the client connection coming from and when does it get sent? Is it sent from the injected client.js running in the browser? I'm concerned about the potential for 2 seconds being too short compared to the 15 second timeout I see in _performWebSocketHotRestart().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The client connection is sent from the injected client running in the browser. This actually doesn’t impact hot restart, but rather a page refresh operation. I found that in my previous implementation, when there’s only a single window open and we trigger a page refresh, the server might not recognize it as a refresh and could start destroying the isolate immediately since there are no connections for a short period. This change just tells the server to wait an extra 2 seconds, then check if any new connections appear before proceeding to destroy the isolate. From my local testing, 2 seconds seems to work fine, but I can increase it if you think it would make more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I increased it to 10 seconds instead

@jyameo
Copy link
Contributor Author

jyameo commented Aug 11, 2025

Do we already have tests configured in this repo that perform a hot restart using the websockets channel? We should have something to make sure we can get an early signal on breakages here.

@nshahan We do not have a test here but I can add a test in a flutter repo where I use a headless chrome browser similar to what I did for hot reload. we can't really test the websocket channel here without establishing the connection between the injected client and DWDS.

@jyameo jyameo requested a review from nshahan August 11, 2025 18:29
@nshahan
Copy link
Contributor

nshahan commented Aug 11, 2025

We do not have a test here but I can add a test in a flutter repo where I use a headless chrome browser similar to what I did for hot reload. we can't really test the websocket channel here without establishing the connection between the injected client and DWDS.

@jyameo
With the changes @bkonyi made to the PR in the Flutter repo, do we actually still need the headless chrome session involved in the test?

We have these tests already but it is getting harder to understand what scenarios they cover.

@srujzs
I see you added this test a while back, do you recall what is being tested here?
https://github.com/dart-lang/webdev/blame/94c172cc862d0c39c72158c6537f1e20b4432e0e/dwds/test/common/hot_restart_common.dart#L141-L172

I'm getting lost in the details of

  • hot_restart_common.dart
  • "and without debugging using WebSockets"
  • "live reload"

@srujzs
Copy link
Contributor

srujzs commented Aug 11, 2025

My rough thoughts:

  • I added that to unify the hot restart tests across the build daemon and frontend server configurations.
  • Looking at the diff between that test and the test before it, it looks like useSse determines whether to use SSE (true) or WebSockets (false) to connect to DDS.
  • The live reload I believe refers to the build daemon sending build results to the injected client which then triggers a hard reload.

Also note that that whole group is skipped when using the frontend server as those tests only make sense for the build daemon. It may be less confusing to pull those out into a separate test that's only run with the build daemon.

Copy link
Contributor

@srujzs srujzs left a comment

Choose a reason for hiding this comment

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

I generally agree that it's better to have tests in this repo as well, but I'm not familiar with how difficult that may be.

/// Default implementation throws UnimplementedError.
/// Override in subclasses that support hot restart completion.
void completeHotRestart(HotRestartResponse response) {
throw UnimplementedError('completeHotRestart not supported');
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not make this method abstract instead?

Copy link
Contributor Author

@jyameo jyameo Aug 12, 2025

Choose a reason for hiding this comment

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

Because only WebSocketProxyService implements hot restart completion. The chrome path handles hot restart differently therefore the method is an optional, protocol-specific behavior, not core functionality for all proxy services.

/// Grace period before destroying isolate when no clients are detected.
/// This handles the race condition during page refresh where the old connection
/// closes before the new connection is established, preventing premature isolate destruction.
const _isolateDestructionGracePeriod = Duration(seconds: 10);
Copy link

@Salakar Salakar Aug 11, 2025

Choose a reason for hiding this comment

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

Not sure I understand this change correctly but wanted to check;

Is the duration here dependant on how long the client takes to load all JS bundles again? In some of our products we have a remote server of flutter run -d web-server.

Loading from a remote server serveral hundred Flutter debug JS bundles unfortunately takes a long time - consistently more than 10s on average, sometimes minutes on a poor connection - so wanted to check if this change affects this in anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is NOT related to how long it takes to load JS bundles. Instead, it handles a specific race condition during page refresh so it should NOT negatively affect your remote server setup

@jyameo
Copy link
Contributor Author

jyameo commented Aug 12, 2025

We do not have a test here but I can add a test in a flutter repo where I use a headless chrome browser similar to what I did for hot reload. we can't really test the websocket channel here without establishing the connection between the injected client and DWDS.

@jyameo With the changes @bkonyi made to the PR in the Flutter repo, do we actually still need the headless chrome session involved in the test?

We have these tests already but it is getting harder to understand what scenarios they cover.

@srujzs I see you added this test a while back, do you recall what is being tested here? https://github.com/dart-lang/webdev/blame/94c172cc862d0c39c72158c6537f1e20b4432e0e/dwds/test/common/hot_restart_common.dart#L141-L172

I'm getting lost in the details of

  • hot_restart_common.dart
  • "and without debugging using WebSockets"
  • "live reload"

@nshahan Will land this PR for now and look into adding a test in a follow up PR

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

Successfully merging this pull request may close these issues.

4 participants