Skip to content

Consistently catch LSP panics - #1405

Open
lionel- wants to merge 7 commits into
mainfrom
oak-panic/catch
Open

Consistently catch LSP panics#1405
lionel- wants to merge 7 commits into
mainfrom
oak-panic/catch

Conversation

@lionel-

@lionel- lionel- commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

This PR revisits how LSP panics are caught to:

Special care is taken for:

  • Salsa cancellation panics, which should not be reported as crash but simply advance the LSP loop or close the background task.
  • Panics and cancellations across an r_task(). This is mainly defensive, but r_task() is used in legacy parts of the LSP so we better be safe.

Unlike panics in request handlers, panics in notification handlers are unrecoverable. See these screencasts, the first causes a notification panic and shuts down the LSP in an orderly fashion after notifying the user:

Screen.Recording.2026-09-11.at.16.24.33.mov

The second is a request panic that only notifies the user and keeps the LSP in working order:

Screen.Recording.2026-09-11.at.16.25.36.mov

To avoid spamming the user with notifications, panics are only reported once per handler. They keep being logged in the background so user reports are complete.

When a panic occurs in a background task (e.g. diagnostics), a notification is sent once. Further panics are logged only.

Screen.Recording.2026-09-11.at.16.51.18.mov

Positron Release Notes

New Features

  • N/A

Bug Fixes

  • N/A

@lionel-
lionel- added this pull request to stack #1407 September 11, 2026 15:03
@juliasilge

Copy link
Copy Markdown
Member

I want to make sure I understand what's going on here. The old code caught unconditionally, right? A panic with no recovering caller was caught on the R thread, ferried back, and re-raised on the calling thread? The hook then saw no boundary and aborted. The new code aborts at the original panic site instead. Both paths abort, so is there a case where the difference is visible?

I ask because the condition removes the guard that the old comment called out: "a Rust unwind crossing those C frames is UB". Can we keep catch_unwind unconditional and decide whether to abort at the re-raise site? That would keep the guarantee from the old comment, and AFACT it gives the same observable behavior.

@lionel-

lionel- commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

@juliasilge Assuming this is about r_task(), before I implemented this catch site in 4de75ff, we were not catching anything. In that case the panic hook takes over, with specific dispositions for the R thread (backtrace capture), and aborts the process. There is no unwinding or crossing of C frames. The catching and rethrow was specifically implemented for Salsa cancellations.

I'd say the new behaviour of checking if there is a recovery boundary is marginally better because of the immediate process exit in case of panic. I also like that this unifies r_task() and Console::with() recovery mechanisms.

Are you concerned about other kinds of resume_unwind() throwing (same mechanism as Salsa cancellations) which does not invoke the panic hook? I'd says those are UB (from our own policy standpoint, not the language) to throw on the R thread. The Salsa situation is a temporary stopgap. If we wanted to do better here, we'd have to consider other boundaries like try_catch() and that seems daunting.

@lionel-

lionel- commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Worth noting that in parallel to that commit for catching salsa cancellations we've started catching LSP panics. And since the legacy LSP is the main consumer of r_task (now that comms run directly on the R threads) it should be unexpected to go through that immediately aborting path, since we are now consistently installing recovery boundaries.

Also since resume_unwind doesn't reinvoke the panic hook, re throwing panics unconditionally would result in a greater risk of a thread silently dying. Although we've been heading in the opposite direction of keeping the process alive in case of panics, we should arguably only recover in controlled situations rather than let the process in a partially working state.

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