Skip to content

Infinite spinner while trying to load property editor #8144

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

Closed
kenzieschmoll opened this issue Apr 30, 2025 · 11 comments · Fixed by #8177
Closed

Infinite spinner while trying to load property editor #8144

kenzieschmoll opened this issue Apr 30, 2025 · 11 comments · Fixed by #8177
Assignees
Labels
Milestone

Comments

@kenzieschmoll
Copy link
Member

kenzieschmoll commented Apr 30, 2025

Successful load (remove .txt to load in Chrome DevTools):

successful_load.har.txt

@jwren @elliette

@kenzieschmoll kenzieschmoll added this to the M85.3 milestone Apr 30, 2025
@jwren
Copy link
Member

jwren commented Apr 30, 2025

Attaching a spinning ball case:

@kenzieschmoll
Copy link
Member Author

Attaching a spinning ball case:

I think the attachment is missing. I had to add .txt to the file extension to get github to accept it.

@DanTup
Copy link
Contributor

DanTup commented May 1, 2025

I can reproduce this in Android Studio. I saw it consistently yesterday, however today I was able to load the property editor once, however it usually fails. When it fails, the property editor connects to DTD and connects to the Service stream, but it is not told about any services (like the Editor services), and therefore doesn't initialize.

I enabled the analysis server instrumentation log and confirmed that Android Studio was calling connectToDtd. However, it appears that it's connecting to a different version to the one sent to the analysis server:

Image

When I review my running processes, I can see a single DTD instance, and I can see that DevTools was started with a DTD URI that matches the one the analysis server connected to.. However I also see another DevTools running, that does not have a --dtd-uri:

Image

Based on this, my current theory (with zero knowledge of how the plugins work) is that Android Studio is somehow spawning two DevTools servers. One is correctly being provided a DTD URI (for the DTD that Android Studio started), but the second is not, and is therefore spawning its own DTD, and that's the server being used by the Property Editor panel.

Since this is intermittent, I wonder if there is a race here, and when it works, only one DevTools server was spawned (or, both were spawned after DTD was started and therefore were provided the same/correct DTD URI).

@DanTup
Copy link
Contributor

DanTup commented May 1, 2025

I missed it in the list before, but I do also see the second instance of DTD in the process list, but it is spawned as dartaotruntime.exe instead of dart.exe (my assumption is that this is the one spawned by DevTools).

Image

I don't think I know enough about the plugins to debug this any further, but perhaps it will be useful to someone more familiar with them.

@DanTup
Copy link
Contributor

DanTup commented May 1, 2025

@jwren @alexander-doroshko I had a quick search across the plugin code to see if I could spot anything. I found that DevTools is spawned here:

https://github.com/JetBrains/intellij-plugins/blob/351d68772945e958214652001bb448d8ca549cff/Dart/src/com/jetbrains/lang/dart/ide/devtools/DartDevToolsService.kt#L32

The startService function checks serviceRunning to (presumably) guard against spawning a second copy of DevTools. However this boolean is not set to true until the server.started event arrives from the server. I think this means if there is a second call to startService() before the first one is completely up and running, it would still result in two DevTools servers running?

Fixing that (for example by setting serviceRunning=true immediately in startService) might not solve the issue if the first one spawned is the one that doesn't have the DTD URI.

@jwren
Copy link
Member

jwren commented May 1, 2025

Attaching a spinning ball case:

I think the attachment is missing. I had to add .txt to the file extension to get github to accept it.

I did as well, but it keeps getting rejected by GitHub.

@pq pq self-assigned this May 2, 2025
@pq pq added the P1 label May 2, 2025
@pq
Copy link
Contributor

pq commented May 2, 2025

Thanks for the digging @DanTup!

DevTools is also potentially spawned in the Flutter Plugin here:

final String dartPluginUri = DartDevToolsService.getInstance(project).getDevToolsHostAndPort();
if (dartPluginUri != null) {
String[] parts = dartPluginUri.split(":");
String host = parts[0];
Integer port = Integer.parseInt(parts[1]);
if (host != null && port != null) {
devToolsFutureRef.get().complete(new DevToolsInstance(host, port));
return;
}
}
setUpWithDart(createCommand(DartSdk.getDartSdk(project).getHomePath(),
DartSdk.getDartSdk(project).getHomePath() + File.separatorChar + "bin" + File.separatorChar + "dart",
ImmutableList.of("devtools", "--machine")));
}

If dartPluginUri returns null (as it could if DevTools is not fully up yet), line 179 will call setUpWithDart and create another instance. (In fact, forcing this to happen by commenting out the check above is the only way I could reproduce the spinner; but then I could!)

What's interesting is that this fallback to create a DevTools instance here may be entirely unnecessary and was only in place to support a time when the Dart plugin wasn't creating one itself (or unreliably). As that time was long ago, at least since JetBrains/intellij-plugins@9182286, we can probably safely skip this fallback and I'm exploring what that might mean.

Whether or not we can do away with that instance creation, I'm also wondering about getting the DevTools instance information properly cached for the views that need to access it. In particular, what I don't know is whether we need to put a wait condition around DartDevToolsService.getInstance(project).getDevToolsHostAndPort() to ensure that the host and port get properly set in the future for later retrieval. Specifically this needs to get called at some point before anyone can get details.

devToolsFutureRef.get().complete(new DevToolsInstance(host, port));

That said, the containing method startServer() does get called a few times so it's possible that the race is rarely (if ever) lost in practice and that by the time, for example the property editor, needs the port and host the last call to startServer() has set it, even if the first had not.

(fyi @elliette who may have more insight as I'm just getting my head wrapped around all of this.)

Another thing that could be confusing folks debugging is whether they've got a LOCAL_DEVTOOLS_DIR registry entry set. If they do, we have another path to a second DevTools instance:

final String localDevToolsDir = Registry.stringValue(LOCAL_DEVTOOLS_DIR);
if (!localDevToolsDir.isEmpty()) {
// This is only for development to check integration with a locally run DevTools server.
// To enable, follow the instructions in:
// https://github.com/flutter/flutter-intellij/blob/master/CONTRIBUTING.md#developing-with-local-devtools
final DtdUtils dtdUtils = new DtdUtils();
try {
final DartToolingDaemonService dtdService = dtdUtils.readyDtdService(project).get();
final String dtdUri = dtdService.getUri();
final List<String> args = new ArrayList<>();
args.add("serve");
args.add("--machine");
args.add("--dtd-uri=" + dtdUri);
final String localDevToolsArgs = Registry.stringValue(LOCAL_DEVTOOLS_ARGS);
if (!localDevToolsArgs.isEmpty()) {
args.addAll(Arrays.stream(localDevToolsArgs.split(" ")).toList());
}
setUpInDevMode(createCommand(localDevToolsDir, "dt", args));
}
catch (InterruptedException | java.util.concurrent.ExecutionException e) {
throw new RuntimeException(e);
}
return;
}

There's also the (slim) chance folks will get a second instance here:

// This condition means we can use `dart devtools` to start.
final WorkspaceCache workspaceCache = WorkspaceCache.getInstance(project);
if (workspaceCache.isBazel()) {
// This is only for internal usages.
setUpWithDart(createCommand(workspaceCache.get().getRoot().getPath(), workspaceCache.get().getDevToolsScript(),
ImmutableList.of("--machine")));
}

The comment suggests this is internal only but I'm not sure if that's totally right as there are external folks using Bazel workspaces. (I think?)

@pq
Copy link
Contributor

pq commented May 2, 2025

Doing further testing I was able to reproduce a spinner! 🎉

Luckily I had added some logging that confirmed I was spawning a second DevTools from the flutter plugin. To test the earlier hypothesis, I removed the spawning logic and saw no spinner but an empty property editor:

Image

The logs reveal the timing issue:

2025-05-02 14:04:36,539 [ 319394]   INFO - #io.flutter.run.daemon.DevToolsService - Calling server start
2025-05-02 14:04:36,539 [ 319394]   INFO - #io.flutter.run.daemon.DevToolsService - Looking for a running DevTools instance...
2025-05-02 14:04:36,540 [ 319395]   INFO - #io.flutter.run.daemon.DevToolsService - No DevTools instance found
2025-05-02 14:04:37,345 [ 320200]   INFO - #o.j.j.b.i.CompilerReferenceIndex - backward reference index version doesn't exist
2025-05-02 14:04:37,346 [ 320201]   INFO - #c.j.l.d.i.t.DartToolingDaemonService - Starting Dart Tooling Daemon, sdk 3.9.0-63.0.dev
2025-05-02 14:04:37,346 [ 320201]   INFO - #c.i.c.b.IsUpToDateCheckStartupActivity - suitable consumer is not found
2025-05-02 14:04:37,359 [ 320214]   INFO - #c.i.w.i.i.GlobalWorkspaceModel - Sync global entities with mutable entity storage
2025-05-02 14:04:37,360 [ 320215]   INFO - #c.i.w.i.i.j.s.JpsProjectModelSynchronizer - Attempt 1: Apply JPS storage (iml files)
2025-05-02 14:04:37,372 [ 320227]   INFO - #c.i.w.i.i.EntitiesOrphanageImpl - Update orphanage. 0 modules added
2025-05-02 14:04:37,373 [ 320228]   INFO - #c.i.w.i.i.j.s.JpsProjectModelSynchronizer - Attempt 1: Changes were successfully applied
2025-05-02 14:04:37,373 [ 320228]   INFO - #c.i.w.i.i.j.s.DelayedProjectSynchronizer$Util - Workspace model loaded from cache. Syncing real project state into workspace model in 28 ms. Thread[#44,DefaultDispatcher-worker-26 @run activity#97624,5,main]
2025-05-02 14:04:37,387 [ 320242]   INFO - #c.i.o.e.s.p.IdeModifiableModelsProviderImpl - Ide modifiable models provider, create builder from version 5
2025-05-02 14:04:37,463 [ 320318]   INFO - #c.j.l.d.i.d.DartDevToolsService - Starting Dart DevTools, sdk 3.9.0-63.0.dev
2025-05-02 14:04:37,476 [ 320331]   INFO - #c.j.l.d.i.t.DartToolingDaemonService - Connected to DTD successfully

DevTools had not started until after we'd asked for it.

It occurs to me that while adding a conditional wait might solve the problem it could introduce another one if it required us to block the UI thread on tool window creation.

Maybe we could rejigger the DevTools tool windows to subscribe to a service and get updated when the server is up?

Relatedly, is there an existing way to restart DevTools from the IDE? How does that work with the toolbars? On the surface, it looks like they would remain attached to the defunct instance and need to be closed and reopend to get a fresh connection?

@helin24: maybe you know?

@pq
Copy link
Contributor

pq commented May 2, 2025

For anyone following along, another thing I'm noticing is that I most reliably get a spinner if the property editor is open when I start the IDE. If the inspector is active instead, the property editor is more likely to be happy when I switch to it.

Logs from a session with the inspector active on open where I switch to the property editor:

025-05-02 16:32:43,569 [  11020]   INFO - #o.j.j.b.i.CompilerReferenceIndex - backward reference index version doesn't exist
2025-05-02 16:32:43,574 [  11025]   INFO - #c.i.c.b.IsUpToDateCheckStartupActivity - suitable consumer is not found
2025-05-02 16:32:43,577 [  11028]   INFO - #c.j.l.d.i.t.DartToolingDaemonService - Starting Dart Tooling Daemon, sdk 3.9.0-63.0.dev
2025-05-02 16:32:43,585 [  11036]   INFO - #c.i.w.i.i.GlobalWorkspaceModel - Sync global entities with mutable entity storage
2025-05-02 16:32:43,588 [  11039]   INFO - #c.i.w.i.i.j.s.JpsProjectModelSynchronizer - Attempt 1: Apply JPS storage (iml files)
2025-05-02 16:32:43,595 [  11046]   INFO - #c.i.w.i.i.EntitiesOrphanageImpl - Update orphanage. 0 modules added
2025-05-02 16:32:43,596 [  11047]   INFO - #c.i.w.i.i.j.s.JpsProjectModelSynchronizer - Attempt 1: Changes were successfully applied
2025-05-02 16:32:43,598 [  11049]   INFO - #c.i.w.i.i.j.s.DelayedProjectSynchronizer$Util - Workspace model loaded from cache. Syncing real project state into workspace model in 39 ms. Thread[#52,DefaultDispatcher-worker-27 @run activity#8468,5,main]
2025-05-02 16:32:43,614 [  11065]   INFO - #c.i.o.e.s.p.IdeModifiableModelsProviderImpl - Ide modifiable models provider, create builder from version 6
2025-05-02 16:32:43,667 [  11118]   INFO - #c.j.l.d.i.d.DartDevToolsService - Starting Dart DevTools, sdk 3.9.0-63.0.dev
2025-05-02 16:32:43,683 [  11134]   INFO - #c.j.l.d.i.t.DartToolingDaemonService - Connected to DTD successfully
2025-05-02 16:32:44,735 [  12186]   INFO - #io.flutter.run.daemon.DevToolsService - Getting DevTools instance
2025-05-02 16:32:44,735 [  12186]   INFO - #io.flutter.run.daemon.DevToolsService - Calling server start
2025-05-02 16:32:44,736 [  12187]   INFO - #io.flutter.run.daemon.DevToolsService - Looking for a running DevTools instance...
2025-05-02 16:32:44,736 [  12187]   INFO - #io.flutter.run.daemon.DevToolsService - Found running DevTools instance: 127.0.0.1:9100

@pq
Copy link
Contributor

pq commented May 5, 2025

A bit more logging suggests that the timing issue boils down to a sequence like this:

  1. IDE Opens
  2. Project gets initialized, Dart Plugin starts process to launch DevTools
  3. Platform builds tool windows
  4. Properties Editor looks for a DevTools instance when creating content (but finds none)
  5. Another one is started but it won't connect as @DanTup observed above
  6. First DevTools instance finishes startup
2025-05-05 09:15:18,370 [   1680]   INFO - #io.flutter.propertyeditor.PropertyEditorViewFactory - Creating tool window content: Flutter Property Editor
2025-05-05 09:15:18,372 [   1682]   INFO - #io.flutter.run.daemon.DevToolsService - Getting DevTools instance
2025-05-05 09:15:18,372 [   1682]   INFO - #io.flutter.run.daemon.DevToolsService - Calling server start
2025-05-05 09:15:18,374 [   1684]   INFO - #io.flutter.run.daemon.DevToolsService - Looking for a running DevTools instance...
2025-05-05 09:15:18,375 [   1685]   INFO - #io.flutter.run.daemon.DevToolsService - No DevTools instance found

...

2025-05-05 09:15:24,564 [   7874]   INFO - #c.j.l.d.i.d.DartDevToolsService - Starting Dart DevTools, sdk 3.9.0-63.0.dev
2025-05-05 09:15:24,579 [   7889]   INFO - #c.j.l.d.i.t.DartToolingDaemonService - Connected to DTD successfully
2025-05-05 09:15:24,584 [   7894]   INFO - #io.flutter.run.daemon.DeviceDaemon - flutter device daemon #1: Device daemon started.

/fyi @helin24 @elliette who might have ideas about how we to best re-think tool window content initialization

@DanTup
Copy link
Contributor

DanTup commented May 10, 2025

@elliette @pq if there's value in me testing this locally before it releases (I had the issue almost 100% of the time on Android Studio / Windows), let me know (although I have no idea how to run from source, so you might need to provide a dev build).

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

Successfully merging a pull request may close this issue.

4 participants