Skip to content

fix: exit shortly after shutdown so clients can stop the server - #33

Closed
jamietanna wants to merge 1 commit into
vale-cli:mainfrom
jamietanna:fix/exit-after-shutdown
Closed

jamietanna wants to merge 1 commit into
vale-cli:mainfrom
jamietanna:fix/exit-after-shutdown

Conversation

@jamietanna

Copy link
Copy Markdown

I've noticed recently that when calling :lsp stop vale_ls in Neovim,
the vale-ls process remained alive.

With some investigation from Claude Sonnet 5.5, it highlighted that
tower-lsp 0.19's Server::serve doesn't return on exit. It only
marks the service as exited, and the final join! waits on channels
whose senders live outside the read loop, so it only completes once
stdin reaches EOF.

This also means anything placed after serve() in main is never
reached.

As clients like Neovim send shutdown then exit, and keep stdin open
until the process exits, neither LSP client nor LSP server can proceed.

In particular for Neovim, this means that on_exit and LspDetach
never fire.

As exit is the only valid message after shutdown, we can instead
exit the process from the shutdown handler, after a short delay so the
reply has been written.

Now, we exit with exit code 0 ~0.2s after shutdown with stdin still
open, with or without a following exit.

We use 200ms as a guesstimate of a reasonable time to wait for a
response ot be flushed.

A client that was extremely slow to read could see the process exit
before the reply, which should be treated as a normal shutdown.

Co-Authored-By: Claude Sonnet 5.5 claude-code@jamietanna.co.uk

I've noticed recently that when calling `:lsp stop vale_ls` in Neovim,
the `vale-ls` process remained alive.

With some investigation from Claude Sonnet 5.5, it highlighted that
`tower-lsp` 0.19's `Server::serve` doesn't return on `exit`. It only
marks the service as exited, and the final `join!` waits on channels
whose senders live outside the read loop, so it only completes once
stdin reaches EOF.

This also means anything placed after `serve()` in `main` is never
reached.

As clients like Neovim send `shutdown` then `exit`, and keep stdin open
until the process exits, neither LSP client nor LSP server can proceed.

In particular for Neovim, this means that `on_exit` and `LspDetach`
never fire.

As `exit` is the only valid message after `shutdown`, we can instead
exit the process from the `shutdown` handler, after a short delay so the
reply has been written.

Now, we exit with exit code 0 ~0.2s after `shutdown` with stdin still
open, with or without a following `exit`.

We use 200ms as a guesstimate of a reasonable time to wait for a
response ot be flushed.

A client that was extremely slow to read could see the process exit
before the reply, which should be treated as a normal shutdown.

Co-Authored-By: Claude Sonnet 5.5 <claude-code@jamietanna.co.uk>
jdkato added a commit that referenced this pull request Oct 1, 2026
tower-lsp's `serve` only returns once stdin closes, so a client that
sends `shutdown` and `exit` but keeps stdin open, as Neovim does, waited
on a server that never quit. A wrapper around the service exits on
`exit`: 0 after a shutdown, 1 without one, as the spec asks.

Reported in #33.

Signed-off-by: Joseph Kato <joseph@jdkato.io>
@jdkato

jdkato commented Oct 1, 2026

Copy link
Copy Markdown
Member

Thanks for tracking this down, and for the clear write-up of why serve never returns. It made the fix easy to find.

I went with a slightly different approach in : instead of exiting from shutdown after a delay, a small wrapper around the service exits when exit arrives. That keeps the server up after shutdown as the spec asks, exits immediately rather than after 200ms, and uses exit code 1 for an exit that comes without a shutdown. There are tests for all three that run the server with stdin held open, as Neovim does.

Closing this in favor of that commit. :lsp stop vale_ls should now end the process right away, and on_exit and LspDetach should fire. If you can confirm on your setup once it's released, that'd be great.

@jdkato jdkato closed this Oct 1, 2026
@jamietanna

Copy link
Copy Markdown
Author

Amazing, thank you! I'd wondered about raising an issue instead - but appreciate a fix either way ☺️

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