Gopls:作为守护进程运行

注意:此功能是新推出的。如果您遇到 bug,请提交 issue

如果您只想尝试一下,请直接跳转到快速入门

背景:gopls 执行模式

Gopls 最初是作为一个 LSP 辅助进程实现的:由编辑器或编辑器插件启动,并通过 jsonrpc 2.0 在 stdin/stdout 上进行通信。通过作为有状态进程执行,gopls 可以维护大量的缓存,并可以主动对正在编辑的源代码进行分析。

当存在许多独立的编辑器进程或编辑器进程生命周期很短时,这种执行模式效果不佳,这通常是 Vim 或 Emacs 等非 IDE 编辑器的用户遇到的情况。拥有许多进程意味着拥有许多缓存,占用了大量的系统资源。使用生命周期短的会话意味着每次创建会话都要支付启动成本。

为了支持这些工作流,gopls 的执行模式得到了扩展,支持一种新的模式:单个、持久的、共享的 gopls “守护进程”负责管理所有 gopls 会话。在此模式下,编辑器仍然会启动一个 gopls 辅助进程,但该辅助进程仅充当一个轻量级的“转发器”,负责将 LSP 转发到共享的 gopls 实例,并记录指标、日志和 rpc 跟踪。

快速入门

要使用共享的 gopls 实例,您必须自己管理守护进程,或者让 gopls 转发进程按需启动共享守护进程。

使用 -remote=auto 运行

守护进程的自动管理是最简单的,可以通过将标志 -remote=auto 传递给编辑器启动的 gopls 进程来完成。这将导致该进程在需要时自动启动 gopls 守护进程,连接到它,并转发 LSP。例如,下面是一个合理的 gopls 调用,它设置了一些额外的标志以便于调试

gopls -remote=auto -logfile=auto -debug=:0 -remote.debug=:0 -rpc.trace

请注意,共享的 gopls 进程在没有连接客户端的情况下会在一分钟后自动关闭。

手动管理守护进程

要通过外部方式管理 gopls 守护进程,而不是让转发进程管理它,您必须使用 -listen=<addr> 标志启动一个 gopls 守护进程,然后将 -remote=<addr> 传递给编辑器启动的 gopls 进程。

例如,要在 TCP 端口 37374 上托管守护进程,请执行以下操作:

gopls -listen=:37374 -logfile=auto -debug=:0

然后从编辑器中运行:

gopls -remote=:37374 -logfile=auto -debug=:0 -rpc.trace

如果您使用的是 POSIX 系统,也可以通过在标志值前加上 unix; 来使用 Unix 域套接字。例如:

gopls -listen="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0

并通过以下方式连接:

gopls -remote="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0 -rpc.trace

(请注意,这些标志值必须用引号括起来,因为 ‘;’ 是一个特殊的 shell 字符。因此,此语法在未来可能会发生变化。)

调试

调试共享的 gopls 会话比调试单例会话更复杂,因为现在有两个 gopls 进程参与处理 LSP。以下是一些技巧:

查找日志文件和调试地址

在守护进程模式下运行时,您可以使用 gopls inspect sessions 命令查找 gopls 守护进程实例(以及所有连接的客户端)的日志文件和调试端口。默认情况下,这将检查默认守护进程(即 -remote=auto)。要检查不同的守护进程,请明确使用 -remote 标志:gopls -remote=localhost:12345 inspect sessions

无论您是否启用了 -remote.debug,此命令都有效。

遍历调试页面

当将 -debug=:0 传递给 gopls 时,它会运行一个 Web 服务器,该服务器提供有状态的调试页面(请参阅troubleshooting.md)。您可以通过使用 gopls inspect sessions 命令,或者通过检查日志文件的开头来找到托管这些页面的实际端口——它将是早期日志消息之一。例如,如果使用 -logfile=auto,请通过检查 head /tmp/gopls-<pid>.log 来查找调试地址。

默认情况下,gopls 守护进程不会以 -debug 启动。要启用它,请在转发器实例上设置 -remote.debug 标志,以便在启动守护进程时使用 -debug 调用 gopls。

转发器进程的调试页面将有一个链接指向守护进程服务器进程的调试页面。相应地,守护进程进程的调试页面将有一个链接指向其每个客户端。

这可以帮助您找到各种服务器和客户端的指标、跟踪和日志文件。

使用日志文件

gopls 守护进程默认以禁用日志记录的方式启动。要自定义此设置,请向 gopls 转发器传递 -remote.logfile。使用 -remote.logfile=auto,守护进程将日志记录到默认位置(在 posix 系统上:/tmp/gopls-daemon-<pid>.log)。

gopls 守护进程不记录会话范围的消息:这些消息会反射回转发器,以便编辑器可以访问。守护进程日志仅包含全局消息,例如会话连接和断开连接时的日志。

建议使用 -rpc.trace 启动转发器 gopls 进程,这样它的日志文件将包含特定于 LSP 会话的 rpc 跟踪日志。

使用多个共享 gopls 实例

在某些环境中,可能希望拥有多个共享的 gopls 实例。如果手动管理守护进程,只需为每个不同的守护进程指定不同的 -listen 地址即可。

在 POSIX 系统上,还支持自动管理不同的共享 gopls 进程:可以通过传递 -remote="auto;<id>" 来选择不同的守护进程。任何传递相同 <id> 值的 gopls 转发器将使用相同的共享守护进程。

常见问题

问:为什么在使用共享 gopls 时内存节省不如预期?

答:如implementation.md中所述,gopls 具有视图/会话/缓存的概念。每个会话和视图都精确地映射到一个编辑器会话(因为它们包含已编辑但未保存的缓冲区等内容)。缓存包含独立于任何编辑器会话的内容,因此可以共享。

例如,当三个编辑器会话共享一个 gopls 进程时,它们将共享缓存,但每个会话都有自己的会话和视图。与三个独立的 gopls 进程相比,此模式下的内存节省相当于会话之间缓存重叠的量。

由于过去这一点不太重要,因此很可能有一些状态可以从会话/视图移到缓存中,从而增加共享模式下的内存节省量。

问:在使用 -remote=auto 时,如何自定义守护进程实例?

可以使用转发器 gopls 上的 -remote.* 形式的标志来自定义守护进程。这会导致转发器在启动守护进程时使用这些设置调用 gopls。截至目前,我们暴露了以下配置:

  • -remote.logfile:守护进程日志文件的位置
  • -remote.debug:守护进程的调试地址
  • -remote.listen.timeout:当没有当前连接时,守护进程在关闭之前等待新连接的时间。必须设置为有效的 time.Duration(例如 30s5m)。如果设置为 0,则无限期等待。默认值:1m

请注意,一旦守护进程正在运行,设置这些标志将不会改变其配置。这些标志仅对实际启动守护进程的转发器进程有效。


本文档的源代码可以在 golang.org/x/tools/gopls/doc 下找到。