Skip to content

Commit 2b0cbfd

Browse files
committed
Guard access to state by queue in Clang/SwiftLanguageServer
1 parent 7ec52e3 commit 2b0cbfd

File tree

2 files changed

+48
-26
lines changed

2 files changed

+48
-26
lines changed

Sources/SourceKit/clangd/ClangLanguageServer.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import TSCBasic
2121
/// A thin wrapper over a connection to a clangd server providing build setting handling.
2222
final class ClangLanguageServerShim: ToolchainLanguageServer {
2323

24-
/// The server's request queue, used to serialize requests and responses to `clangd`.
24+
/// The server's request queue, used to protect shared access to mutable state and to serialize requests and responses to `clangd`.
2525
public let queue: DispatchQueue = DispatchQueue(label: "clangd-language-server-queue", qos: .userInitiated)
2626

2727
/// The connection to `clangd`. `nil` before `initialize` has been called.
@@ -39,6 +39,10 @@ final class ClangLanguageServerShim: ToolchainLanguageServer {
3939

4040
private var state: LanguageServerState {
4141
didSet {
42+
if #available(OSX 10.12, *) {
43+
// `state` must only be set from `queue`.
44+
dispatchPrecondition(condition: .onQueue(queue))
45+
}
4246
for handler in stateChangeHandlers {
4347
handler(oldValue, state)
4448
}
@@ -109,8 +113,10 @@ final class ClangLanguageServerShim: ToolchainLanguageServer {
109113
connection.close()
110114
if process.terminationStatus != 0 {
111115
if let self = self {
112-
self.state = .connectionInterrupted
113-
self.restartClangd()
116+
self.queue.async {
117+
self.state = .connectionInterrupted
118+
self.restartClangd()
119+
}
114120
}
115121
}
116122
}
@@ -153,15 +159,19 @@ final class ClangLanguageServerShim: ToolchainLanguageServer {
153159
_ = try self.initializeSync(initializeRequest)
154160
self.clientInitialized(InitializedNotification())
155161
self.reopenDocuments(self)
156-
self.state = .connected
162+
self.queue.async {
163+
self.state = .connected
164+
}
157165
} catch {
158166
log("Failed to restart clangd after a crash.", level: .error)
159167
}
160168
}
161169
}
162170

163171
func addStateChangeHandler(handler: @escaping (LanguageServerState, LanguageServerState) -> Void) {
164-
stateChangeHandlers.append(handler)
172+
queue.async {
173+
self.stateChangeHandlers.append(handler)
174+
}
165175
}
166176

167177

Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fileprivate func diagnosticsEnabled(for document: DocumentURI) -> Bool {
6060

6161
public final class SwiftLanguageServer: ToolchainLanguageServer {
6262

63-
/// The server's request queue, used to serialize requests and responses to `sourcekitd`.
63+
/// The server's request queue, used to protect shared access to mutable state and to serialize requests and responses to `sourcekitd`.
6464
public let queue: DispatchQueue = DispatchQueue(label: "swift-language-server-queue", qos: .userInitiated)
6565

6666
let client: Connection
@@ -69,6 +69,10 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
6969

7070
private var state: LanguageServerState {
7171
didSet {
72+
if #available(OSX 10.12, *) {
73+
// `state` must only be set from `queue`.
74+
dispatchPrecondition(condition: .onQueue(queue))
75+
}
7276
for handler in stateChangeHandlers {
7377
handler(oldValue, state)
7478
}
@@ -109,7 +113,9 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
109113
}
110114

111115
public func addStateChangeHandler(handler: @escaping (_ oldState: LanguageServerState, _ newState: LanguageServerState) -> Void) {
112-
stateChangeHandlers.append(handler)
116+
queue.async {
117+
self.stateChangeHandlers.append(handler)
118+
}
113119
}
114120

115121
/// Should be called on self.queue.
@@ -174,24 +180,28 @@ extension SwiftLanguageServer {
174180
api.set_notification_handler { [weak self] notification in
175181
guard let self = self else { return }
176182
let notification = SKResponse(notification, sourcekitd: self.sourcekitd)
177-
178-
if notification.value?[self.keys.notification] == self.values.notification_sema_enabled {
179-
self.state = .connected
180-
}
181-
182-
if self.state == .connectionInterrupted {
183-
// If we get a notification while we are restoring the connection, it means that the server has restarted.
184-
// We still need to wait for semantic functionality to come back up.
185-
self.state = .semanticFunctionalityDisabled
186-
187-
// Ask our parent to re-open all of our documents.
188-
self.reopenDocuments(self)
189-
}
190-
191-
if let error = notification.error, error.code == .connectionInterrupted {
192-
self.state = .connectionInterrupted
193-
194-
self.queue.async {
183+
184+
// Check if we need to update our `state` based on the contents of the notification.
185+
// Execute the entire code block on `queue` because we need to switch to `queue` anyway to
186+
// check `state` in the second `if`. Moving `queue.async` up ensures we only need to switch
187+
// queues once and makes the code inside easier to read.
188+
self.queue.async {
189+
if notification.value?[self.keys.notification] == self.values.notification_sema_enabled {
190+
self.state = .connected
191+
}
192+
193+
if self.state == .connectionInterrupted {
194+
// If we get a notification while we are restoring the connection, it means that the server has restarted.
195+
// We still need to wait for semantic functionality to come back up.
196+
self.state = .semanticFunctionalityDisabled
197+
198+
// Ask our parent to re-open all of our documents.
199+
self.reopenDocuments(self)
200+
}
201+
202+
if let error = notification.error, error.code == .connectionInterrupted {
203+
self.state = .connectionInterrupted
204+
195205
// We don't have any open documents anymore after sourcekitd crashed.
196206
// Reset the document manager to reflect that.
197207
self.documentManager = DocumentManager()
@@ -259,7 +269,9 @@ extension SwiftLanguageServer {
259269

260270
func exit(_ notification: Notification<ExitNotification>) {
261271
api.shutdown()
262-
state = .shutDown
272+
queue.async {
273+
self.state = .shutDown
274+
}
263275
}
264276

265277
/// Tell sourcekitd to crash itself. For testing purposes only.

0 commit comments

Comments
 (0)