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

x/tools/gopls: versions >= 0.7.0 consume entire cpu, whereas v0.6.11 works perfectly #48293

Closed
michaelperel opened this issue Sep 9, 2021 · 5 comments
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Milestone

Comments

@michaelperel
Copy link

michaelperel commented Sep 9, 2021

What version of Go are you using (go version)?

$ go version
go1.16.7 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

$ go env
GO111MODULE=""                                                                                                                                        
GOARCH="amd64"                                                                                                                                        
GOBIN=""                                                                                                                                              
GOCACHE="/nonroot/home/.cache/go-build"                                                                                                               
GOENV="/nonroot/home/.config/go/env"                                                                                                                  
GOEXE=""                                                                                                                                              
GOFLAGS=""                                                                                                                                            
GOHOSTARCH="amd64"                                                                                                                                    
GOHOSTOS="linux"                                                                                                                                      
GOINSECURE=""                                                                                                                                         
GOMODCACHE="/nonroot/home/go/pkg/mod"                                                                                                                 
GONOPROXY=""                                                                                                                                          
GONOSUMDB=""                                                                                                                                          
GOOS="linux"                                                                                                                                          
GOPATH="/nonroot/home/go"                                                                                                                             
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.16.7"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/nonroot/home/workspaces/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3294291348=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Using coc.nvim and gopls v0.7.1, after about 1min 30secs of jumping around function definitions (specifically :call CocAction('jumpDefinition'), gopls consumes the entire cpu of the machine and editing slows to a crawl/the machine is no longer responsive.

Downgrading to gopls v0.6.11 alleviates all issues.

Here is a picture of htop with gopls v0.7.1 that shows the high cpu consumption:
IMG_0019

Here is a picture of htop with gopls v0.6.11 that shows normal cpu consumption:
IMG_0020

Note: this seems to only occur on low powered machines. I am using a digital ocean droplet with 1 CPU. On high powered machines (8-core, ubuntu), this is not a problem. However, it appears to be a regression from v0.6.11 and makes gopls unusable for a basic VM.

Here are the machine details where the problem occurs:

$ lscpu
Architecture:                    x86_64
CPU op-mode(s):                  32-bit, 64-bit
Byte Order:                      Little Endian
Address sizes:                   40 bits physical, 48 bits virtual
CPU(s):                          1
On-line CPU(s) list:             0
Thread(s) per core:              1
Core(s) per socket:              1
Socket(s):                       1
NUMA node(s):                    1
Vendor ID:                       GenuineIntel
CPU family:                      6
Model:                           85
Model name:                      DO-Premium-Intel
Stepping:                        4
CPU MHz:                         2494.138
BogoMIPS:                        4988.27
Virtualization:                  VT-x
Hypervisor vendor:               KVM
Virtualization type:             full
L1d cache:                       32 KiB
L1i cache:                       32 KiB
L2 cache:                        4 MiB
NUMA node0 CPU(s):               0
Vulnerability Itlb multihit:     KVM: Mitigation: Split huge pages
Vulnerability L1tf:              Mitigation; PTE Inversion; VMX conditional cache flushes, SMT disabled
Vulnerability Mds:               Mitigation; Clear CPU buffers; SMT Host state unknown
Vulnerability Meltdown:          Mitigation; PTI
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1:        Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:        Mitigation; Full generic retpoline, IBPB conditional, IBRS_FW, STIBP disabled, RSB filling
Vulnerability Srbds:             Not affected
Vulnerability Tsx async abort:   Not affected
Flags:                           fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1
                                 gb rdtscp lm constant_tsc arch_perfmon rep_good nopl cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_
                                 1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_
                                 fault invpcid_single pti ssbd ibrs ibpb tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 sm
                                 ep bmi2 erms invpcid mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec 
                                 xgetbv1 pku ospke md_clear

Here is my coc-settings.json, taken from the gopls documentation:

{
  "languageserver": {
    "golang": {
      "command": "gopls",
      "rootPatterns": ["go.mod", ".vim/", ".git/", ".hg/"],
      "filetypes": ["go"]
    }
  }
}

To reproduce this issue, you can use a docker container that I have built to show the problem.

step-by-step:
(1) Spin up a low powered VM
(2) Run docker run --rm -it -v ${PWD}:/home/nonroot/workspaces/app mperel/dev-container. This container has coc.nvim and gopls v0.6.11. It maps your current directory into the container, so you can edit files in the container.
(3) Create a basic main.go and use :call CocAction(‘jumpDefinition’) to jump around the standard library (specifically, I jumped into fmt.Println and jumped around to different files from there). Notice that everything is fine.
(4) Run go install golang.org/x/tools/gopls@latest to update gopls to v0.7.1
(5) Jump around using :call CocAction(‘jumpDefinition’) and notice after a little while that the machine becomes unresponsive. Consider running htop in another window to see the cpu consumption as the photos show.

