joaotavora / eglot

A client for Language Server Protocol servers
GNU General Public License v3.0
2.25k stars 200 forks source link

Creating a completion list lags Emacs for several seconds if the completion list is large #1046

Closed hgranthorner closed 2 years ago

hgranthorner commented 2 years ago

Emacs hangs for several seconds when trying to create a completion list for a large number completions. I've recreated the issue with gopls (since that's where I bumped into it), but I'm not sure if it's a go specific issue. The problem doesn't manifest with vs code or with lsp-mode, so the problem appears to be with eglot.

Feel free to review this reddit post where I originally posted the issue - it may be a bit easier to follow since it's much shorter.

LSP transcript - M-x eglot-events-buffer (mandatory unless Emacs inoperable)

This thing is genuinely enormous... if you really want I'll paste it here, but everything appears to be working as expected.

Backtrace (mandatory, unless no error message seen or heard):

No error.

Minimum Reproducible Example (mandatory)

  1. How is Emacs started?

Running the emacs-plus@29 executable on mac (Emacs.app). Native compiled.

  1. Where does the language server executable live in your machine and how should it be installed?

~/go/bin/gopls, installed through go install

  1. What project files are needed to demonstrate the problem? Please don't say "just open any Zglorb source file".

Create a go module and install github.com/gdamore/tcell/v2 Then create a file (main.go or w/e) with the following contents and cursor position:

package main

import (
    "github.com/gdamore/tcell/v2"
)

func main() {
    tcell.[cursor goes here - `completion-at-point`]
}

Once you hit completion-at-point, emacs will hang for quite a bit.

  1. How is Emacs operated/configured before you invoke Eglot?

Company mode may be enabled (or not - the same behavior is observed using completion-at-point) I've tried using Emacs with and without the following settings (no difference observed):

(setq gc-cons-threshold 100000000)
(setq read-process-output-max (* 1024 1024))
  1. How is Emacs operated after you invoke Eglot7.

Still the same basically, other than lagging for several seconds.

  1. What actions must be taken for the problem to manifest itself?

Put the cursor in the indicated position and trigger completion-at-point.

  1. What is the expected behaviour?

The completion list should pop up almost immediately (as when using lsp-mode or vs code)

  1. What is the observed behaviour?

Emacs hangs for several seconds.

Please let me know if you need/want anything else.

joaotavora commented 2 years ago

If you know your way around text files with a text editor, please find the parts of the transcript containing the textdocumentl,/completions JSONRPC request and response and paste them here. Or alternatively, attach this information.

You didn't include the elisp code used to configure emacs after it has been started. You just say company may be enabled out not (in which case, better not mention it, since it's not on there minimal sweet). Please, start your Emacs when the -Q flag and show the minimal number of Elisp lines of configuration.

Then run your own recipe again and verify the problem still reproduces.

See the recently created #1037 for a well crafted minimal reproduction recipe.

joaotavora commented 2 years ago

After you have done the above things, you can try with:

(setq eglot-events-buffer-size 0)

which controls the size of the logging/debugging buffer. Sometimes, for very verbose servers, that speeds things up.

Also I recently used Eglot with the (presumably large) "Kubernetes" project and completions appear reasonably fast for me.

Create a go module and install github.com/gdamore/tcell/v2

I am not a Go programmer habitually, so I don't know what you mean by this. Please explain.

I just created an empty Go file, typed in your contents, typed M-x eglot, but the server complains "it's not in a module".

joaotavora commented 2 years ago

on there minimal sweet

Btw, sorry for the ridiculous autocorrect, i meant "in the minimal recipe"

joaotavora commented 2 years ago

Spent a bit more time with this, can't figure out how to "install tcell". Maybe issuer will complete the issue report and give:

Looking up the reddit link, if common-lisp-indent-function is a performance culprit, maybe (setq eglot-events-buffer-size 0) will do the trick.

hgranthorner commented 2 years ago

Hi @joaotavora, thank you for the responses. During creation of a minimal setup, I found that it couldn't be reproduced with default settings. Instead, the error occurs when I have

