Skip to content

Commit 216a860

Browse files
committed
Don’t send messages to clangd after it has crashed but before it has restarted
1 parent 67bc93f commit 216a860

File tree

1 file changed

+85
-54
lines changed

1 file changed

+85
-54
lines changed

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 85 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,31 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
5050
/// to the editor.
5151
public let client: Connection
5252

53-
/// The connection to the clangd LSP. `nil` until `startClangdProcesss` has been called.
54-
var clangd: Connection!
53+
/// The connection to the clangd LSP.
54+
///
55+
/// `nil` until `startClangdProcesss` has been called.
56+
///
57+
/// - Important: Access `clangd` to ensure all documents have been reopened
58+
/// after clangd crashes.
59+
var unsafeClangd: Connection!
60+
61+
/// The connection to `clangd`.
62+
///
63+
/// After `clangd` crashes, this property doesn't return until `clangd` has
64+
/// been relaunched, documents have been reopened in `clangd` and `clangd`
65+
/// is ready to receive requests again.
66+
var clangd: Connection {
67+
get async {
68+
// If clangd has crashed, wait for it to restart before sending any
69+
// messages to it.
70+
_ = await clangdRestartTask?.value
71+
return unsafeClangd
72+
}
73+
}
74+
75+
/// If `clangd` is restared, the task to relaunch it and reopen all the
76+
/// documents within.
77+
var clangdRestartTask: Task<Void, Never>?
5578

5679
/// Capabilities of the clangd LSP, if received.
5780
var capabilities: ServerCapabilities? = nil
@@ -179,7 +202,7 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
179202
inFD: clangdToUs.fileHandleForReading,
180203
outFD: usToClangd.fileHandleForWriting
181204
)
182-
self.clangd = connectionToClangd
205+
self.unsafeClangd = connectionToClangd
183206

184207
connectionToClangd.start(receiveHandler: self) {
185208
// FIXME: keep the pipes alive until we close the connection. This
@@ -235,8 +258,8 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
235258
}
236259
self.lastClangdRestart = Date()
237260

238-
Task {
239-
try await Task.sleep(nanoseconds: UInt64(restartDelay) * 1_000_000_000)
261+
clangdRestartTask = Task {
262+
try? await Task.sleep(nanoseconds: UInt64(restartDelay) * 1_000_000_000)
240263
self.clangRestartScheduled = false
241264
do {
242265
try self.startClangdProcesss()
@@ -250,7 +273,8 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
250273
} catch {
251274
log("Failed to restart clangd after a crash.", level: .error)
252275
}
253-
}
276+
clangdRestartTask = nil
277+
}
254278
}
255279

256280
/// Handler for notifications received **from** clangd, ie. **clangd** is
@@ -260,7 +284,7 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
260284
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
261285
if let publishDiags = params as? PublishDiagnosticsNotification {
262286
await self.publishDiagnostics(Notification(publishDiags, clientID: clientID))
263-
} else if clientID == ObjectIdentifier(self.clangd) {
287+
} else if clientID == ObjectIdentifier(self.unsafeClangd) {
264288
self.client.send(params)
265289
}
266290
}
@@ -279,7 +303,7 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
279303
reply(result)
280304
})
281305

282-
if request.clientID == ObjectIdentifier(self.clangd) {
306+
if request.clientID == ObjectIdentifier(self.unsafeClangd) {
283307
self.forwardRequest(request, to: self.client)
284308
} else {
285309
request.reply(.failure(ResponseError.methodNotFound(R.method)))
@@ -373,16 +397,23 @@ extension ClangLanguageServerShim {
373397
// Store the initialize request so we can replay it in case clangd crashes
374398
self.initializeRequest = initialize
375399

376-
let result = try clangd.sendSync(initialize)
400+
// This must not access `clangd` because it is called from the
401+
// `clangdRestartTask` and accessing `clangd` is blocked until that task
402+
// completes.
403+
let result = try unsafeClangd.sendSync(initialize)
377404
self.capabilities = result.capabilities
378405
return result
379406
}
380407

381408
public func clientInitialized(_ initialized: InitializedNotification) {
382-
clangd.send(initialized)
409+
// This must not access `clangd` because it is called from the
410+
// `clangdRestartTask` and accessing `clangd` is blocked until that task
411+
// completes.
412+
unsafeClangd.send(initialized)
383413
}
384414

385415
public func shutdown() async {
416+
let clangd = await clangd
386417
await withCheckedContinuation { continuation in
387418
_ = clangd.send(ShutdownRequest(), queue: self.clangdCommunicationQueue) { [weak self] _ in
388419
guard let self else { return }
@@ -405,24 +436,24 @@ extension ClangLanguageServerShim {
405436
// sending the open notification, so that the initial diagnostics already
406437
// have build settings.
407438
await documentUpdatedBuildSettings(note.textDocument.uri, change: .removedOrUnavailable)
408-
clangd.send(note)
439+
await clangd.send(note)
409440
}
410441

411-
public func closeDocument(_ note: DidCloseTextDocumentNotification) {
442+
public func closeDocument(_ note: DidCloseTextDocumentNotification) async {
412443
openDocuments[note.textDocument.uri] = nil
413-
clangd.send(note)
444+
await clangd.send(note)
414445
}
415446

416-
public func changeDocument(_ note: DidChangeTextDocumentNotification) {
417-
clangd.send(note)
447+
public func changeDocument(_ note: DidChangeTextDocumentNotification) async {
448+
await clangd.send(note)
418449
}
419450

420451
public func willSaveDocument(_ note: WillSaveTextDocumentNotification) {
421452

422453
}
423454

424-
public func didSaveDocument(_ note: DidSaveTextDocumentNotification) {
425-
clangd.send(note)
455+
public func didSaveDocument(_ note: DidSaveTextDocumentNotification) async {
456+
await clangd.send(note)
426457
}
427458

428459
// MARK: - Build System Integration
@@ -448,101 +479,101 @@ extension ClangLanguageServerShim {
448479
let note = DidChangeConfigurationNotification(settings: .clangd(
449480
ClangWorkspaceSettings(
450481
compilationDatabaseChanges: [pathString: compileCommand])))
451-
clangd.send(note)
482+
await clangd.send(note)
452483
}
453484
}
454485

455-
public func documentDependenciesUpdated(_ uri: DocumentURI) {
486+
public func documentDependenciesUpdated(_ uri: DocumentURI) async {
456487
// In order to tell clangd to reload an AST, we send it an empty `didChangeTextDocument`
457488
// with `forceRebuild` set in case any missing header files have been added.
458489
// This works well for us as the moment since clangd ignores the document version.
459490
let note = DidChangeTextDocumentNotification(
460491
textDocument: VersionedTextDocumentIdentifier(uri, version: 0),
461492
contentChanges: [],
462493
forceRebuild: true)
463-
clangd.send(note)
494+
await clangd.send(note)
464495
}
465496

466497
// MARK: - Text Document
467498

468499

469500
/// Returns true if the `ToolchainLanguageServer` will take ownership of the request.
470-
public func definition(_ req: Request<DefinitionRequest>) -> Bool {
501+
public func definition(_ req: Request<DefinitionRequest>) async -> Bool {
471502
// We handle it to provide jump-to-header support for #import/#include.
472-
self.forwardRequest(req, to: self.clangd)
503+
self.forwardRequest(req, to: await self.clangd)
473504
return true
474505
}
475506

476507
/// Returns true if the `ToolchainLanguageServer` will take ownership of the request.
477-
public func declaration(_ req: Request<DeclarationRequest>) -> Bool {
508+
public func declaration(_ req: Request<DeclarationRequest>) async -> Bool {
478509
// We handle it to provide jump-to-header support for #import/#include.
479-
forwardRequest(req, to: clangd)
510+
forwardRequest(req, to: await clangd)
480511
return true
481512
}
482513

483-
func completion(_ req: Request<CompletionRequest>) {
484-
forwardRequest(req, to: clangd)
514+
func completion(_ req: Request<CompletionRequest>) async {
515+
forwardRequest(req, to: await clangd)
485516
}
486517

487-
func hover(_ req: Request<HoverRequest>) {
488-
forwardRequest(req, to: clangd)
518+
func hover(_ req: Request<HoverRequest>) async {
519+
forwardRequest(req, to: await clangd)
489520
}
490521

491-
func symbolInfo(_ req: Request<SymbolInfoRequest>) {
492-
forwardRequest(req, to: clangd)
522+
func symbolInfo(_ req: Request<SymbolInfoRequest>) async {
523+
forwardRequest(req, to: await clangd)
493524
}
494525

495-
func documentSymbolHighlight(_ req: Request<DocumentHighlightRequest>) {
496-
forwardRequest(req, to: clangd)
526+
func documentSymbolHighlight(_ req: Request<DocumentHighlightRequest>) async {
527+
forwardRequest(req, to: await clangd)
497528
}
498529

499-
func documentSymbol(_ req: Request<DocumentSymbolRequest>) {
500-
forwardRequest(req, to: clangd)
530+
func documentSymbol(_ req: Request<DocumentSymbolRequest>) async {
531+
forwardRequest(req, to: await clangd)
501532
}
502533

503-
func documentColor(_ req: Request<DocumentColorRequest>) {
534+
func documentColor(_ req: Request<DocumentColorRequest>) async {
504535
if self.capabilities?.colorProvider?.isSupported == true {
505-
forwardRequest(req, to: clangd)
536+
forwardRequest(req, to: await clangd)
506537
} else {
507538
req.reply(.success([]))
508539
}
509540
}
510541

511-
func documentSemanticTokens(_ req: Request<DocumentSemanticTokensRequest>) {
512-
forwardRequest(req, to: clangd)
542+
func documentSemanticTokens(_ req: Request<DocumentSemanticTokensRequest>) async {
543+
forwardRequest(req, to: await clangd)
513544
}
514545

515-
func documentSemanticTokensDelta(_ req: Request<DocumentSemanticTokensDeltaRequest>) {
516-
forwardRequest(req, to: clangd)
546+
func documentSemanticTokensDelta(_ req: Request<DocumentSemanticTokensDeltaRequest>) async {
547+
forwardRequest(req, to: await clangd)
517548
}
518549

519-
func documentSemanticTokensRange(_ req: Request<DocumentSemanticTokensRangeRequest>) {
520-
forwardRequest(req, to: clangd)
550+
func documentSemanticTokensRange(_ req: Request<DocumentSemanticTokensRangeRequest>) async {
551+
forwardRequest(req, to: await clangd)
521552
}
522553

523-
func colorPresentation(_ req: Request<ColorPresentationRequest>) {
554+
func colorPresentation(_ req: Request<ColorPresentationRequest>) async {
524555
if self.capabilities?.colorProvider?.isSupported == true {
525-
forwardRequest(req, to: clangd)
556+
forwardRequest(req, to: await clangd)
526557
} else {
527558
req.reply(.success([]))
528559
}
529560
}
530561

531-
func codeAction(_ req: Request<CodeActionRequest>) {
532-
forwardRequest(req, to: clangd)
562+
func codeAction(_ req: Request<CodeActionRequest>) async {
563+
forwardRequest(req, to: await clangd)
533564
}
534565

535-
func inlayHint(_ req: Request<InlayHintRequest>) {
536-
forwardRequest(req, to: clangd)
566+
func inlayHint(_ req: Request<InlayHintRequest>) async {
567+
forwardRequest(req, to: await clangd)
537568
}
538569

539-
func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>) {
540-
forwardRequest(req, to: clangd)
570+
func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>) async {
571+
forwardRequest(req, to: await clangd)
541572
}
542573

543-
func foldingRange(_ req: Request<FoldingRangeRequest>) {
574+
func foldingRange(_ req: Request<FoldingRangeRequest>) async {
544575
if self.capabilities?.foldingRangeProvider?.isSupported == true {
545-
forwardRequest(req, to: clangd)
576+
forwardRequest(req, to: await clangd)
546577
} else {
547578
req.reply(.success(nil))
548579
}
@@ -554,8 +585,8 @@ extension ClangLanguageServerShim {
554585

555586
// MARK: - Other
556587

557-
func executeCommand(_ req: Request<ExecuteCommandRequest>) {
558-
forwardRequest(req, to: clangd)
588+
func executeCommand(_ req: Request<ExecuteCommandRequest>) async {
589+
forwardRequest(req, to: await clangd)
559590
}
560591
}
561592

0 commit comments

Comments
 (0)