Skip to content

Fix LSP timeout behaviour when loading a large package of small modules - #23224

Open
cgibbard wants to merge 2 commits into
digital-asset:mainfrom
obsidiansystems:keepalive-configurable-and-activity-based
Open

Fix LSP timeout behaviour when loading a large package of small modules#23224
cgibbard wants to merge 2 commits into
digital-asset:mainfrom
obsidiansystems:keepalive-configurable-and-activity-based

Conversation

@cgibbard

@cgibbard cgibbard commented Jul 22, 2026

Copy link
Copy Markdown

These changes make the LSP client timeout get reset any time that the LSP server produces output, which fixes the case where damlc is making good progress on building modules but doesn't get through them all before hitting the timeout.

Additionally, this adds configuration options for the keepalive interval and timeout, with the same defaults as the current client had. There was a comment from (JM) to remember to update the language server's timeouts if the client's timeouts were changed, which seemed relevant at first (if we make it configurable, both things should use the same config), but I didn't actually see any code that depended on similar values over in daml-ide or daml-ide-core. If I was looking in the wrong place (or suffering from blindness 😅), please help me out. Happy to split the PR or drop the configurability if it turns out to be undesirable. The timeout resets more robustly fixed the issue we were having regardless.

The timeout gets reset any time that the server produces output, which
fixes the case where damlc is making good progress on building modules
but doesn't get through them all before hitting the timeout.

Additionally, add configuration for the keepalive interval and timeout.

@samuel-williams-da samuel-williams-da left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're allowing the timeouts to be changed, we might want to communicate the keep alive interval to the language server, via startup params. If you've reduced this 60s KA timer locally, have you faced any issues during inactivity?

Comment on lines +490 to +492
if (proc && typeof proc.kill === "function") {
proc.kill("SIGTERM");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Killing processes here is tricky, since we support many platforms. Which platform have you tried this on? Are you able to verify this works in Windows?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this is what it was already doing to try to kill the process, but you're right that it's suspicious and we should check this. I don't have a Windows machine to hand, but I'll get one of my coworkers to try it.

@cgibbard cgibbard Jul 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://nodejs.org/api/child_process.html#subprocesskillsignal seems to indicate that on Windows, the kill method will kill the process forcefully when given any of 'SIGKILL', 'SIGTERM', 'SIGINT' or 'SIGQUIT', which perhaps is less nice than it ought to be (might not give the murdered process a chance to clean up subprocesses...), but I'm not certain yet what to do about that, if anything.

For fun, I also tracked down where this actually happens. Node delegates to the libuv library to abstract over OS-specific things, and ultimately ends up in uv__kill, here

I discovered during further experimentation.
* Fixes a missing field error that was present from before:
  _childProcess was renamed to _serverProcess in the
  vscode-languageclient library, and if we actually tried to kill the
  server, we'd instead just error out due to the missing field
* Also reattach the stdout/stderr listeners when the server process is
  restarted
* Fix an issue where self.startKeepAliveWatchdog was being run with
  `this` set to the wrong object (or maybe undefined) when restarting
  it 10s after killing the process.
@cgibbard

cgibbard commented Jul 23, 2026

Copy link
Copy Markdown
Author

Since you're allowing the timeouts to be changed, we might want to communicate the keep alive interval to the language server, via startup params. If you've reduced this 60s KA timer locally, have you faced any issues during inactivity?

I did a bunch of additional testing here, and while I didn't have any issues reducing the intervals, I did actually find a few more issues, and fixed them.

The new liveness checking had prevented the code path where we actually try to kill the server from being exercised at all, so I hadn't noticed some other problems with it. Upon sending a STOP signal to the running damlc so that it wouldn't respond to the keepalive, I noticed that the client died while trying to kill it. The culprit was this commit in 2017 to vscode-languageclient which renamed _childProcess to _serverProcess. (The Haskeller in me is screaming about how this ought to have been a static type error.)

I also noticed once I fixed that that when the server came back up, after a 10 second timeout when we tried to restart the watchdog, the this variable wasn't set correctly, making the startKeepAliveWatchdog code fail. Using a lambda there so we could actually apply the function to an empty argument list (and capture the appropriate this fixed that.

Following that, I realized that when the server restarts, our liveness event handlers would no longer be bound to the right process, so I added some code to reconnect them when the client's state changes.

Doing a little more research on the timeout values point, on the server-side, both in the single IDE code and the multi IDE code the language server handles the keepalive by responding immediately with no apparent sensitivity to whatever timeouts the client is using.

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

Successfully merging this pull request may close these issues.

2 participants