@@ -60,7 +60,7 @@ fileprivate func diagnosticsEnabled(for document: DocumentURI) -> Bool {
60
60
61
61
public final class SwiftLanguageServer : ToolchainLanguageServer {
62
62
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`.
64
64
public let queue : DispatchQueue = DispatchQueue ( label: " swift-language-server-queue " , qos: . userInitiated)
65
65
66
66
let client : Connection
@@ -69,6 +69,10 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
69
69
70
70
private var state : LanguageServerState {
71
71
didSet {
72
+ if #available( OSX 10 . 12 , * ) {
73
+ // `state` must only be set from `queue`.
74
+ dispatchPrecondition ( condition: . onQueue( queue) )
75
+ }
72
76
for handler in stateChangeHandlers {
73
77
handler ( oldValue, state)
74
78
}
@@ -109,7 +113,9 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
109
113
}
110
114
111
115
public func addStateChangeHandler( handler: @escaping ( _ oldState: LanguageServerState , _ newState: LanguageServerState ) -> Void ) {
112
- stateChangeHandlers. append ( handler)
116
+ queue. async {
117
+ self . stateChangeHandlers. append ( handler)
118
+ }
113
119
}
114
120
115
121
/// Should be called on self.queue.
@@ -174,24 +180,28 @@ extension SwiftLanguageServer {
174
180
api. set_notification_handler { [ weak self] notification in
175
181
guard let self = self else { return }
176
182
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
+
195
205
// We don't have any open documents anymore after sourcekitd crashed.
196
206
// Reset the document manager to reflect that.
197
207
self . documentManager = DocumentManager ( )
@@ -259,7 +269,9 @@ extension SwiftLanguageServer {
259
269
260
270
func exit( _ notification: Notification < ExitNotification > ) {
261
271
api. shutdown ( )
262
- state = . shutDown
272
+ queue. async {
273
+ self . state = . shutDown
274
+ }
263
275
}
264
276
265
277
/// Tell sourcekitd to crash itself. For testing purposes only.
0 commit comments