77
88#![ allow( deprecated) ]
99
10+ use std:: sync:: atomic:: Ordering ;
1011use std:: sync:: Arc ;
1112
13+ use amalthea:: comm:: ui_comm:: ShowMessageParams as UiShowMessageParams ;
14+ use amalthea:: comm:: ui_comm:: UiFrontendEvent ;
1215use crossbeam:: channel:: Sender ;
1316use serde_json:: Value ;
1417use stdext:: result:: ResultOrLog ;
@@ -26,6 +29,7 @@ use tower_lsp::LanguageServer;
2629use tower_lsp:: LspService ;
2730use tower_lsp:: Server ;
2831
32+ use super :: main_loop:: LSP_HAS_CRASHED ;
2933use crate :: interface:: RMain ;
3034use crate :: lsp:: handlers:: VirtualDocumentParams ;
3135use crate :: lsp:: handlers:: VirtualDocumentResponse ;
@@ -44,24 +48,68 @@ use crate::lsp::statement_range::StatementRangeParams;
4448use crate :: lsp:: statement_range:: StatementRangeResponse ;
4549use 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
4867macro_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 ) ]
59110pub ( 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 ) ]
80128pub ( 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 ) ]
102149pub ( 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 ) ]
124170struct 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
133183impl 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 {
156209impl 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