Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ToolchainLanguageServerCrashHandler to handle crashes #178

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/Csourcekitd/include/sourcekitd_functions.h
Expand Up @@ -69,6 +69,8 @@ typedef const char *(^sourcekitd_str_from_uid_handler_t)(sourcekitd_uid_t uid);
typedef struct {
void (*initialize)(void);
void (*shutdown)(void);
void (*set_interrupted_connection_handler)(
sourcekitd_interrupted_connection_handler_t handler);

sourcekitd_uid_t (*uid_get_from_cstr)(const char *string);
sourcekitd_uid_t (*uid_get_from_buf)(const char *buf, size_t length);
Expand Down
22 changes: 18 additions & 4 deletions Sources/SourceKit/SourceKitServer.swift
Expand Up @@ -191,7 +191,7 @@ public final class SourceKitServer: LanguageServer {

// Start a new service.
return orLog("failed to start language service", level: .error) {
guard let service = try SourceKit.languageService(for: toolchain, language, options: options, client: self) else {
guard let service = try SourceKit.languageService(for: toolchain, language, options: options, client: self, crashHandler: self) else {
return nil
}

Expand Down Expand Up @@ -258,6 +258,15 @@ extension SourceKitServer: BuildSystemDelegate {
}
}

// MARK: - Crash Handling

extension SourceKitServer: ToolchainLanguageServerCrashHandler {
public func handleCrash(_ languageService: ToolchainLanguageServer, _ debugInfo: String) {
// TODO: Chose to re-initialize the given languageService or not, log the crash, and
// possibly notify the user.
}
}

// MARK: - Request and notification handling

extension SourceKitServer {
Expand Down Expand Up @@ -702,17 +711,22 @@ public func languageService(
for toolchain: Toolchain,
_ language: Language,
options: SourceKitServer.Options,
client: MessageHandler) throws -> ToolchainLanguageServer?
client: MessageHandler,
crashHandler: ToolchainLanguageServerCrashHandler) throws -> ToolchainLanguageServer?
{
switch language {

case .c, .cpp, .objective_c, .objective_cpp:
guard toolchain.clangd != nil else { return nil }
return try makeJSONRPCClangServer(client: client, toolchain: toolchain, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clangdOptions: options.clangdOptions)
let service = try makeJSONRPCClangServer(client: client, toolchain: toolchain, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clangdOptions: options.clangdOptions)
service.crashHandler = crashHandler
return service

case .swift:
guard let sourcekitd = toolchain.sourcekitd else { return nil }
return try makeLocalSwiftServer(client: client, sourcekitd: sourcekitd, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clientCapabilities: (client as? SourceKitServer)?.workspace?.clientCapabilities)
let service = try makeLocalSwiftServer(client: client, sourcekitd: sourcekitd, buildSettings: (client as? SourceKitServer)?.workspace?.buildSettings, clientCapabilities: (client as? SourceKitServer)?.workspace?.clientCapabilities)
service.crashHandler = crashHandler
return service

default:
return nil
Expand Down
2 changes: 2 additions & 0 deletions Sources/SourceKit/ToolchainLanguageServer.swift
Expand Up @@ -18,6 +18,8 @@ public protocol ToolchainLanguageServer: AnyObject {

// MARK: Lifetime

var crashHandler: ToolchainLanguageServerCrashHandler? {get set}

func initializeSync(_ initialize: InitializeRequest) throws -> InitializeResult
func clientInitialized(_ initialized: InitializedNotification)

Expand Down
22 changes: 22 additions & 0 deletions Sources/SourceKit/ToolchainLanguageServerCrashHandler.swift
@@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation

public protocol ToolchainLanguageServerCrashHandler: AnyObject {
/// Called when the given `ToolchainLanguageServer` has crashed and needs to be reinitialized with information
/// such as the list of open documents.
///
/// The handler may or may not chose to re-open previously open documents and/or
/// stop sending any further requests to the given `ToolchainLanguageServer`.
func handleCrash(_ languageService: ToolchainLanguageServer, _ debugInfo: String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the idea for the debug info parameter? Right now I see it's always a fixed string - where would we get more information to pass here?

}
4 changes: 3 additions & 1 deletion Sources/SourceKit/clangd/ClangLanguageServer.swift
Expand Up @@ -19,10 +19,11 @@ import Foundation

/// A thin wrapper over a connection to a clangd server providing build setting handling.
final class ClangLanguageServerShim: ToolchainLanguageServer {

/// The server's request queue, used to serialize requests and responses to `clangd`.
public let queue: DispatchQueue = DispatchQueue(label: "clangd-language-server-queue", qos: .userInitiated)

public weak var crashHandler: ToolchainLanguageServerCrashHandler? = nil

let clangd: Connection

var capabilities: ServerCapabilities? = nil
Expand Down Expand Up @@ -215,6 +216,7 @@ func makeJSONRPCClangServer(
process.standardOutput = serverToClient
process.standardInput = clientToServer
process.terminationHandler = { process in
// TODO: Inform the crash handler and provide a way to restart clangd.
log("clangd exited: \(process.terminationReason) \(process.terminationStatus)")
connection.close()
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift
Expand Up @@ -23,6 +23,8 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
/// The server's request queue, used to serialize requests and responses to `sourcekitd`.
public let queue: DispatchQueue = DispatchQueue(label: "swift-language-server-queue", qos: .userInitiated)

public weak var crashHandler: ToolchainLanguageServerCrashHandler? = nil

let client: Connection

let sourcekitd: SwiftSourceKitFramework
Expand Down Expand Up @@ -129,6 +131,11 @@ extension SwiftLanguageServer {
}
}

api.set_interrupted_connection_handler { [weak self] in
guard let self = self else { return }
self.crashHandler?.handleCrash(self, "sourcekitd connection interrupted")
}

return InitializeResult(capabilities: ServerCapabilities(
textDocumentSync: TextDocumentSyncOptions(
openClose: true,
Expand Down Expand Up @@ -162,6 +169,7 @@ extension SwiftLanguageServer {

func shutdown(_ request: Request<Shutdown>) {
api.set_notification_handler(nil)
api.set_interrupted_connection_handler(nil)
}

func exit(_ notification: Notification<Exit>) {
Expand Down
1 change: 1 addition & 0 deletions Sources/SourceKit/sourcekitd/SwiftSourceKitFramework.swift
Expand Up @@ -69,6 +69,7 @@ final class SwiftSourceKitFramework {

api.initialize = try dlsym_required(dylib, symbol: "sourcekitd_initialize")
api.shutdown = try dlsym_required(dylib, symbol: "sourcekitd_shutdown")
api.set_interrupted_connection_handler = try dlsym_required(dylib, symbol: "sourcekitd_set_interrupted_connection_handler")
api.uid_get_from_cstr = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_from_cstr")
api.uid_get_from_buf = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_from_buf")
api.uid_get_length = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_length")
Expand Down