Skip to content

Commit b44bd41

Browse files
committed
Restart sourcekitd and clangd after they have crashed
1 parent babf190 commit b44bd41

File tree

18 files changed

+669
-101
lines changed

18 files changed

+669
-101
lines changed

Documentation/Development.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ Location(ws.testLoc("aaa:call"))
175175
Position(ws.testLoc("aaa:call"))
176176
```
177177

178+
### Long tests
179+
180+
Tests that run longer than approx. 1 second are only executed if the the `SOURCEKIT_LSP_ENABLE_LONG_TESTS` environment variable is set to `YES` or `1`. This, in particular, includes the crash recovery tests.
181+
178182
## Tibs
179183

180184
We use Tibs, the "Test Index Build System" from the IndexStoreDB project to provide build system support for test projects, including getting compiler arguments and building an index.

Sources/LanguageServerProtocol/Error.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public struct ErrorCode: RawRepresentable, Codable, Hashable {
3535

3636
// LSP
3737
public static let cancelled: ErrorCode = ErrorCode(rawValue: -32800)
38+
39+
// sourcekitd
40+
public static let connectionInterrupted = ErrorCode(rawValue: -13000)
3841
}
3942

4043
/// An error response represented by a code and message.
@@ -63,6 +66,10 @@ extension ResponseError {
6366
public static func unknown(_ message: String) -> ResponseError {
6467
return ResponseError(code: .unknownErrorCode, message: message)
6568
}
69+
70+
public static func connectionInterrupted(_ message: String) -> ResponseError {
71+
return ResponseError(code: .connectionInterrupted, message: message)
72+
}
6673
}
6774

6875
/// An error during message decoding.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
public var longTestsEnabled: Bool {
16+
if let value = ProcessInfo.processInfo.environment["SOURCEKIT_LSP_ENABLE_LONG_TESTS"] {
17+
return value == "1" || value == "YES"
18+
}
19+
return false
20+
}

Sources/SKTestSupport/SKTibsTestWorkspace.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,5 @@ extension TibsToolchain {
216216

217217
extension TestLocation {
218218
public var docIdentifier: TextDocumentIdentifier { TextDocumentIdentifier(url) }
219+
public var docUri: DocumentURI { DocumentURI(url) }
219220
}

Sources/SourceKit/SourceKitServer.swift

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,36 @@ public final class SourceKitServer: LanguageServer {
181181
return nil
182182
}
183183

184+
func reopenDocuments(for languageService: ToolchainLanguageServer) {
185+
queue.async {
186+
guard let workspace = self.workspace else {
187+
return
188+
}
189+
190+
for documentUri in workspace.documentManager.openDocuments where workspace.documentService[documentUri] === languageService {
191+
guard let snapshot = workspace.documentManager.latestSnapshot(documentUri) else {
192+
// The document has been closed since we retrieved its URI. We don't care about it anymore.
193+
continue
194+
}
195+
196+
do {
197+
// Close the document in the document manager so we can reopen it. If the document has been
198+
// closed since, the try will fail and we won't try to reopen it.
199+
try workspace.documentManager.close(documentUri)
200+
let textDocument = TextDocumentItem(uri: documentUri,
201+
language: snapshot.document.language,
202+
version: snapshot.version,
203+
text: snapshot.text)
204+
// Re-open the document but don't register for build system notifications again since
205+
// the document is still open in the build system server from before the sourcektid crash.
206+
self.openDocument(DidOpenTextDocumentNotification(textDocument: textDocument), workspace: workspace, registerForBuildSystemNotifications: false)
207+
} catch {
208+
// The document was no longer open. Ignore it
209+
}
210+
}
211+
}
212+
}
213+
184214
func languageService(for toolchain: Toolchain, _ language: Language) -> ToolchainLanguageServer? {
185215
let key = LanguageServiceKey(toolchain: toolchain.identifier, language: language)
186216
if let service = languageService[key] {
@@ -189,7 +219,7 @@ public final class SourceKitServer: LanguageServer {
189219

190220
// Start a new service.
191221
return orLog("failed to start language service", level: .error) {
192-
guard let service = try SourceKit.languageService(for: toolchain, language, options: options, client: self) else {
222+
guard let service = try SourceKit.languageService(for: toolchain, language, options: options, client: self, reopenDocuments: reopenDocuments) else {
193223
return nil
194224
}
195225

@@ -216,7 +246,8 @@ public final class SourceKitServer: LanguageServer {
216246
}
217247
}
218248

219-
func languageService(for uri: DocumentURI, _ language: Language, in workspace: Workspace) -> ToolchainLanguageServer? {
249+
/// **Public for testing purposes only**
250+
public func languageService(for uri: DocumentURI, _ language: Language, in workspace: Workspace) -> ToolchainLanguageServer? {
220251
if let service = workspace.documentService[uri] {
221252
return service
222253
}
@@ -424,14 +455,21 @@ extension SourceKitServer {
424455
// MARK: - Text synchronization
425456

426457
func openDocument(_ note: Notification<DidOpenTextDocumentNotification>, workspace: Workspace) {
427-
workspace.documentManager.open(note.params)
458+
openDocument(note.params, workspace: workspace)
459+
}
460+
461+
private func openDocument(_ note: DidOpenTextDocumentNotification, workspace: Workspace, registerForBuildSystemNotifications: Bool = true) {
462+
workspace.documentManager.open(note)
428463

429-
let textDocument = note.params.textDocument
430-
workspace.buildSettings.registerForChangeNotifications(
431-
for: textDocument.uri, language: textDocument.language)
464+
let textDocument = note.textDocument
465+
466+
if registerForBuildSystemNotifications {
467+
workspace.buildSettings.registerForChangeNotifications(
468+
for: textDocument.uri, language: textDocument.language)
469+
}
432470

433471
if let service = languageService(for: textDocument.uri, textDocument.language, in: workspace) {
434-
service.openDocument(note.params)
472+
service.openDocument(note)
435473
}
436474
}
437475

@@ -771,17 +809,18 @@ public func languageService(
771809
for toolchain: Toolchain,
772810
_ language: Language,
773811
options: SourceKitServer.Options,
774-
client: MessageHandler) throws -> ToolchainLanguageServer?
812+
client: MessageHandler,
813+
reopenDocuments: @escaping (ToolchainLanguageServer) -> Void) throws -> ToolchainLanguageServer?
775814
{
776815
switch language {
777816

778817
case .c, .cpp, .objective_c, .objective_cpp:
779818
guard toolchain.clangd != nil else { return nil }
780-
return try makeJSONRPCClangServer(client: client, toolchain: toolchain, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clangdOptions: options.clangdOptions)
819+
return try makeJSONRPCClangServer(client: client, toolchain: toolchain, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clangdOptions: options.clangdOptions, reopenDocuments: reopenDocuments)
781820

782821
case .swift:
783822
guard let sourcekitd = toolchain.sourcekitd else { return nil }
784-
return try makeLocalSwiftServer(client: client, sourcekitd: sourcekitd, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clientCapabilities: (client as? SourceKitServer)?.workspace?.clientCapabilities)
823+
return try makeLocalSwiftServer(client: client, sourcekitd: sourcekitd, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clientCapabilities: (client as? SourceKitServer)?.workspace?.clientCapabilities, reopenDocuments: reopenDocuments)
785824

786825
default:
787826
return nil

Sources/SourceKit/ToolchainLanguageServer.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
import Foundation
1414
import LanguageServerProtocol
1515

16+
/// The state of a `ToolchainLanguageServer`
17+
public enum LanguageServerState {
18+
/// The language server is running with semantic functionality enabled
19+
case connected
20+
/// The language server server has crashed and we are waiting for it to relaunch
21+
case connectionInterrupted
22+
/// The language server has relaunched but semantic functionality is currently disabled
23+
case semanticFunctionalityDisabled
24+
/// The langauge server has been shut down gracefully
25+
case shutDown
26+
}
27+
1628
/// A `LanguageServer` that exists within the context of the current process.
1729
public protocol ToolchainLanguageServer: AnyObject {
1830

@@ -21,6 +33,9 @@ public protocol ToolchainLanguageServer: AnyObject {
2133
func initializeSync(_ initialize: InitializeRequest) throws -> InitializeResult
2234
func clientInitialized(_ initialized: InitializedNotification)
2335

36+
/// Add a handler that is called whenever the state of the language server changes.
37+
func addStateChangeHandler(handler: @escaping (_ oldState: LanguageServerState, _ newState: LanguageServerState) -> Void)
38+
2439
// MARK: - Text synchronization
2540

2641
func openDocument(_ note: DidOpenTextDocumentNotification)

0 commit comments

Comments
 (0)