From be6d2e3a0074d560e95b97a561eca3ce6a6d1802 Mon Sep 17 00:00:00 2001 From: Nicholas Shahan Date: Mon, 28 Apr 2025 23:25:56 -0700 Subject: [PATCH] [ddc] Cancel futures after a hot restart Outstanding async code now checks and cancels itself if it was created in a previous version of the application from before a hot restart operation. This includes outstanding `Future`s created by calling the `dart:js_util` helper `promiseToFuture`. Issue: https://github.com/flutter/flutter/issues/166004 Change-Id: I342bbd2f8eda6b58d2f0fdaf3c00f55f03561b1a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423961 Reviewed-by: Nate Biggs Reviewed-by: Srujan Gaddam Commit-Queue: Nicholas Shahan --- CHANGELOG.md | 11 +++++ .../lib/js/ddc/ddc_module_loader.js | 7 +-- pkg/dev_compiler/lib/src/kernel/compiler.dart | 5 ++ .../lib/src/kernel/compiler_new.dart | 6 ++- .../js_dev_runtime/patch/async_patch.dart | 7 +-- .../js_dev_runtime/patch/internal_patch.dart | 12 +++++ .../private/ddc_runtime/operations.dart | 4 +- .../private/ddc_runtime/runtime.dart | 3 ++ .../private/isolate_helper.dart | 8 ++-- .../js_runtime/lib/internal_patch.dart | 12 +++++ .../js_shared/lib/js_util_patch.dart | 19 ++++++-- .../config.json | 3 ++ .../main.0.dart | 19 ++++++++ .../main.1.restart.dart | 32 +++++++++++++ .../util.0.dart | 31 ++++++++++++ .../config.json | 3 ++ .../main.0.dart | 19 ++++++++ .../main.1.restart.dart | 32 +++++++++++++ .../util.0.dart | 31 ++++++++++++ .../hot_restart_timer_periodic/config.json | 3 ++ .../hot_restart_timer_periodic/main.0.dart | 26 ++++++++++ .../main.1.restart.dart | 48 +++++++++++++++++++ .../hot_restart_timer_timeout/config.json | 3 ++ .../hot_restart_timer_timeout/main.0.dart | 18 +++++++ .../main.1.restart.dart | 38 +++++++++++++++ 25 files changed, 381 insertions(+), 19 deletions(-) create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_reject/config.json create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_reject/main.0.dart create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_reject/main.1.restart.dart create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_reject/util.0.dart create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_resolve/config.json create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_resolve/main.0.dart create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_resolve/main.1.restart.dart create mode 100644 tests/hot_reload/hot_restart_js_interop_promise_resolve/util.0.dart create mode 100644 tests/hot_reload/hot_restart_timer_periodic/config.json create mode 100644 tests/hot_reload/hot_restart_timer_periodic/main.0.dart create mode 100644 tests/hot_reload/hot_restart_timer_periodic/main.1.restart.dart create mode 100644 tests/hot_reload/hot_restart_timer_timeout/config.json create mode 100644 tests/hot_reload/hot_restart_timer_timeout/main.0.dart create mode 100644 tests/hot_reload/hot_restart_timer_timeout/main.1.restart.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e2c87884f..d097a750239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ ## 3.9.0 +### Tools + +#### Dart Development Compiler (dartdevc) + +Outstanding async code now checks and cancels itself after a hot restart if +it was started in a different generation of the application before the restart. +This includes outstanding `Future`s created by calling `JSPromise.toDart` from +`dart:js_interop` and the underlying the `dart:js_util` helper +`promiseToFuture`. Dart callbacks will not be run, but callbacks on the +JavaScript side will still be executed. + ## 3.8.0 **Released on:** Unreleased diff --git a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js index c1cd432aed7..8b893241cf0 100644 --- a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js +++ b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js @@ -1641,7 +1641,7 @@ if (!self.deferred_loader) { // Then we link the existing libraries. Note this may trigger initializing // and linking new library dependencies that were not present before and - // requires for all library intitializers to be up to date. + // requires for all library initializers to be up to date. for (let name in this.pendingHotReloadLibraryInitializers) { if (previouslyLoaded[name]) { this.libraries[name].link(); @@ -1662,11 +1662,6 @@ if (!self.deferred_loader) { if (!this.savedEntryPointLibraryName) { throw "Error: Hot restart requested before application started."; } - // TODO(nshahan): Stop calling hotRestart in the SDK when scheduled - // futures no longer keep lazy initialized values from the previous - // generation alive. - let dart = this.importLibrary('dart:_runtime'); - dart.hotRestart(); // Clear all libraries. this.libraries = Object.create(null); this.triggeredSDKLibrariesWithSideEffects = false; diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart index 6f3dd4762b4..6079ee6d506 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart @@ -6205,6 +6205,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor if (_isSdkInternalRuntime(enclosingLibrary)) { var name = target.name.text; + if (node.arguments.positional.isEmpty) { + if (name == 'hotRestartGeneration') { + return _runtimeCall('hotRestartIteration'); + } + } if (node.arguments.positional.length == 1) { var firstArg = node.arguments.positional.single; if (name == 'extensionSymbol' && firstArg is StringLiteral) { diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index 5740571ad79..c775ab15831 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -6655,7 +6655,11 @@ class LibraryCompiler extends ComputeOnceConstantVisitor if (_isSdkInternalRuntime(enclosingLibrary)) { var name = target.name.text; - if (node.arguments.positional.length == 1) { + if (node.arguments.positional.isEmpty) { + if (name == 'hotRestartGeneration') { + return js.call('dartDevEmbedder.hotRestartGeneration'); + } + } else if (node.arguments.positional.length == 1) { var firstArg = node.arguments.positional.single; if (name == 'extensionSymbol' && firstArg is StringLiteral) { return _getSymbol(_getExtensionSymbolInternal(firstArg.value)); diff --git a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart index 5d8c30a4c7f..657bb3cdd2d 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart @@ -175,15 +175,17 @@ class _AsyncRun { @ReifyFunctionTypes(false) static void _scheduleImmediateJSOverride(void Function() callback) { + final createdGeneration = dart.hotRestartGeneration(); JS('void', '#.scheduleImmediate(#)', dart.global_, () { - callback(); + if (createdGeneration == dart.hotRestartGeneration()) callback(); }); } @ReifyFunctionTypes(false) static void _scheduleImmediateWithPromise(void Function() callback) { + final createdGeneration = dart.hotRestartGeneration(); JS('', '#.Promise.resolve(null).then(#)', dart.global_, () { - callback(); + if (createdGeneration == dart.hotRestartGeneration()) callback(); }); } } @@ -487,7 +489,6 @@ class _AsyncStarImpl { class _AsyncAwaitCompleter implements Completer { final _future = _Future(); bool isSync; - int hotRestartIteration = dart.hotRestartIteration; _AsyncAwaitCompleter() : isSync = false; diff --git a/sdk/lib/_internal/js_dev_runtime/patch/internal_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/internal_patch.dart index 0f16b88bba3..9a1fb48a72f 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/internal_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/internal_patch.dart @@ -15,6 +15,18 @@ import 'dart:typed_data' show Uint8List; @patch bool typeAcceptsNull() => null is T; +int? getHotRestartGeneration() => dart.hotRestartGeneration(); + +/// Returns `true` when the provided [generation] matches the current hot +/// restart generation. +/// +/// This is intended to avoid completing a Dart Future after a hot restart that +/// originated from a converted Promise before the hot restart. +/// +/// See uses in `promiseToFuture` from `dart:js_util`. +bool isCurrentHotRestartGeneration(int generation) => + generation == dart.hotRestartGeneration(); + @patch class Symbol implements core.Symbol { @patch diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart index e583b3e18e2..d1d0793074b 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart @@ -1242,7 +1242,7 @@ Future loadLibrary( JS('', '#.add(#)', result, importPrefix); return _ddcNewLoadLibraryTiming ? Future(() {}) : Future.value(); } else { - int currentHotRestartIteration = hotRestartIteration; + int hotRestartGenerationBefore = hotRestartGeneration(); var loadId = '$libraryUri::$importPrefix'; if (targetModule.isEmpty) { throw ArgumentError('Empty module passed for deferred load: $loadId.'); @@ -1254,7 +1254,7 @@ Future loadLibrary( // Don't mark a load ID as loaded across hot restart boundaries. void internalComplete(void Function()? beforeComplete) { - if (hotRestartIteration == currentHotRestartIteration && + if (hotRestartGeneration() == hotRestartGenerationBefore && beforeComplete != null) { beforeComplete(); } diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart index 0a906a3943b..73b17c4345c 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart @@ -245,6 +245,9 @@ final List resetFields = JS('', '[]'); @notNull final JSArray moduleConstCaches = JS('!', 'new Map()'); +/// Returns the current hot restart generation number. +external int hotRestartGeneration(); + /// A counter to track each time [hotRestart] is invoked. This is used to ensure /// that pending callbacks that were created on a previous iteration (e.g. a /// timer callback or a DOM callback) will not execute when they get invoked. diff --git a/sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart index 51959937580..813bfbca596 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart @@ -39,11 +39,11 @@ class TimerImpl implements Timer { TimerImpl(int milliseconds, void callback()) : _once = true { if (hasTimer()) { - int currentHotRestartIteration = dart.hotRestartIteration; + int hotRestartGenerationBefore = dart.hotRestartGeneration(); void internalCallback() { _handle = null; _tick = 1; - if (currentHotRestartIteration == dart.hotRestartIteration) { + if (hotRestartGenerationBefore == dart.hotRestartGeneration()) { callback(); } } @@ -64,9 +64,9 @@ class TimerImpl implements Timer { : _once = false { if (hasTimer()) { int start = JS('!', 'Date.now()'); - int currentHotRestartIteration = dart.hotRestartIteration; + int hotRestartGenerationBefore = dart.hotRestartGeneration(); _handle = JS('!', '#.setInterval(#, #)', global, () { - if (currentHotRestartIteration != dart.hotRestartIteration) { + if (hotRestartGenerationBefore != dart.hotRestartGeneration()) { cancel(); return; } diff --git a/sdk/lib/_internal/js_runtime/lib/internal_patch.dart b/sdk/lib/_internal/js_runtime/lib/internal_patch.dart index 5eb42ac4be0..96bf78cb247 100644 --- a/sdk/lib/_internal/js_runtime/lib/internal_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/internal_patch.dart @@ -15,6 +15,18 @@ import 'dart:typed_data' show Uint8List; @pragma('dart2js:tryInline') bool typeAcceptsNull() => null is T; +/// No-op in dart2js. +/// +/// Only used in DDC for hot restart correctness. +@pragma('dart2js:tryInline') +int? getHotRestartGeneration() => null; + +/// No-op in dart2js. +/// +/// Only used in DDC for hot restart correctness. +@pragma('dart2js:tryInline') +bool isCurrentHotRestartGeneration(int _) => true; + @patch class Symbol implements core.Symbol { @patch diff --git a/sdk/lib/_internal/js_shared/lib/js_util_patch.dart b/sdk/lib/_internal/js_shared/lib/js_util_patch.dart index 7e09dfc17ee..81754a9fa2f 100644 --- a/sdk/lib/_internal/js_shared/lib/js_util_patch.dart +++ b/sdk/lib/_internal/js_shared/lib/js_util_patch.dart @@ -3,7 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:_foreign_helper' show JS; -import 'dart:_internal' show patch; +import 'dart:_internal' + show getHotRestartGeneration, isCurrentHotRestartGeneration, patch; import 'dart:_js_helper' show assertInterop, @@ -561,9 +562,21 @@ num unsignedRightShift(Object? leftOperand, Object? rightOperand) { @patch Future promiseToFuture(Object jsPromise) { final completer = Completer(); - - final success = convertDartClosureToJS((r) => completer.complete(r), 1); + final restartGenerationBefore = getHotRestartGeneration(); + final success = convertDartClosureToJS((r) { + if (restartGenerationBefore != null) { + // These nested if statements are intended for simple optimization by + // dart2js. + if (!isCurrentHotRestartGeneration(restartGenerationBefore)) return; + } + return completer.complete(r); + }, 1); final error = convertDartClosureToJS((e) { + if (restartGenerationBefore != null) { + // These nested if statements are intended for simple optimization by + // dart2js. + if (!isCurrentHotRestartGeneration(restartGenerationBefore)) return; + } // Note that `completeError` expects a non-nullable error regardless of // whether null-safety is enabled, so a `NullRejectionException` is always // provided if the error is `null` or `undefined`. diff --git a/tests/hot_reload/hot_restart_js_interop_promise_reject/config.json b/tests/hot_reload/hot_restart_js_interop_promise_reject/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_reject/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/hot_restart_js_interop_promise_reject/main.0.dart b/tests/hot_reload/hot_restart_js_interop_promise_reject/main.0.dart new file mode 100644 index 00000000000..a2ec5e1d91b --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_reject/main.0.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +import 'util.dart'; + +Future main() async { + injectJS(); + createPromise().toDart.catchError((_) => throw 'Should never run.'); + await Future.delayed(Duration(milliseconds: 100)); + Expect.isFalse(rejectCalled); + await hotRestart(); +} diff --git a/tests/hot_reload/hot_restart_js_interop_promise_reject/main.1.restart.dart b/tests/hot_reload/hot_restart_js_interop_promise_reject/main.1.restart.dart new file mode 100644 index 00000000000..f777811da6c --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_reject/main.1.restart.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +import 'util.dart'; + +Future main() async { + rejectPromise(); + await Future.delayed(Duration(milliseconds: 100)); + Expect.isTrue(rejectCalled); +} + +/** DIFF **/ +/* + import 'util.dart'; + + Future main() async { +- injectJS(); +- createPromise().toDart.catchError((_) => throw 'Should never run.'); ++ rejectPromise(); + await Future.delayed(Duration(milliseconds: 100)); +- Expect.isFalse(rejectCalled); +- await hotRestart(); ++ Expect.isTrue(rejectCalled); + } +*/ diff --git a/tests/hot_reload/hot_restart_js_interop_promise_reject/util.0.dart b/tests/hot_reload/hot_restart_js_interop_promise_reject/util.0.dart new file mode 100644 index 00000000000..0315f8a9fdd --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_reject/util.0.dart @@ -0,0 +1,31 @@ +import 'dart:js_interop'; + +@JS() +external JSAny? eval(String script); + +@JS() +external JSPromise createPromise(); + +@JS() +external void rejectPromise(); + +@JS() +external bool rejectCalled; + +void injectJS() { + eval(''' + self.rejectCalled = false; + self.rejectFunction = null; + self.createPromise = function(s) { + let { promise, resolve, reject } = Promise.withResolvers(); + self.rejectFunction = function() { + self.rejectCalled = true; + reject(); + }; + return promise; + }; + self.rejectPromise = function() { + self.rejectFunction(); + }; + '''); +} diff --git a/tests/hot_reload/hot_restart_js_interop_promise_resolve/config.json b/tests/hot_reload/hot_restart_js_interop_promise_resolve/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_resolve/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.0.dart b/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.0.dart new file mode 100644 index 00000000000..9026cd59a04 --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.0.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +import 'util.dart'; + +Future main() async { + injectJS(); + createPromise().toDart.then((_) => throw 'Should never run.'); + await Future.delayed(Duration(milliseconds: 100)); + Expect.isFalse(resolveCalled); + await hotRestart(); +} diff --git a/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.1.restart.dart b/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.1.restart.dart new file mode 100644 index 00000000000..4aa6dc763ef --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_resolve/main.1.restart.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +import 'util.dart'; + +Future main() async { + resolvePromise(); + await Future.delayed(Duration(milliseconds: 100)); + Expect.isTrue(resolveCalled); +} + +/** DIFF **/ +/* + import 'util.dart'; + + Future main() async { +- injectJS(); +- createPromise().toDart.then((_) => throw 'Should never run.'); ++ resolvePromise(); + await Future.delayed(Duration(milliseconds: 100)); +- Expect.isFalse(resolveCalled); +- await hotRestart(); ++ Expect.isTrue(resolveCalled); + } +*/ diff --git a/tests/hot_reload/hot_restart_js_interop_promise_resolve/util.0.dart b/tests/hot_reload/hot_restart_js_interop_promise_resolve/util.0.dart new file mode 100644 index 00000000000..8b7aede8fb2 --- /dev/null +++ b/tests/hot_reload/hot_restart_js_interop_promise_resolve/util.0.dart @@ -0,0 +1,31 @@ +import 'dart:js_interop'; + +@JS() +external JSAny? eval(String script); + +@JS() +external JSPromise createPromise(); + +@JS() +external void resolvePromise(); + +@JS() +external bool resolveCalled; + +void injectJS() { + eval(''' + self.resolveCalled = false; + self.resolveFunction = null; + self.createPromise = function(s) { + let { promise, resolve, reject } = Promise.withResolvers(); + self.resolveFunction = function() { + self.resolveCalled = true; + resolve(); + }; + return promise; + }; + self.resolvePromise = function() { + self.resolveFunction(); + }; + '''); +} diff --git a/tests/hot_reload/hot_restart_timer_periodic/config.json b/tests/hot_reload/hot_restart_timer_periodic/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_periodic/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/hot_restart_timer_periodic/main.0.dart b/tests/hot_reload/hot_restart_timer_periodic/main.0.dart new file mode 100644 index 00000000000..194218a3b3a --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_periodic/main.0.dart @@ -0,0 +1,26 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +bool beforeRestart = true; +bool calledBeforeRestart = false; +bool calledAfterRestart = false; +void callback(_) { + if (beforeRestart) { + calledBeforeRestart = true; + } else { + calledAfterRestart = true; + } +} + +void main() async { + Timer.periodic(Duration(milliseconds: 10), callback); + await new Future.delayed(Duration(milliseconds: 50)); + Expect.isTrue(beforeRestart); + Expect.isTrue(calledBeforeRestart); + await hotRestart(); +} diff --git a/tests/hot_reload/hot_restart_timer_periodic/main.1.restart.dart b/tests/hot_reload/hot_restart_timer_periodic/main.1.restart.dart new file mode 100644 index 00000000000..2cb0b971d11 --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_periodic/main.1.restart.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +bool beforeRestart = false; +bool calledBeforeRestart = false; +bool calledAfterRestart = false; +void callback(_) { + if (beforeRestart) { + calledBeforeRestart = true; + } else { + calledAfterRestart = true; + } +} + +void main() async { + await new Future.delayed(Duration(milliseconds: 50)); + Expect.isFalse(beforeRestart); + Expect.isFalse(calledAfterRestart); +} + +/** DIFF **/ +/* + import 'package:expect/expect.dart'; + import 'package:reload_test/reload_test_utils.dart'; + +-bool beforeRestart = true; ++bool beforeRestart = false; + bool calledBeforeRestart = false; + bool calledAfterRestart = false; + void callback(_) { +@@ -18,9 +18,7 @@ void callback(_) { + } + + void main() async { +- Timer.periodic(Duration(milliseconds: 10), callback); + await new Future.delayed(Duration(milliseconds: 50)); +- Expect.isTrue(beforeRestart); +- Expect.isTrue(calledBeforeRestart); +- await hotRestart(); ++ Expect.isFalse(beforeRestart); ++ Expect.isFalse(calledAfterRestart); + } +*/ diff --git a/tests/hot_reload/hot_restart_timer_timeout/config.json b/tests/hot_reload/hot_restart_timer_timeout/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_timeout/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/hot_restart_timer_timeout/main.0.dart b/tests/hot_reload/hot_restart_timer_timeout/main.0.dart new file mode 100644 index 00000000000..d9c79d455f0 --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_timeout/main.0.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +bool restarted() => false; + +void callback() { + throw Exception('Should never run.'); +} + +Future main() async { + Timer(Duration(milliseconds: 200), callback); + await hotRestart(); +} diff --git a/tests/hot_reload/hot_restart_timer_timeout/main.1.restart.dart b/tests/hot_reload/hot_restart_timer_timeout/main.1.restart.dart new file mode 100644 index 00000000000..368034e5b54 --- /dev/null +++ b/tests/hot_reload/hot_restart_timer_timeout/main.1.restart.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +bool restarted() => true; + +void callback() { + throw Exception('Should never run.'); +} + +Future main() async { + await new Future.delayed(Duration(milliseconds: 300)); + Expect.isTrue(restarted()); +} + +/** DIFF **/ +/* + import 'package:expect/expect.dart'; + import 'package:reload_test/reload_test_utils.dart'; + +-bool restarted() => false; ++bool restarted() => true; + + void callback() { + throw Exception('Should never run.'); + } + + Future main() async { +- Timer(Duration(milliseconds: 200), callback); +- await hotRestart(); ++ await new Future.delayed(Duration(milliseconds: 300)); ++ Expect.isTrue(restarted()); + } +*/