(setq lisp-indent-function 'common-lisp-indent-function)

This corresponds to what I found when I ran the profiler in Emacs as well. I guess the solution is to just not use the common-lisp-indent-function?

I created a better example here - https://github.com/hgranthorner/eglot-issue-1046. Let me know if you'd like any further clarification on the steps involved.

hgranthorner commented 2 years ago

P.S.: (setq eglot-events-buffer-size 0) does seem to fix it for me.

joaotavora commented 2 years ago

I guess the solution is to just not use the common-lisp-indent-function?

Yes, that could be a solution . I'm a Common Lisp programmer and I don't mess with that variable. I'd suggest neither should you. The variable's default value is lisp-indent-function. Only in Common Lisp mode is it common-lisp-indent-function.

Likely the problem happens because Eglot's logging statements, which occur in a sort of a Lisp (not "Common Lisp") context, is trying to use this much more expensive function.

This would also explain why (setq eglot-events-buffer-size 0) fixes it.

joaotavora commented 2 years ago

Perhaps you could indicate in the reddit post how this was solved?

BrunodaSilvaBelo commented 1 year ago

@hgranthorner i have the same problem with omnisharp server, in a large project, using the default value for gc-cons-threshold and read-process-output-max, the completion returns in 10 sec, if i increase the gc-cons-threshold by 100, for example, the completion returns in 1min30sec and if i use a huge value, i have to wait forever. I think if you use the default value for this two variable can be better. i couldn't see a difference changing the read-process-output-max @joaotavora you know why increasing the gc-cons-threshold impacts negatively the eglot performance?

joaotavora commented 1 year ago

Basic garbage collector science, I guess. I'd guess that increasing gc-cons-threshold impacts all emacs performance negatively. It means that the garbage collector is allows huge mounts of trash to build up and eventually that causes the process size to blow up, have to page to disk etc. Just guessing though.

What I think you should do is first try (setq eglot-events-buffer-size 0) and then if that doesn't fix it, follow the steps in the documentation and explain your test case clearly. The reason I suggest (setq eglot-events-buffer-size 0) is that Eglot not only reads the responses from the server, it also logs them, for debugging. That probably also uses cons cells. But since it's not essential for working (once you have things set up), then it might improve performance.

Many language servers allow you to limit the number of completions sent over the wire. That works really well, and only the first, say 100, most valuable top-sorted completions are sent over. Contact the Omnisharp people for how to do that for that server.

BrunodaSilvaBelo commented 1 year ago

@joaotavora setting eglot-events-buffer-size to 0 fixes the problem, completion returns almost immediately, i tried this change before but i didn't restart eglot and thought this did nothing :sweat_smile:

sirikid commented 1 year ago

The reason I suggest (setq eglot-events-buffer-size 0) is that Eglot not only reads the responses from the server, it also logs them, for debugging. That probably also uses cons cells. But since it's not essential for working (once you have things set up), then it might improve performance.

I see two problems here:

  1. Logging is enabled by default
  2. Each response is pretty printed

The second problem is related to jsonrpc and will probably be discussed on the bug-gnu-emacs mailing list soon, but the first can be solved on eglot side.

So, why should logging be enabled by default?

joaotavora commented 1 year ago

So, why should logging be enabled by default?

Because I get loads and loads of bug reports with just screenshots or minimal information and want to have something simple to ask my bug reporters. In all the projects I work on, I always keep logging enabled and there is no noticeable slowdown. But the servers I use (mostly clangd) are quite smart at not dumping tons of JSON and instead use the LSP "resolve" idiom.

But lets continue in Emacs bug#65760 which you've created. Maybe there is a finer-grained solution.

sirikid commented 1 year ago

But lets continue in Emacs bug#65760 which you've created. Maybe there is a finer-grained solution.

Sure, but I didn't create it.

joaotavora commented 1 year ago

Sure, but I didn't create it.

Sorry :-) was under the impression that you had.