Skip to content

Hot Restart does not complete if exceptions are hit during the restart #3448

Closed
flutter/flutter
#93411

Description

@tjx666

Describe the bug

First, check the All Exceptions and Uncaught Exceptions setting at debug panel.

Then debug the following example code. When debugger stoped at exception in function a, press continue button, the debugger will stoped at catch block in function b. Finally, click restart button, debugger will run into endless waiting.

import 'package:flutter/material.dart';

void a() {
  throw Exception('a');
}

void b() {
  try {
    a();
  } catch (e) {
    throw Exception('b');
  }
}

void c() {
  try {
    b();
  } catch (e) {
    throw Exception('c');
  }
}

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    c();
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Study Flutter',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: Container(),
    );
  }
}

image

Expected behavior

After I click the restart button, debugger should stopped at exception in function a

Versions (please complete the following information):

  • VS Code version: 1.58.0-insider
  • Dart extension version: 2.2.0
  • Dart/Flutter SDK version: 3.23.1

Activity

  1. DanTup commented on Jul 1, 2021

    @DanTup
    Member

    @bkonyi @jonahwilliams I can repro this, but I'm not sure whether it's a Flutter or VM issue. Here's an annotated log of what happens:

    Thu Jul 01 2021 [12:21:44 GMT+0100 (British Summer Time)] Log file started
    
    // Send a Hot Restart
    [12:21:50] [FlutterRun] [Info] [Dart ] ==> [{"id":"6","method":"app.restart","params":{"appId":"3b4b3b51-22aa-427d-b47c-108e801a623c","fullRestart":true,"pause":true,"reason":"manual"}}]
    [12:21:50] [FlutterRun] [Info] [Dart ] <== [{"event":"app.progress","params":{"appId":"3b4b3b51-22aa-427d-b47c-108e801a623c","id":"2","progressId":"hot.restart","message":"Performing hot restart...","finished":false}}]
    [12:21:50] [FlutterRun] [Info] [Dart ] <== [{"event":"app.progress","params":{"appId":"3b4b3b51-22aa-427d-b47c-108e801a623c","id":"3","progressId":null,"message":"Syncing files to device iPhone 12 Pro Max...","finished":false}}]
    [12:21:50] [FlutterRun] [Info] [Dart ] <== [{"event":"app.progress","params":{"appId":"3b4b3b51-22aa-427d-b47c-108e801a623c","id":"3","progressId":null,"finished":true}}]
    
    // Something resumes the isolate that was paused at the excepton (Flutter tool?)
    [12:21:50] [VmService] [Info] [Dart ] <== {"jsonrpc":"2.0","method":"streamNotify","params":{"streamId":"Debug","event":{"type":"Event","kind":"Resume","isolate":{"type":"@Isolate","id":"isolates/1903522107348907","name":"main","number":"1903522107348907","isSystemIsolate":false},"timestamp":1625138510269,"topFrame":{"type":"Frame","kind":"Regular","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@551033593/scripts/package%3Aflutter_application_1%2Ffoo.dart/17a61cd7c22","uri":"package:flutter_application_1/foo.dart","_kind":"kernel"},"tokenPos":127},"function":{"type":"@Function","fixedId":true,"id":"libraries/@551033593/functions/b","name":"b","owner":{"type":"@Library","fixedId":true,"id":"libraries/@551033593","name":"","uri":"package:flutter_application_1/foo.dart"},"_kind":"RegularFunction","static":true,"const":false,"_intrinsic":false,"_native":false,"location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@551033593/scripts/package%3Aflutter_application_1%2Ffoo.dart/17a61cd7c22","uri":"package:flutter_application_1/foo.dart","_kind":"kernel"},"tokenPos":79,"endTokenPos":153}},"code":{"type":"@Code","fixedId":true,"id":"code/3220792bd-1309b0e40","name":"[Unoptimized] b","kind":"Dart","_optimized":false,"_intrinsic":false,"_native":false},"vars":[{"type":"BoundVariable","name":"e","value":{"type":"@Instance","class":{"type":"@Class","fixedId":true,"id":"classes/237","name":"_Exception","_vmName":"_Exception@0150898","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@0150898/scripts/dart%3Acore%2Fexceptions.dart/0","uri":"dart:core/exceptions.dart","_kind":"kernel"},"tokenPos":1005,"endTokenPos":1243},"library":{"type":"@Library","fixedId":true,"id":"libraries/@0150898","name":"dart.core","uri":"dart:core"}},"identityHashCode":2125179152,"kind":"PlainInstance","id":"objects/99"},"declarationTokenPos":118,"scopeStartTokenPos":119,"scopeEndTokenPos":151}],"index":0}}}}
    
    // Another exception is hit in the code that ran after the resume
    [12:21:50] [VmService] [Info] [Dart ] <== {"jsonrpc":"2.0","method":"streamNotify","params":{"streamId":"Debug","event":{"type":"Event","kind":"PauseException","isolate":{"type":"@Isolate","id":"isolates/1903522107348907","name":"main","number":"1903522107348907","isSystemIsolate":false},"timestamp":1625138510269,"topFrame":{"type":"Frame","kind":"Regular","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@551033593/scripts/package%3Aflutter_application_1%2Ffoo.dart/17a61cd7c22","uri":"package:flutter_application_1/foo.dart","_kind":"kernel"},"tokenPos":204},"function":{"type":"@Function","fixedId":true,"id":"libraries/@551033593/functions/c","name":"c","owner":{"type":"@Library","fixedId":true,"id":"libraries/@551033593","name":"","uri":"package:flutter_application_1/foo.dart"},"_kind":"RegularFunction","static":true,"const":false,"_intrinsic":false,"_native":false,"location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@551033593/scripts/package%3Aflutter_application_1%2Ffoo.dart/17a61cd7c22","uri":"package:flutter_application_1/foo.dart","_kind":"kernel"},"tokenPos":156,"endTokenPos":230}},"code":{"type":"@Code","fixedId":true,"id":"code/322079285-1309b0d30","name":"[Unoptimized] c","kind":"Dart","_optimized":false,"_intrinsic":false,"_native":false},"vars":[{"type":"BoundVariable","name":"e","value":{"type":"@Instance","class":{"type":"@Class","fixedId":true,"id":"classes/237","name":"_Exception","_vmName":"_Exception@0150898","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@0150898/scripts/dart%3Acore%2Fexceptions.dart/0","uri":"dart:core/exceptions.dart","_kind":"kernel"},"tokenPos":1005,"endTokenPos":1243},"library":{"type":"@Library","fixedId":true,"id":"libraries/@0150898","name":"dart.core","uri":"dart:core"}},"identityHashCode":739325732,"kind":"PlainInstance","id":"objects/100"},"declarationTokenPos":195,"scopeStartTokenPos":196,"scopeEndTokenPos":228}],"index":0},"exception":{"type":"@Instance","class":{"type":"@Class","fixedId":true,"id":"classes/237","name":"_Exception","_vmName":"_Exception@0150898","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@0150898/scripts/dart%3Acore%2Fexceptions.dart/0","uri":"dart:core/exceptions.dart","_kind":"kernel"},"tokenPos":1005,"endTokenPos":1243},"library":{"type":"@Library","fixedId":true,"id":"libraries/@0150898","name":"dart.core","uri":"dart:core"}},"identityHashCode":1822449961,"kind":"PlainInstance","id":"objects/101"}}}}
    
    // VS Code tries to fetch information/stack for the exception/pause (some of these never respond)
    [12:21:50] [VmService] [Info] [Dart ] ==> {"id":"587","jsonrpc":"2.0","method":"invoke","params":{"argumentIds":[],"disableBreakpoints":true,"isolateId":"isolates/1903522107348907","selector":"toString","targetId":"objects/101"}}
    [12:21:50] [VmService] [Info] [Dart ] <== {"jsonrpc":"2.0","result":{"type":"@Instance","_vmType":"String","class":{"type":"@Class","fixedId":true,"id":"classes/81","name":"_OneByteString","_vmName":"_OneByteString@0150898","location":{"type":"SourceLocation","script":{"type":"@Script","fixedId":true,"id":"libraries/@0150898/scripts/dart%3Acore-patch%2Fstring_patch.dart/0","uri":"dart:core-patch/string_patch.dart","_kind":"kernel"},"tokenPos":32310,"endTokenPos":44599},"library":{"type":"@Library","fixedId":true,"id":"libraries/@0150898","name":"dart.core","uri":"dart:core"}},"identityHashCode":0,"kind":"String","id":"objects/102","length":12,"valueAsString":"Exception: c"},"id":"587"}
    [12:21:50] [VmService] [Info] [Dart ] ==> {"id":"588","jsonrpc":"2.0","method":"getObject","params":{"isolateId":"isolates/1903522107348907","objectId":"objects/101"}}
    [12:21:50] [VmService] [Info] [Dart ] ==> {"id":"589","jsonrpc":"2.0","method":"getStack","params":{"isolateId":"isolates/1903522107348907","limit":20}}
    
    // I clicked Resume to try and continue from the exception pause, but that also never responds
    [12:21:58] [VmService] [Info] [Dart ] ==> {"id":"590","jsonrpc":"2.0","method":"resume","params":{"isolateId":"isolates/1903522107348907"}}
    
    
    Thu Jul 01 2021 [12:22:21 GMT+0100 (British Summer Time)] Log file ended
    

    Is this best raised in Flutter or SDK repo?

  2. changed the title [-]restart will alway pending when there are many break points [/-] [+]restart will always pending when there are many breakpoints [/+] on Jul 2, 2021
  3. tjx666 commented on Jul 3, 2021

    @tjx666
    Author

    In fact, this issue often happens when I debug hybird app using flutter attach. At that time, I usually have to uncheck All Exceptions and Uncaught Exceptions setting at debug panel. Then press the continue button in debug toolbar and restart.

  4. jonahwilliams commented on Jul 3, 2021

    @jonahwilliams

    The flutter engine cannot hot restart if it is paused at a breakpoint , so the tool will attempt to remove breakpoints before performing the hot restart. looks like this doesn't work well if its due to an exception - but unfortunately I'm not sure there is any way around it.

    Hot restart has some requirements to run on the same thread as the dart ui thread, that requires it to be un-paused. The only way to fix this would be to find a way to relax this requirement.

    I would file a new issue on the flutter issue tracker.

  5. DanTup commented on Jul 3, 2021

    @DanTup
    Member

    so the tool will attempt to remove breakpoints before performing the hot restart. looks like this doesn't work well if its due to an exception - but unfortunately I'm not sure there is any way around it.

    Does it put these back, or rely on the IDE doing it? Perhaps it could also just set the pause-on-exception behaviour to Non (and then also put that back, or rely on the IDE putting it back)?

  6. changed the title [-]restart will always pending when there are many breakpoints [/-] [+]Hot Restart does not complete if exceptions are hit during the restart[/+] on Jul 3, 2021
  7. jonahwilliams commented on Jul 3, 2021

    @jonahwilliams

    I don't think its putting these back - since the restart destroys breakpoints anyway. We could definitely try disabling the pause on exception behavior - will the IDE reset that after a restart?

  8. DanTup commented on Jul 3, 2021

    @DanTup
    Member

    It doesn't seem like it today - but I don't think it would be an unreasonable request for them to do so if it would fix this (another option could be to force the IDEs to remove it before sending the hot reload then put them back - avoiding any changes in Flutter - although I think for consistency it'd be better done in the tool along with the breakpoints).

    @jacob314 @stevemessick wdyt? Does it seem reasonable to also update IntelliJ/DevTools to re-send pause-on-exception behaviour when they re-send breakpoints after a restart? If we shipped that, we could then update Flutter tools to reset it during the restart, which would solve this issue.

  9. jonahwilliams commented on Jul 3, 2021

    @jonahwilliams

    We could also re-enable from the tool as well. I'm not sure if there would be timing issues though

  10. DanTup commented on Jul 6, 2021

    @DanTup
    Member

    In VS Code we pass pause:true when restarting so we can re-send the breakpoints (so doing it there seems like a reasonable place). If the tool was doing it and pause:true was not passed, there could be races - but that would be the case for the breakpoints the editor is sending too so I suspect the tools would probably all be using pause:true.

    There's also the possibility that DDS may take on some of what the editors currently do (like resetting breakpoints after a restart) - there are some weird cases if you have multiple tools attached. If so, maybe this would fit nicely there too. I don't know if there are concrete plans in this area yet though (@bkonyi may).

  11. added this to the v3.26.0 milestone on Aug 21, 2021
  12. DanTup commented on Aug 24, 2021

    @DanTup
    Member

    @jacob314 @stevemessick do you have any thoughts on the above? If Flutter started resetting the break-on-exception behaviour during a restart, is it reasonable for IntelliJ/DevTools (and VS Code) to re-set them along with breakpoints before unpausing after the restart?

    (or @bkonyi - are there plans for DDS to handle some of this behaviour, and would it make sense handled there?)

  13. modified the milestones: v3.26.0, On Deck on Aug 24, 2021
  14. modified the milestones: On Deck, v3.27.0 on Sep 5, 2021
  15. modified the milestones: v3.27.0, v3.28.0 on Sep 29, 2021
  16. DanTup commented on Oct 25, 2021

    @DanTup
    Member

    I've added a fix to VS Code that disables pause-on-exceptions during a Hot Restart. It turns out we don't need to re-set it, because the restart results in a new isolate, which means we re-transmit all breakpoints and exception-pause behaviour anyway.

    The fix is currently only in the Dart-Code DAP, but also needs replicating in the SDK DAP. If we can confirm AS/IntelliJ also always send the pause-exception behaviour during a restart, we could move this to the tool instead.

  17. reopened this on Oct 25, 2021
  18. added
    in debuggingRelates to the debug adapter or process of running debug sessions
    and removed on Oct 26, 2021
  19. DanTup commented on Oct 26, 2021

    @DanTup
    Member

    I've opened #3631 to track the Flutter DAP fix, as it's a bit stalled on the main DAP work landing.

  20. DanTup commented on Nov 10, 2021

    @DanTup
    Member

    It turned out this doesn't need any cooperation from the IDEs to fix in Flutter. The flag is per-isolate and and the restart creates new isolates, so it's safe to just remove the flag on the existing isolates before the resume (same pace as breakpoints are removed) and the IDE already needs to set this flag on new isolates that are created.

    I've opened a PR to move the fix to Flutter in flutter/flutter#93411 since we needed it Flutter-side for the Flutter SDK DAP anyway.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    in debuggingRelates to the debug adapter or process of running debug sessionsis bug

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions