Skip to content
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

ElixirLS Running two instances on Vscode #125

Closed
tuomohopia opened this issue Jul 16, 2020 · 26 comments
Closed

ElixirLS Running two instances on Vscode #125

tuomohopia opened this issue Jul 16, 2020 · 26 comments

Comments

@tuomohopia
Copy link

Problem

ElixirLS runs two instances on my Vscode. They both do exactly the same work at their own pace, maxing out my CPU usage all the time. I can see two beam.smp instances in my Mac's Activity Monitor Panel, and likewise two ElixirLS instances in Vscode's Output panel where extensions display their logs.

Even when seemingly doing nothing (logs don't show any new entries), they both keep maxing out my CPU.

The logs on both start with:

Started ElixirLS v0.5.0
Elixir version: "1.10.3 (compiled with Erlang/OTP 22)"
Erlang version: "22"
ElixirLS compiled with Elixir 1.7.4 and erlang 20

The logs appear constantly identical, except the times to compile files are slightly different, indicating they are indeed two different instances doing the same things at the same time.

mix.exs dependencies also have {:dialyzex, "~> 1.2.1", only: [:test, :dev]},, in case that is anyhow relevant.

I'm using asdf with the following content in my .tool-versions:

elixir 1.10.3-otp-22
erlang 22.3.4.1
nodejs 11.15.0

Environment

  • Elixir & Erlang versions (elixir --version):
    Erlang/OTP 22 [erts-10.7.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe]
    Elixir 1.10.3 (compiled with Erlang/OTP 22)
  • Operating system: macOS Catalina 10.15.6 (just installed a major update)
  • Editor: VSCODE
  • LSP Client name: ElixirLS: Elixir support and debugger
@tuomohopia
Copy link
Author

It may be possible this has something to do with the latest macOS Catalina update. It was a major 3.5 gb update and I feel this may have started right after that.

@axelson
Copy link
Member

axelson commented Jul 16, 2020

I haven't been able to reproduce this yet, although I only tried on one project on Catalina. Can you reproduce it on a public repository? (or create a repository that you can reproduce it on)

@tuomohopia
Copy link
Author

Let me take a closer look during the weekend.

I got it working normally today by enabling the extension on a per workspace basis instead of globally.

@axelson
Copy link
Member

axelson commented Jul 16, 2020

I got it working normally today by enabling the extension on a per workspace basis instead of globally.

How did you enable it on a per-workspace basis vs globally?

@maxsalven
Copy link

I'm on Catalina 10.15.5, I have it enabled only for my Workspace, and I also get two instances:

Screen Shot 2020-07-17 at 3 16 59 PM

@tuomohopia
Copy link
Author

@axelson I just individually enable it for the current Workspace for each of the codebases I work on. That's from the extensions panel in Vscode.

@doughsay
Copy link

It may be possible this has something to do with the latest macOS Catalina update. It was a major 3.5 gb update and I feel this may have started right after that.

I'm running Code-OSS on Arch Linux and it's happening to me too. I think we can rule out operating system.

Are we only seeing this in umbrella projects by chance? I have a rather large umbrella project that this is happening in, but I spot checked a couple smaller (non-umbrella) elixir projects and it doesn't happen in any of those...

@doughsay
Copy link

I can't get it to happen on a fresh phoenix umbrella project: mix phx.new --live --umbrella phx_umbrella. It must be something more project specific... I'll keep digging...

@doughsay
Copy link

doughsay commented Jul 22, 2020

Got it!

Steps to reproduce (shortcuts are for Linux, not sure what they are for mac):

  1. mix new double_double
  2. cd double_double
  3. code . (or launch VScode however you normally do)
  4. toggle output (use the command palette or ctrl+k ctrl+h)
    • NOTE: you should have ZERO ElixirLS processes running
  5. open mix.exs in editor
    • NOTE: you should now have ONE ElixirLS process running
  6. create a new blank unsaved file (ctrl-n)
  7. change language mode to "Elixir" (use the command palette or ctrl-k m)
    • you should now have TWO ElixirLS processes running

Quitting the editor in this state and re-launching it using code . restores unsaved files, and the restoration of those files launches a secondary ElixirLS process. I keep unsaved files around for long periods of time sometimes, and this is why the double-process thing has been bothering me for quite a while ;) .

@maxsalven
Copy link

Thanks @doughsay, that's really helpful. I can confirm that closing all my unsaved files and restarting VSCode caused my duplicate ElixirLS instance to go away.

@axelson
Copy link
Member

axelson commented Jul 22, 2020

@doughsay thanks for the detailed reproduction steps! That's very helpful and I am indeed able to replicate this issue.

Quitting the editor in this state and re-launching it using code . restores unsaved files

That's very interesting and personally I wouldn't have ever expected that behavior although I can definitely see how it is helpful. I guess I've been trained by other software to never trust that an unsaved file will persist.

As a side-note I never encountered this bug because I always create new elixir files by right-clicking the directory and typing the filename (e.g. user_schema.ex) which does not tickle this bug.

I'm guessing that this bug was introduced in #70 and somewhat matches part of the description there:

It spawns a "default" language server for open files not belonging to a workspace and a language server for each workspace.

But I would think that if an open file not belonging to a workspace but that is within the files already "watched" by an existing server, that a new server should not be spawned. @alex88 do you have any thoughts on a clean way to resolve this?

@alex88
Copy link
Contributor

alex88 commented Jul 23, 2020

@axelson I see the same behavior, however that's expected as the text you've quoted
However I see 0% CPU usage on both LS instances, and even opening other unsaved files doesn't spawn more LS instances.
Is it a problem of having two LS instances? For new files we could pick a random existing LS but I think it'll autocomplete based on another workspace the file doesn't belong in (maybe that's not a big problem either).

Update: it seems that the LS of the open file picks up the first workspace anyway (I see the DoubleDouble autocomplete), that's probably because that's the default path VSCode gives to the LS

@alex88
Copy link
Contributor

alex88 commented Jul 23, 2020

Alright, so, I've checked the initialization request that the LS receives, for both the workspace and the in-memory file the root_uri is the first workspace path, the double compilation someone is seeing is probably because they're both using the same .elixir_ls folder..

I've also tried to:

  • open a fresh vscode instance
  • open a non-elixir project as the first workspace
  • open the elixir project as the second workspace
  • open a new in-memory file and set elixir as its language

The elixir project works just fine, the in-memory file however starts the LS in the first workspace which also creates the .elixir_ls folder in it.

I've then checked out version 0.4.0 (before the above changes) to see what the behavior was, doing the same steps as above. I've seen that the language server is started in the first workspace, so even if the first workspace isn't an elixir project, it gets "polluted" with the .elixir_ls folder. At that point both the elixir project (which is in another folder) and the in-memory files use that same LS.

I think the main conflict at this point is that there are two LS instances using the same .elixir_ls folder. One started because the first workspace is an elixir project, the second one started for the in-memory file which by default uses the first workspace as its path.

I see two possible solutions:

  1. when spawning the LS for the in-memory file, check if the first workspace has a LS instance running, if it does, use that, otherwise start the LS with the default settings which will use the first workspace as the root path
  2. when spawning the LS for the in-memory file, use something like mkdtemp to create a temp folder which will be the root of the newly spawned LS

The first solution has the problem that it "pollutes" an unrelated folder (like 0.4.0 was doing), the second one that we just need to ensure we delete the temp folder when the LS is being terminated.

What do you think?

@tuomohopia
Copy link
Author

Thanks guys, good job figuring out the problem so quickly here! At least we know how to avoid it now while figuring out a permanent fix.

@jayjun
Copy link
Contributor

jayjun commented Jul 23, 2020

How about,

  1. when spawning the LS for the in-memory file, check if the first workspace has an instance running, if it does use that, otherwise create a temp folder which will be the root of the newly spawned LS.

@doughsay
Copy link

FWIW, I vote for any solution that does still use the existing workspace for in-memory files. I use in-memory files as a scratch-pad and really do appreciate having my project's auto-complete available to me.

when spawning the LS for the in-memory file, check if the first workspace has an instance running, if it does use that, otherwise create a temp folder which will be the root of the newly spawned LS.

This seems reasonable to me!

@axelson
Copy link
Member

axelson commented Jul 23, 2020

I have a slight concern about the logic of:

when spawning the LS for the in-memory file, check if the first workspace has an instance running

Since we reverted #107 in #115 can we be sure that when the first workspace does not have an instance running that it should not? Basically I'm worried about the case when the first workspace contains elixir files but no elixir files have been opened, for example the case where only README.md has been opened.

Perhaps at that time we could do a search for an elixir file? And maybe we should only search the top-level of the workspace. Although it might be better to implement that as a re-introduction of #107 (without negatively impacting windows) while still keeping most of the benefits of #107.

@alex88
Copy link
Contributor

alex88 commented Jul 24, 2020

I think what nodejs extensions do is to navigate up the tree until they find the outermost package.json, we can probably do the same looking for mix.exs? In that way we can probably cover umbrella apps too.
However I'm not sure what will happen if we end up starting two nested LS instances when the user opens a file from the workspace (in the case when the user opens a child app in the workspace and we start the LS for the in-memory file in one of the parent folders).
The safest is probably just to start in the first workspace if we detect elixir files so that when the user opens an actual file it'll reuse that. But what if the first workspace doesn't contain elixir files?
I mean some users might not want .elixir_ls and we would have to change behavior again, at that point might as well take a decision and consider other cases as cases we won't support.

@jayjun
Copy link
Contributor

jayjun commented Jul 24, 2020

when the first workspace does not have an instance running that it should not?

My understanding is this check guarantees two instances never share the same .elixir_ls (definitely correct), not guarantee unsaved files always use an Elixir workspace’s instance if there should be one (sometimes correct) because unsaved files technically don’t belong to any workspace yet. It’s unambiguous in projects with one Elixir workspace, the problem is multi Elixir root workspaces.

@alex88 Wouldn’t one instance per outermost mix.exs avoid nested instances? If the child app belongs to a parent, it should use its instance.

I agree with reintroducing #107 somehow. Activating one ElixirLS instance per outermost mix.exs at launch is the most intuitive behaviour. Restricting unsaved files to the default ElixirLS instance is the most deterministic behaviour but the drawback is no project file code completion until your files are saved. However, allocating them to an existing ElixirLS instance “only if there is one” and nondeterministically breaking project code completion isn’t helpful either. So I’m leaning towards option 2 now.

@alex88
Copy link
Contributor

alex88 commented Jul 24, 2020

It’s unambiguous in projects with one Elixir workspace, the problem is multi Elixir root workspaces.

and also the non elixir single workspaces (although that's a rare case imho), or multi workspaces where the first one isn't elixir (e.g. this happens a lot to me when I open frontend/backend and frontend is first)

@alex88 Wouldn’t one instance per outermost mix.exs avoid nested instances? If the child app belongs to a parent, it should use its instance.

you're right, my bad, I even did that part myself. I don't think that would be a problem at this point.
So it might be better to always look for a parent mix.exs? This way even without the in-memory file we always pick the parent app as the LS root.
Which is probably always the better place to start the LS? Because in case the order is reversed (child first, parent second) we would have two LS servers because the child is already started and it wouldn't reuse the parent LS.

@krainboltgreene
Copy link

Might be related to #122

@alex88
Copy link
Contributor

alex88 commented Jul 29, 2020

Might be related to #122

it probably is since there might be two LS instances

@cjbottaro
Copy link

Is there any movement on this? I get two elixir-ls instances (and thus double of everything) when I run code . in an umbrella project. Doesn't seem to matter if I have unsaved files or whatever.

@axelson
Copy link
Member

axelson commented Dec 12, 2020

@cjbottaro that seems more like #122, I don't believe there's been any movement on this or #122. On #122 could you reply whether you have multiple folders in your workspace and ideally post reproduction steps with a git repo?

@acco
Copy link

acco commented Dec 13, 2020

Just chiming in to echo that ~6 months later, the advice above still applies. I also got this and was able to resolve by just closing open "unsaved" editor windows and restarting.

In the top left of VSCode, it will tell you how many unsaved open editors you have:

I used this little window to guide my hunt. After closing all unsaved tabs and restarting VSCode, I only had one ElixirLS running.

@lukaszsamson
Copy link
Collaborator

Closing as elixir-ls instances management has been refactored. Please reopen if this is stil relevant

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

No branches or pull requests

10 participants