Skip to content

Commit 0b31c63

Browse files
authored
Disable the LSP on crash (#679)
1 parent 846b9e7 commit 0b31c63

File tree

3 files changed

+171
-47
lines changed

3 files changed

+171
-47
lines changed

crates/ark/src/lsp/backend.rs

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77

88
#![allow(deprecated)]
99

10+
use std::sync::atomic::Ordering;
1011
use std::sync::Arc;
1112

13+
use amalthea::comm::ui_comm::ShowMessageParams as UiShowMessageParams;
14+
use amalthea::comm::ui_comm::UiFrontendEvent;
1215
use crossbeam::channel::Sender;
1316
use serde_json::Value;
1417
use stdext::result::ResultOrLog;
@@ -26,6 +29,7 @@ use tower_lsp::LanguageServer;
2629
use tower_lsp::LspService;
2730
use tower_lsp::Server;
2831

32+
use super::main_loop::LSP_HAS_CRASHED;
2933
use crate::interface::RMain;
3034
use crate::lsp::handlers::VirtualDocumentParams;
3135
use crate::lsp::handlers::VirtualDocumentResponse;
@@ -44,24 +48,68 @@ use crate::lsp::statement_range::StatementRangeParams;
4448
use crate::lsp::statement_range::StatementRangeResponse;
4549
use crate::r_task;
4650

51+
// This enum is useful for two things. First it allows us to distinguish a
52+
// normal request failure from a crash. In the latter case we send a
53+
// notification to the client so the user knows the LSP has crashed.
54+
//
55+
// Once the LSP has crashed all requests respond with an error. This prevents
56+
// any handler from running while we process the message to shut down the
57+
// server. The `Disabled` enum variant is an indicator of this state. We could
58+
// have just created an anyhow error passed through the `Result` variant but that
59+
// would flood the LSP logs with irrelevant backtraces.
60+
pub(crate) enum RequestResponse {
61+
Disabled,
62+
Crashed(anyhow::Result<LspResponse>),
63+
Result(anyhow::Result<LspResponse>),
64+
}
65+
4766
// Based on https://stackoverflow.com/a/69324393/1725177
4867
macro_rules! cast_response {
49-
($target:expr, $pat:path) => {{
68+
($self:expr, $target:expr, $pat:path) => {{
5069
match $target {
51-
Ok($pat(resp)) => Ok(resp),
52-
Err(err) => Err(new_jsonrpc_error(format!("{err:?}"))),
70+
RequestResponse::Result(Ok($pat(resp))) => Ok(resp),
71+
RequestResponse::Result(Err(err)) => Err(new_jsonrpc_error(format!("{err:?}"))),
72+
RequestResponse::Crashed(err) => {
73+
// Notify user that the LSP has crashed and is no longer active
74+
report_crash();
75+
76+
// The backtrace is reported via `err` and eventually shows up
77+
// in the LSP logs on the client side
78+
let _ = $self.shutdown_tx.send(()).await;
79+
Err(new_jsonrpc_error(format!("{err:?}")))
80+
},
81+
RequestResponse::Disabled => Err(new_jsonrpc_error(String::from(
82+
"The LSP server has crashed and is now shut down!",
83+
))),
5384
_ => panic!("Unexpected variant while casting to {}", stringify!($pat)),
5485
}
5586
}};
5687
}
5788

89+
fn report_crash() {
90+
let user_message = concat!(
91+
"The R language server has crashed and has been disabled. ",
92+
"Smart features such as completions will no longer work in this session. ",
93+
"Please report this crash to https://github.com/posit-dev/positron/issues ",
94+
"with full logs (see https://positron.posit.co/troubleshooting.html#python-and-r-logs)."
95+
);
96+
97+
r_task(|| {
98+
let event = UiFrontendEvent::ShowMessage(UiShowMessageParams {
99+
message: String::from(user_message),
100+
});
101+
102+
let main = RMain::get();
103+
if let Some(ui_comm_tx) = main.get_ui_comm_tx() {
104+
ui_comm_tx.send_event(event);
105+
}
106+
});
107+
}
108+
58109
#[derive(Debug)]
59110
pub(crate) enum LspMessage {
60111
Notification(LspNotification),
61-
Request(
62-
LspRequest,
63-
TokioUnboundedSender<anyhow::Result<LspResponse>>,
64-
),
112+
Request(LspRequest, TokioUnboundedSender<RequestResponse>),
65113
}
66114

67115
#[derive(Debug)]
@@ -79,7 +127,6 @@ pub(crate) enum LspNotification {
79127
#[derive(Debug)]
80128
pub(crate) enum LspRequest {
81129
Initialize(InitializeParams),
82-
Shutdown(),
83130
WorkspaceSymbol(WorkspaceSymbolParams),
84131
DocumentSymbol(DocumentSymbolParams),
85132
ExecuteCommand(ExecuteCommandParams),
@@ -101,7 +148,6 @@ pub(crate) enum LspRequest {
101148
#[derive(Debug)]
102149
pub(crate) enum LspResponse {
103150
Initialize(InitializeResult),
104-
Shutdown(()),
105151
WorkspaceSymbol(Option<Vec<SymbolInformation>>),
106152
DocumentSymbol(Option<DocumentSymbolResponse>),
107153
ExecuteCommand(Option<Value>),
@@ -122,6 +168,10 @@ pub(crate) enum LspResponse {
122168

123169
#[derive(Debug)]
124170
struct Backend {
171+
/// Shutdown notifier used to unwind tower-lsp and disconnect from the
172+
/// client when an LSP handler panics.
173+
shutdown_tx: tokio::sync::mpsc::Sender<()>,
174+
125175
/// Channel for communication with the main loop.
126176
events_tx: TokioUnboundedSender<Event>,
127177

@@ -131,9 +181,12 @@ struct Backend {
131181
}
132182

133183
impl Backend {
134-
async fn request(&self, request: LspRequest) -> anyhow::Result<LspResponse> {
135-
let (response_tx, mut response_rx) =
136-
tokio_unbounded_channel::<anyhow::Result<LspResponse>>();
184+
async fn request(&self, request: LspRequest) -> RequestResponse {
185+
if LSP_HAS_CRASHED.load(Ordering::Acquire) {
186+
return RequestResponse::Disabled;
187+
}
188+
189+
let (response_tx, mut response_rx) = tokio_unbounded_channel::<RequestResponse>();
137190

138191
// Relay request to main loop
139192
self.events_tx
@@ -156,6 +209,7 @@ impl Backend {
156209
impl LanguageServer for Backend {
157210
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
158211
cast_response!(
212+
self,
159213
self.request(LspRequest::Initialize(params)).await,
160214
LspResponse::Initialize
161215
)
@@ -166,10 +220,9 @@ impl LanguageServer for Backend {
166220
}
167221

168222
async fn shutdown(&self) -> Result<()> {
169-
cast_response!(
170-
self.request(LspRequest::Shutdown()).await,
171-
LspResponse::Shutdown
172-
)
223+
// Don't go through the main loop because we want this request to
224+
// succeed even when the LSP has crashed and has been disabled.
225+
Ok(())
173226
}
174227

175228
async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
@@ -189,6 +242,7 @@ impl LanguageServer for Backend {
189242
params: WorkspaceSymbolParams,
190243
) -> Result<Option<Vec<SymbolInformation>>> {
191244
cast_response!(
245+
self,
192246
self.request(LspRequest::WorkspaceSymbol(params)).await,
193247
LspResponse::WorkspaceSymbol
194248
)
@@ -199,6 +253,7 @@ impl LanguageServer for Backend {
199253
params: DocumentSymbolParams,
200254
) -> Result<Option<DocumentSymbolResponse>> {
201255
cast_response!(
256+
self,
202257
self.request(LspRequest::DocumentSymbol(params)).await,
203258
LspResponse::DocumentSymbol
204259
)
@@ -209,6 +264,7 @@ impl LanguageServer for Backend {
209264
params: ExecuteCommandParams,
210265
) -> jsonrpc::Result<Option<Value>> {
211266
cast_response!(
267+
self,
212268
self.request(LspRequest::ExecuteCommand(params)).await,
213269
LspResponse::ExecuteCommand
214270
)
@@ -232,27 +288,31 @@ impl LanguageServer for Backend {
232288

233289
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
234290
cast_response!(
291+
self,
235292
self.request(LspRequest::Completion(params)).await,
236293
LspResponse::Completion
237294
)
238295
}
239296

240297
async fn completion_resolve(&self, item: CompletionItem) -> Result<CompletionItem> {
241298
cast_response!(
299+
self,
242300
self.request(LspRequest::CompletionResolve(item)).await,
243301
LspResponse::CompletionResolve
244302
)
245303
}
246304

247305
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
248306
cast_response!(
307+
self,
249308
self.request(LspRequest::Hover(params)).await,
250309
LspResponse::Hover
251310
)
252311
}
253312

254313
async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
255314
cast_response!(
315+
self,
256316
self.request(LspRequest::SignatureHelp(params)).await,
257317
LspResponse::SignatureHelp
258318
)
@@ -263,6 +323,7 @@ impl LanguageServer for Backend {
263323
params: GotoDefinitionParams,
264324
) -> Result<Option<GotoDefinitionResponse>> {
265325
cast_response!(
326+
self,
266327
self.request(LspRequest::GotoDefinition(params)).await,
267328
LspResponse::GotoDefinition
268329
)
@@ -273,6 +334,7 @@ impl LanguageServer for Backend {
273334
params: GotoImplementationParams,
274335
) -> Result<Option<GotoImplementationResponse>> {
275336
cast_response!(
337+
self,
276338
self.request(LspRequest::GotoImplementation(params)).await,
277339
LspResponse::GotoImplementation
278340
)
@@ -283,13 +345,15 @@ impl LanguageServer for Backend {
283345
params: SelectionRangeParams,
284346
) -> Result<Option<Vec<SelectionRange>>> {
285347
cast_response!(
348+
self,
286349
self.request(LspRequest::SelectionRange(params)).await,
287350
LspResponse::SelectionRange
288351
)
289352
}
290353

291354
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
292355
cast_response!(
356+
self,
293357
self.request(LspRequest::References(params)).await,
294358
LspResponse::References
295359
)
@@ -300,6 +364,7 @@ impl LanguageServer for Backend {
300364
params: DocumentOnTypeFormattingParams,
301365
) -> Result<Option<Vec<TextEdit>>> {
302366
cast_response!(
367+
self,
303368
self.request(LspRequest::OnTypeFormatting(params)).await,
304369
LspResponse::OnTypeFormatting
305370
)
@@ -327,6 +392,7 @@ impl Backend {
327392
params: StatementRangeParams,
328393
) -> jsonrpc::Result<Option<StatementRangeResponse>> {
329394
cast_response!(
395+
self,
330396
self.request(LspRequest::StatementRange(params)).await,
331397
LspResponse::StatementRange
332398
)
@@ -337,6 +403,7 @@ impl Backend {
337403
params: HelpTopicParams,
338404
) -> jsonrpc::Result<Option<HelpTopicResponse>> {
339405
cast_response!(
406+
self,
340407
self.request(LspRequest::HelpTopic(params)).await,
341408
LspResponse::HelpTopic
342409
)
@@ -347,6 +414,7 @@ impl Backend {
347414
params: VirtualDocumentParams,
348415
) -> tower_lsp::jsonrpc::Result<VirtualDocumentResponse> {
349416
cast_response!(
417+
self,
350418
self.request(LspRequest::VirtualDocument(params)).await,
351419
LspResponse::VirtualDocument
352420
)
@@ -357,6 +425,7 @@ impl Backend {
357425
params: InputBoundariesParams,
358426
) -> tower_lsp::jsonrpc::Result<InputBoundariesResponse> {
359427
cast_response!(
428+
self,
360429
self.request(LspRequest::InputBoundaries(params)).await,
361430
LspResponse::InputBoundaries
362431
)
@@ -381,6 +450,8 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
381450
log::trace!("Connected to LSP at '{}'", address);
382451
let (read, write) = tokio::io::split(stream);
383452

453+
let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
454+
384455
let init = |client: Client| {
385456
let state = GlobalState::new(client);
386457
let events_tx = state.events_tx();
@@ -403,6 +474,7 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
403474
});
404475

405476
Backend {
477+
shutdown_tx,
406478
events_tx,
407479
_main_loop: main_loop,
408480
}
@@ -424,12 +496,21 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
424496
.finish();
425497

426498
let server = Server::new(read, write, socket);
427-
server.serve(service).await;
428499

429-
log::trace!(
430-
"LSP thread exiting gracefully after connection closed ({:?}).",
431-
address
432-
);
500+
tokio::select! {
501+
_ = server.serve(service) => {
502+
log::trace!(
503+
"LSP thread exiting gracefully after connection closed ({:?}).",
504+
address
505+
);
506+
},
507+
_ = shutdown_rx.recv() => {
508+
log::trace!(
509+
"LSP thread exiting after receiving a shutdown request ({:?}).",
510+
address
511+
);
512+
}
513+
}
433514
})
434515
}
435516

0 commit comments

Comments
 (0)