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

Merged
merged 4 commits into from
Aug 12, 2025

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 (via dwds_vm_client) 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

Copy link

auto-submit bot commented Aug 12, 2025

autosubmit label was removed for dart-lang/webdev/2666, because - The status or check suite unit_test; windows; Dart dev; PKG: dwds; `dart test --total-shards 3 --shard-index 0 --exclude-ta... has failed. Please fix the issues identified (or deflake) before re-applying this label.

@jyameo jyameo merged commit b43030e into dart-lang:main Aug 12, 2025
51 of 52 checks passed
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Aug 14, 2025
Revisions updated by `dart tools/rev_sdk_deps.dart`.

ai (https://github.com/dart-lang/ai/compare/6b4b2bc..ee5b2b2):
  ee5b2b2  2025-08-13  Greg Spencer  Stop reporting errors for non-zero exits (dart-lang/ai#262)

protobuf (https://github.com/dart-lang/protobuf/compare/0b73b0d..6e9c9f4):
  6e9c9f4  2025-08-12  Ömer Sinan Ağacan  Improve clone and deepCopy tests (google/protobuf.dart#1039)
  086dfab  2025-08-12  Ömer Sinan Ağacan  Make proto3_json.dart aware of well known types (google/protobuf.dart#1037)
  277b45a  2025-08-12  Ömer Sinan Ağacan  Release protobuf 4.2.0 (google/protobuf.dart#1038)

shelf (https://github.com/dart-lang/shelf/compare/2a46b4f..400fc39):
  400fc39  2025-08-11  Kevin Moore  [shelf_router_generator] latest deps, prepare release (dart-lang/shelf#480)

web (https://github.com/dart-lang/web/compare/72cdd84..4310354):
  4310354  2025-08-12  Nikechukwu  [interop] Add Support for JSDoc Documentation (dart-lang/web#435)

webdev (https://github.com/dart-lang/webdev/compare/94c172c..c0492f1):
  c0492f1b  2025-08-13  Srujan Gaddam  [dwds] Wait for scripts to be parsed on a hot restart and publish DWDS 25.0.0 (dart-lang/webdev#2667)
  595f8768  2025-08-13  Nicholas Shahan  [dwds] Cleanup unused null safety related fields (dart-lang/webdev#2660)
  b43030e2  2025-08-12  Jessy Yameogo  Implemented hot restart over websocket (dart-lang/webdev#2666)

Change-Id: I1cb2a0c2ed693bb740ab4c40ea70678b03eec596
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/445340
Auto-Submit: Devon Carew <devoncarew@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
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