What did you expect to see?

I expected gopls >= v0.7.0 to work on a low powered linux VM, just as gopls v0.6.11 does.

What did you see instead?

I saw excessive cpu consumption that drew the machine to a halt when using gopls >= v0.7.0.

@gopherbot gopherbot added Tools This label describes issues relating to any tools in the x/tools repository. gopls Issues related to the Go language server, gopls. labels Sep 9, 2021
@gopherbot gopherbot added this to the Unreleased milestone Sep 9, 2021
@findleyr
Copy link
Contributor

findleyr commented Sep 9, 2021

Hi, thanks very much for the report. We weren't aware of this regression, and with your reproducer hopefully we can bisect.

You mention v0.7.1 specifically, but cite this problem for gopls >=v0.7.0. Just to clarify: did you test this explicitly on v0.7.0 and confirm that it reproduces with that version?

@findleyr
Copy link
Contributor

findleyr commented Sep 9, 2021

Ah, I think I know what this is.

I believe this is a consequence of https://golang.org/cl/330529, which fixed loading std and cmd, plus the way that coc.nvim sets workspace roots.

IIRC When you jump to definition in std in coc.nvim, it detects that you are jumping out of your workspace, and attempts to add a new workspace due to detecting the workspace root from the go.mod file in std. In other words, it sets the standard library as a new workspace. For workspaces, gopls loads everything (all packages, including function bodies) so that it can compute things like references. When the standard library is a workspace root, this pulls quite a lot of code into memory.

By comparison, other LSP clients (such as vscode-go) don't add a new workspace root, so gopls only fully parses and type checks packages that you have open in the standard library, not the entire standard library.

The CL above fixed a bug causing gopls to essentially not load the standard library. Before that CL, coc.nvim would have behaved very similarly to vscode-go or other LSP clients. After that CL, coc.nvim will incur a penalty for loading the entire standard library.

I wouldn't categorize this as a regression in gopls per se, but it is certainly a problem that one can't use gopls to work on the standard library on an underpowered machine. While we have some distant plans to improve this by limiting the scope of the workspace gopls has to consider, can you try managing your workspaces explicitly in coc.nvim (using :CocList folders, I think?).

@findleyr findleyr added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Sep 9, 2021
@findleyr findleyr modified the milestones: Unreleased, gopls/unplanned Sep 9, 2021
@michaelperel
Copy link
Author

michaelperel commented Sep 10, 2021

@findleyr Thanks for the quick and helpful response!

Having tested further, v0.7.0 works fine, so my title is a mistake, it should say > rather than >=. The problem is with v0.7.1 and above (I tried v0.7.1 and v0.7.2). Does that information check out with your hypothesis around https://golang.org/cl/330529?

As for running :CocList folders:

Before jumping to fmt.Println’s defintion in the standard library, I get:

/home/nonroot/workspace/app

After jumping to fmt.Println‘s definition (tested on v0.6.11 and v0.7.2), I get:

/home/nonroot/workspaces/app                                                                                                                        
/usr/local/go/src 

which shows that you are correct, jumping into the standard library adds a new workspace.

As for the severity of the issue, I wouldn’t think of it so much in terms of people working on the standard library. Rather, I would think of it in terms of people who work with the standard library - i.e. every now and then need to go to a definition to read an implementation/documentation. For this group of people (which I would imagine characterizes everyone at some point), they can no longer use coc.nvim with gopls effectively on a low powered machine. Testing out different VMs on digital ocean, v0.6.11 performs excellently on a $10 per month VM, whereas > v0.7.0 is still kind of slow on a $25 per month VM.

Do you think this is an issue that should be addressed with gopls or do you think it is more of an issue with coc.nvim?

Thanks again for all of your help :)

@findleyr
Copy link
Contributor

@michaelperel https://golang.org/cl/330529 is in v0.7.1 but not v0.7.0, so the fact that v0.7.0 is fine supports the hypothesis that this is the root cause.

Have you tried the settings listed in the coc.nvim documentation to prevent GOROOT/src from being set as a workspace folder? Unfortunately, it looks like coc.nvim really wants to place each open buffer inside a workspace.

I don't think we can (or rather should) do anything about this on the gopls side: coc.nvim is telling us that it wants GOROOT/src to be a workspace folder, and it wouldn't be correct for us to ignore this request. If you can't get this to work with the coc.nvim documentation, raising an issue with coc.nvim makes sense to me.

@findleyr
Copy link
Contributor

I'm going to close this, not because there isn't a poor experience here -- it's definitely a problem std consumes so many resources -- but because the root cause of this memory jump is not a regression. We have other open issues for performance.

@golang golang locked and limited conversation to collaborators Sep 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Projects
None yet
Development

No branches or pull requests

4 participants