Skip to content

Commit 1040621

Browse files
committed
Shutdown toolchain connections on exit
When using SourceKit-LSP in tests (or otherwise in a library), we do not want to leak the toolchain connections.
1 parent 5449a2a commit 1040621

File tree

6 files changed

+54
-29
lines changed

6 files changed

+54
-29
lines changed

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -104,14 +104,18 @@ public final class LocalConnection {
104104

105105
extension LocalConnection: Connection {
106106
public func send<Notification>(_ notification: Notification) where Notification: NotificationType {
107-
precondition(state == .started)
108-
handler!.handle(notification, from: ObjectIdentifier(self))
107+
handler?.handle(notification, from: ObjectIdentifier(self))
109108
}
110109

111110
public func send<Request>(_ request: Request, queue: DispatchQueue, reply: @escaping (LSPResult<Request.Response>) -> Void) -> RequestID where Request: RequestType {
112-
precondition(state == .started)
113111
let id = nextRequestID()
114-
handler!.handle(request, id: id, from: ObjectIdentifier(self)) { result in
112+
guard let handler = handler else {
113+
queue.async { reply(.failure(.cancelled)) }
114+
return id
115+
}
116+
117+
precondition(state == .started)
118+
handler.handle(request, id: id, from: ObjectIdentifier(self)) { result in
115119
queue.async {
116120
reply(result)
117121
}

Sources/SKTestSupport/TestServer.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import SourceKitLSP
1919
import class Foundation.Pipe
2020
import LSPTestSupport
2121

22-
public struct TestSourceKitServer {
22+
public final class TestSourceKitServer {
2323
public enum ConnectionKind {
2424
case local, jsonrpc
2525
}
@@ -40,6 +40,8 @@ public struct TestSourceKitServer {
4040
public let client: TestClient
4141
let connImpl: ConnectionImpl
4242

43+
public var hasShutdown: Bool = false
44+
4345
/// The server, if it is in the same process.
4446
public let server: SourceKitServer?
4547

@@ -98,15 +100,15 @@ public struct TestSourceKitServer {
98100
}
99101
}
100102

103+
deinit {
104+
close()
105+
}
106+
101107
func close() {
102-
switch connImpl {
103-
case .local(clientConnection: let cc, serverConnection: let sc):
104-
cc.close()
105-
sc.close()
106-
107-
case .jsonrpc(clientToServer: _, serverToClient: _, clientConnection: let cc, serverConnection: let sc):
108-
cc.close()
109-
sc.close()
108+
if !hasShutdown {
109+
hasShutdown = true
110+
_ = try! self.client.sendSync(ShutdownRequest())
111+
self.client.send(ExitNotification())
110112
}
111113
}
112114
}

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,23 @@ final class ClangLanguageServerShim: ToolchainLanguageServer {
2626

2727
let clangd: Connection
2828

29+
let client: LocalConnection
30+
2931
var capabilities: ServerCapabilities? = nil
3032

3133
let buildSystem: BuildSystem
3234

3335
let clang: AbsolutePath?
3436

3537
/// Creates a language server for the given client using the sourcekitd dylib at the specified path.
36-
public init(client: Connection, clangd: Connection, buildSystem: BuildSystem,
37-
clang: AbsolutePath?) throws {
38+
public init(
39+
client: LocalConnection,
40+
clangd: Connection,
41+
buildSystem: BuildSystem,
42+
clang: AbsolutePath?
43+
) throws {
3844
self.clangd = clangd
45+
self.client = client
3946
self.buildSystem = buildSystem
4047
self.clang = clang
4148
}
@@ -82,6 +89,13 @@ extension ClangLanguageServerShim {
8289
clangd.send(initialized)
8390
}
8491

92+
public func shutdown() {
93+
_ = clangd.send(ShutdownRequest(), queue: queue) { [weak self] _ in
94+
self?.clangd.send(ExitNotification())
95+
self?.client.close()
96+
}
97+
}
98+
8599
// MARK: - Text synchronization
86100

87101
public func openDocument(_ note: DidOpenTextDocumentNotification) {

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,10 @@ extension SourceKitServer {
520520

521521
func shutdown(_ request: Request<ShutdownRequest>) {
522522
_prepareForExit()
523+
for service in languageService.values {
524+
service.shutdown()
525+
}
526+
languageService = [:]
523527
request.reply(VoidResponse())
524528
}
525529

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -81,7 +81,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
8181
/// The server's request queue, used to serialize requests and responses to `sourcekitd`.
8282
public let queue: DispatchQueue = DispatchQueue(label: "swift-language-server-queue", qos: .userInitiated)
8383

84-
let client: Connection
84+
let client: LocalConnection
8585

8686
let sourcekitd: SourceKitD
8787

@@ -96,20 +96,23 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
9696

9797
var commandsByFile: [DocumentURI: SwiftCompileCommand] = [:]
9898

99-
let onExit: () -> Void
100-
10199
var keys: sourcekitd_keys { return sourcekitd.keys }
102100
var requests: sourcekitd_requests { return sourcekitd.requests }
103101
var values: sourcekitd_values { return sourcekitd.values }
104102

105-
/// Creates a language server for the given client using the sourcekitd dylib at the specified path.
106-
public init(client: Connection, sourcekitd: AbsolutePath, buildSystem: BuildSystem, clientCapabilities: ClientCapabilities, onExit: @escaping () -> Void = {}) throws {
103+
/// Creates a language server for the given client using the sourcekitd dylib at the specified
104+
/// path.
105+
public init(
106+
client: LocalConnection,
107+
sourcekitd: AbsolutePath,
108+
buildSystem: BuildSystem,
109+
clientCapabilities: ClientCapabilities
110+
) throws {
107111
self.client = client
108112
self.sourcekitd = try SourceKitDImpl.getOrCreate(dylibPath: sourcekitd)
109113
self.buildSystem = buildSystem
110114
self.clientCapabilities = clientCapabilities
111115
self.documentManager = DocumentManager()
112-
self.onExit = onExit
113116
}
114117

115118
/// Should be called on self.queue.
@@ -202,12 +205,9 @@ extension SwiftLanguageServer {
202205
// Nothing to do.
203206
}
204207

205-
func shutdown(_ request: Request<ShutdownRequest>) {
208+
public func shutdown() {
206209
sourcekitd.removeNotificationHandler(self)
207-
}
208-
209-
func exit(_ notification: Notification<ExitNotification>) {
210-
onExit()
210+
client.close()
211211
}
212212

213213
// MARK: - Build System Integration

Sources/SourceKitLSP/ToolchainLanguageServer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -21,6 +21,7 @@ public protocol ToolchainLanguageServer: AnyObject {
2121

2222
func initializeSync(_ initialize: InitializeRequest) throws -> InitializeResult
2323
func clientInitialized(_ initialized: InitializedNotification)
24+
func shutdown()
2425

2526
// MARK: - Text synchronization
2627

0 commit comments

Comments
 (0)