Skip to content

Avoid project.save() on the EDT during Flutter module setup - #9102

Open
mehmet-emre-sezer wants to merge 2 commits into
flutter:mainfrom
mehmet-emre-sezer:fix/9093-project-open-deadlock
Open

mehmet-emre-sezer wants to merge 2 commits into
flutter:mainfrom
mehmet-emre-sezer:fix/9093-project-open-deadlock

Conversation

@mehmet-emre-sezer

Copy link
Copy Markdown

What and why

FlutterModuleUtils.setFlutterModuleWithoutReload() called project.save() from
an invokeLater block on the EDT. ProjectImpl.save() enters a nested modal
progress pump on the EDT, while the document save it dispatches calls back into
the EDT via invokeAndWait for beforeAllDocumentsSaving. Neither side can
proceed and the wait has no timeout, so opening a project whose Flutter SDK path
does not resolve hangs the IDE permanently — the user cannot even reach Settings
to correct the path.

The call was also redundant. It was left behind by #8903, which removed the
ProjectManager.reloadProject(project) that used to follow it. Its only purpose
was to flush the module type to disk before that reload read it back; with the
reload gone, the platform persists the module type on its own schedule.

This PR removes the call and leaves a comment explaining why it must not come
back, following the existing convention in this file (see the comment
referencing #8480 a few lines below).

One small correction to the issue report: the deadlock does not require the
write lock. runWriteAction on the preceding line has already exited by the
time save() runs — runIntendedWriteActionOnCurrentThread in the stack trace
is invokeLater's write-intent wrapper, not a held write lock. Being on the EDT
is sufficient to reproduce it. This does not change the fix.

Related issues

Fixes #9093

Same EDT re-entrancy class as #9013 and #9055, different call site.

How to verify

  1. Point a Flutter project's SDK path at something that does not resolve (a
    dangling symlink, e.g. from an FVM cachePath change).
  2. Open the project in IntelliJ IDEA.
  3. Before this change the IDE hangs permanently after logging "Fixing Flutter
    module configuration". After it, project open completes and the IDE stays
    responsive.

The full unit test suite passes locally (32 classes, 160 tests, 0 failures),
including FlutterModuleUtilsTest and FlutterInitializerTest.

On testing

I did not add a regression test. The deadlock needs a real modal progress pump
and the document-save listener chain, neither of which the headless test fixture
sets up, so a test around setFlutterModuleWithoutReload() would pass against
the old code too and guard nothing. The method also reaches
FlutterSdkUtil.locateSdkFromPath(), which makes any test of it dependent on
the host's PATH. I opted for the explanatory comment instead, but I'm happy to
add a test if you see an angle I missed.


Review the contribution guidelines below:

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
  • I've included the required information in the description above.
  • My up-to-date information is in the AUTHORS file.
  • I've updated CHANGELOG.md if appropriate.
Contribution guidelines:
  • See
    our contributor guide and
    the Flutter organization contributor guide
    for general expectations for PRs.
  • Larger or significant changes should be discussed in an issue before creating a PR.
  • Dart contributions to our repos should follow the Dart style guide and use
    dart format.
  • Java and Kotlin contributions should strive to follow Java and Kotlin best
    practices (discussion).

project.save() on the EDT enters a nested modal pump while the document save it dispatches calls back into the EDT, so opening a project whose Flutter SDK path does not resolve hangs the IDE with no timeout. The call was left behind by flutter#8903, which removed the reloadProject() that it used to flush the module type for.

Fixes flutter#9093

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request resolves an IDE deadlock during project startup by removing a synchronous project.save() call from an asynchronous invokeLater block in FlutterModuleUtils.java. The review feedback recommends adding a guard check to ensure that neither the project nor the module has been disposed before executing the asynchronous block, preventing potential AlreadyDisposedException errors.

Comment on lines 346 to 347
ApplicationManager.getApplication().invokeLater(() -> {
ApplicationManager.getApplication().runWriteAction(() -> setFlutterModuleType(module));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

[CONCERN] Since this block is executed asynchronously on the EDT via invokeLater, the project or module might be disposed by the time the runnable is executed (e.g., if the user closes the project or IDE quickly during startup). To prevent AlreadyDisposedException, we should add a guard check for disposal at the beginning of the lambda.

Suggested change
ApplicationManager.getApplication().invokeLater(() -> {
ApplicationManager.getApplication().runWriteAction(() -> setFlutterModuleType(module));
ApplicationManager.getApplication().invokeLater(() -> {
if (project.isDisposed() || module.isDisposed()) return;
ApplicationManager.getApplication().runWriteAction(() -> setFlutterModuleType(module));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks — the race is real, but I'd rather not fold it into this PR.

It predates this change and is unrelated to the deadlock in #9093. More
importantly, the suggested guard would only cover part of it: enableDartSDK()
schedules its own invokeLater a few lines down with no disposal check, so
guarding only this lambda would leave the same race open one call deeper while
looking like it was handled.

Happy to file a separate issue and fix both properly in a follow-up PR if
maintainers agree that's the right scope.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IDE deadlocks permanently on project open when the Flutter SDK path is invalid (EDT blocked in FlutterModuleUtils.setFlutterModuleWithoutReload)

1 participant