7
7
8
8
#![ allow( deprecated) ]
9
9
10
+ use std:: sync:: atomic:: Ordering ;
10
11
use std:: sync:: Arc ;
11
12
13
+ use amalthea:: comm:: ui_comm:: ShowMessageParams as UiShowMessageParams ;
14
+ use amalthea:: comm:: ui_comm:: UiFrontendEvent ;
12
15
use crossbeam:: channel:: Sender ;
13
16
use serde_json:: Value ;
14
17
use stdext:: result:: ResultOrLog ;
@@ -26,6 +29,7 @@ use tower_lsp::LanguageServer;
26
29
use tower_lsp:: LspService ;
27
30
use tower_lsp:: Server ;
28
31
32
+ use super :: main_loop:: LSP_HAS_CRASHED ;
29
33
use crate :: interface:: RMain ;
30
34
use crate :: lsp:: handlers:: VirtualDocumentParams ;
31
35
use crate :: lsp:: handlers:: VirtualDocumentResponse ;
@@ -44,24 +48,68 @@ use crate::lsp::statement_range::StatementRangeParams;
44
48
use crate :: lsp:: statement_range:: StatementRangeResponse ;
45
49
use crate :: r_task;
46
50
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
+
47
66
// Based on https://stackoverflow.com/a/69324393/1725177
48
67
macro_rules! cast_response {
49
- ( $target: expr, $pat: path) => { {
68
+ ( $self : expr , $ target: expr, $pat: path) => { {
50
69
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
+ ) ) ) ,
53
84
_ => panic!( "Unexpected variant while casting to {}" , stringify!( $pat) ) ,
54
85
}
55
86
} } ;
56
87
}
57
88
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
+
58
109
#[ derive( Debug ) ]
59
110
pub ( crate ) enum LspMessage {
60
111
Notification ( LspNotification ) ,
61
- Request (
62
- LspRequest ,
63
- TokioUnboundedSender < anyhow:: Result < LspResponse > > ,
64
- ) ,
112
+ Request ( LspRequest , TokioUnboundedSender < RequestResponse > ) ,
65
113
}
66
114
67
115
#[ derive( Debug ) ]
@@ -79,7 +127,6 @@ pub(crate) enum LspNotification {
79
127
#[ derive( Debug ) ]
80
128
pub ( crate ) enum LspRequest {
81
129
Initialize ( InitializeParams ) ,
82
- Shutdown ( ) ,
83
130
WorkspaceSymbol ( WorkspaceSymbolParams ) ,
84
131
DocumentSymbol ( DocumentSymbolParams ) ,
85
132
ExecuteCommand ( ExecuteCommandParams ) ,
@@ -101,7 +148,6 @@ pub(crate) enum LspRequest {
101
148
#[ derive( Debug ) ]
102
149
pub ( crate ) enum LspResponse {
103
150
Initialize ( InitializeResult ) ,
104
- Shutdown ( ( ) ) ,
105
151
WorkspaceSymbol ( Option < Vec < SymbolInformation > > ) ,
106
152
DocumentSymbol ( Option < DocumentSymbolResponse > ) ,
107
153
ExecuteCommand ( Option < Value > ) ,
@@ -122,6 +168,10 @@ pub(crate) enum LspResponse {
122
168
123
169
#[ derive( Debug ) ]
124
170
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
+
125
175
/// Channel for communication with the main loop.
126
176
events_tx : TokioUnboundedSender < Event > ,
127
177
@@ -131,9 +181,12 @@ struct Backend {
131
181
}
132
182
133
183
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 > ( ) ;
137
190
138
191
// Relay request to main loop
139
192
self . events_tx
@@ -156,6 +209,7 @@ impl Backend {
156
209
impl LanguageServer for Backend {
157
210
async fn initialize ( & self , params : InitializeParams ) -> Result < InitializeResult > {
158
211
cast_response ! (
212
+ self ,
159
213
self . request( LspRequest :: Initialize ( params) ) . await ,
160
214
LspResponse :: Initialize
161
215
)
@@ -166,10 +220,9 @@ impl LanguageServer for Backend {
166
220
}
167
221
168
222
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 ( ( ) )
173
226
}
174
227
175
228
async fn did_change_workspace_folders ( & self , params : DidChangeWorkspaceFoldersParams ) {
@@ -189,6 +242,7 @@ impl LanguageServer for Backend {
189
242
params : WorkspaceSymbolParams ,
190
243
) -> Result < Option < Vec < SymbolInformation > > > {
191
244
cast_response ! (
245
+ self ,
192
246
self . request( LspRequest :: WorkspaceSymbol ( params) ) . await ,
193
247
LspResponse :: WorkspaceSymbol
194
248
)
@@ -199,6 +253,7 @@ impl LanguageServer for Backend {
199
253
params : DocumentSymbolParams ,
200
254
) -> Result < Option < DocumentSymbolResponse > > {
201
255
cast_response ! (
256
+ self ,
202
257
self . request( LspRequest :: DocumentSymbol ( params) ) . await ,
203
258
LspResponse :: DocumentSymbol
204
259
)
@@ -209,6 +264,7 @@ impl LanguageServer for Backend {
209
264
params : ExecuteCommandParams ,
210
265
) -> jsonrpc:: Result < Option < Value > > {
211
266
cast_response ! (
267
+ self ,
212
268
self . request( LspRequest :: ExecuteCommand ( params) ) . await ,
213
269
LspResponse :: ExecuteCommand
214
270
)
@@ -232,27 +288,31 @@ impl LanguageServer for Backend {
232
288
233
289
async fn completion ( & self , params : CompletionParams ) -> Result < Option < CompletionResponse > > {
234
290
cast_response ! (
291
+ self ,
235
292
self . request( LspRequest :: Completion ( params) ) . await ,
236
293
LspResponse :: Completion
237
294
)
238
295
}
239
296
240
297
async fn completion_resolve ( & self , item : CompletionItem ) -> Result < CompletionItem > {
241
298
cast_response ! (
299
+ self ,
242
300
self . request( LspRequest :: CompletionResolve ( item) ) . await ,
243
301
LspResponse :: CompletionResolve
244
302
)
245
303
}
246
304
247
305
async fn hover ( & self , params : HoverParams ) -> Result < Option < Hover > > {
248
306
cast_response ! (
307
+ self ,
249
308
self . request( LspRequest :: Hover ( params) ) . await ,
250
309
LspResponse :: Hover
251
310
)
252
311
}
253
312
254
313
async fn signature_help ( & self , params : SignatureHelpParams ) -> Result < Option < SignatureHelp > > {
255
314
cast_response ! (
315
+ self ,
256
316
self . request( LspRequest :: SignatureHelp ( params) ) . await ,
257
317
LspResponse :: SignatureHelp
258
318
)
@@ -263,6 +323,7 @@ impl LanguageServer for Backend {
263
323
params : GotoDefinitionParams ,
264
324
) -> Result < Option < GotoDefinitionResponse > > {
265
325
cast_response ! (
326
+ self ,
266
327
self . request( LspRequest :: GotoDefinition ( params) ) . await ,
267
328
LspResponse :: GotoDefinition
268
329
)
@@ -273,6 +334,7 @@ impl LanguageServer for Backend {
273
334
params : GotoImplementationParams ,
274
335
) -> Result < Option < GotoImplementationResponse > > {
275
336
cast_response ! (
337
+ self ,
276
338
self . request( LspRequest :: GotoImplementation ( params) ) . await ,
277
339
LspResponse :: GotoImplementation
278
340
)
@@ -283,13 +345,15 @@ impl LanguageServer for Backend {
283
345
params : SelectionRangeParams ,
284
346
) -> Result < Option < Vec < SelectionRange > > > {
285
347
cast_response ! (
348
+ self ,
286
349
self . request( LspRequest :: SelectionRange ( params) ) . await ,
287
350
LspResponse :: SelectionRange
288
351
)
289
352
}
290
353
291
354
async fn references ( & self , params : ReferenceParams ) -> Result < Option < Vec < Location > > > {
292
355
cast_response ! (
356
+ self ,
293
357
self . request( LspRequest :: References ( params) ) . await ,
294
358
LspResponse :: References
295
359
)
@@ -300,6 +364,7 @@ impl LanguageServer for Backend {
300
364
params : DocumentOnTypeFormattingParams ,
301
365
) -> Result < Option < Vec < TextEdit > > > {
302
366
cast_response ! (
367
+ self ,
303
368
self . request( LspRequest :: OnTypeFormatting ( params) ) . await ,
304
369
LspResponse :: OnTypeFormatting
305
370
)
@@ -327,6 +392,7 @@ impl Backend {
327
392
params : StatementRangeParams ,
328
393
) -> jsonrpc:: Result < Option < StatementRangeResponse > > {
329
394
cast_response ! (
395
+ self ,
330
396
self . request( LspRequest :: StatementRange ( params) ) . await ,
331
397
LspResponse :: StatementRange
332
398
)
@@ -337,6 +403,7 @@ impl Backend {
337
403
params : HelpTopicParams ,
338
404
) -> jsonrpc:: Result < Option < HelpTopicResponse > > {
339
405
cast_response ! (
406
+ self ,
340
407
self . request( LspRequest :: HelpTopic ( params) ) . await ,
341
408
LspResponse :: HelpTopic
342
409
)
@@ -347,6 +414,7 @@ impl Backend {
347
414
params : VirtualDocumentParams ,
348
415
) -> tower_lsp:: jsonrpc:: Result < VirtualDocumentResponse > {
349
416
cast_response ! (
417
+ self ,
350
418
self . request( LspRequest :: VirtualDocument ( params) ) . await ,
351
419
LspResponse :: VirtualDocument
352
420
)
@@ -357,6 +425,7 @@ impl Backend {
357
425
params : InputBoundariesParams ,
358
426
) -> tower_lsp:: jsonrpc:: Result < InputBoundariesResponse > {
359
427
cast_response ! (
428
+ self ,
360
429
self . request( LspRequest :: InputBoundaries ( params) ) . await ,
361
430
LspResponse :: InputBoundaries
362
431
)
@@ -381,6 +450,8 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
381
450
log:: trace!( "Connected to LSP at '{}'" , address) ;
382
451
let ( read, write) = tokio:: io:: split ( stream) ;
383
452
453
+ let ( shutdown_tx, mut shutdown_rx) = tokio:: sync:: mpsc:: channel :: < ( ) > ( 1 ) ;
454
+
384
455
let init = |client : Client | {
385
456
let state = GlobalState :: new ( client) ;
386
457
let events_tx = state. events_tx ( ) ;
@@ -403,6 +474,7 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
403
474
} ) ;
404
475
405
476
Backend {
477
+ shutdown_tx,
406
478
events_tx,
407
479
_main_loop : main_loop,
408
480
}
@@ -424,12 +496,21 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
424
496
. finish ( ) ;
425
497
426
498
let server = Server :: new ( read, write, socket) ;
427
- server. serve ( service) . await ;
428
499
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
+ }
433
514
} )
434
515
}
435
516
0 commit comments