Skip to content

Commit e8d0a0d

Browse files
committed
Make SourceKitD build with strict concurrency enabled
1 parent bad10cd commit e8d0a0d

File tree

8 files changed

+44
-34
lines changed

8 files changed

+44
-34
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ let package = Package(
270270
"SKSupport",
271271
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
272272
],
273-
exclude: ["CMakeLists.txt"]
273+
exclude: ["CMakeLists.txt", "sourcekitd_uids.swift.gyb"],
274+
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
274275
),
275276

276277
.testTarget(

Sources/SKSupport/AsyncQueue.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ private class PendingTasks<TaskMetadata: Sendable>: @unchecked Sendable {
8484
}
8585

8686
/// A queue that allows the execution of asynchronous blocks of code.
87-
public final class AsyncQueue<TaskMetadata: DependencyTracker> {
88-
private var pendingTasks: PendingTasks<TaskMetadata> = PendingTasks()
87+
public final class AsyncQueue<TaskMetadata: DependencyTracker>: Sendable {
88+
private let pendingTasks: PendingTasks<TaskMetadata> = PendingTasks()
8989

9090
public init() {}
9191

Sources/SourceKitD/DynamicallyLoadedSourceKitD.swift

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ import SKSupport
1616

1717
import struct TSCBasic.AbsolutePath
1818

19+
extension DLHandle: @unchecked @retroactive Sendable {}
20+
extension sourcekitd_api_keys: @unchecked Sendable {}
21+
extension sourcekitd_api_requests: @unchecked Sendable {}
22+
extension sourcekitd_api_values: @unchecked Sendable {}
23+
1924
/// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler
2025
/// multiplexing.
2126
///
2227
/// Users of this class should not call the api functions `initialize`, `shutdown`, or
2328
/// `set_notification_handler`, which are global state managed internally by this class.
24-
public final class DynamicallyLoadedSourceKitD: SourceKitD {
29+
public actor DynamicallyLoadedSourceKitD: SourceKitD {
2530

2631
/// The path to the sourcekitd dylib.
2732
public let path: AbsolutePath
@@ -41,11 +46,10 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
4146
/// Convenience for accessing known keys.
4247
public let values: sourcekitd_api_values
4348

44-
/// Lock protecting private state.
45-
let lock: NSLock = NSLock()
49+
private nonisolated let notificationHandlingQueue = AsyncQueue<Serial>()
4650

4751
/// List of notification handlers that will be called for each notification.
48-
private var _notificationHandlers: [WeakSKDNotificationHandler] = []
52+
private var notificationHandlers: [WeakSKDNotificationHandler] = []
4953

5054
public static func getOrCreate(dylibPath: AbsolutePath) async throws -> SourceKitD {
5155
try await SourceKitDRegistry.shared
@@ -67,11 +71,13 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
6771
self.api.initialize()
6872
self.api.set_notification_handler { [weak self] rawResponse in
6973
guard let self, let rawResponse else { return }
70-
let handlers = self.lock.withLock { self._notificationHandlers.compactMap(\.value) }
71-
7274
let response = SKDResponse(rawResponse, sourcekitd: self)
73-
for handler in handlers {
74-
handler.notification(response)
75+
self.notificationHandlingQueue.async {
76+
let handlers = await self.notificationHandlers.compactMap(\.value)
77+
78+
for handler in handlers {
79+
handler.notification(response)
80+
}
7581
}
7682
}
7783
}
@@ -85,20 +91,16 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
8591

8692
/// Adds a new notification handler (referenced weakly).
8793
public func addNotificationHandler(_ handler: SKDNotificationHandler) {
88-
lock.withLock {
89-
_notificationHandlers.removeAll(where: { $0.value == nil })
90-
_notificationHandlers.append(.init(handler))
91-
}
94+
notificationHandlers.removeAll(where: { $0.value == nil })
95+
notificationHandlers.append(.init(handler))
9296
}
9397

9498
/// Removes a previously registered notification handler.
9599
public func removeNotificationHandler(_ handler: SKDNotificationHandler) {
96-
lock.withLock {
97-
_notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler })
98-
}
100+
notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler })
99101
}
100102

101-
public func log(request: SKDRequestDictionary) {
103+
public nonisolated func log(request: SKDRequestDictionary) {
102104
logger.info(
103105
"""
104106
Sending sourcekitd request:
@@ -107,7 +109,7 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
107109
)
108110
}
109111

110-
public func log(response: SKDResponse) {
112+
public nonisolated func log(response: SKDResponse) {
111113
logger.log(
112114
level: (response.error == nil || response.error == .requestCancelled) ? .debug : .error,
113115
"""
@@ -117,7 +119,7 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
117119
)
118120
}
119121

120-
public func log(crashedRequest req: SKDRequestDictionary, fileContents: String?) {
122+
public nonisolated func log(crashedRequest req: SKDRequestDictionary, fileContents: String?) {
121123
let log = """
122124
Request:
123125
\(req.description)
@@ -138,7 +140,7 @@ public final class DynamicallyLoadedSourceKitD: SourceKitD {
138140

139141
}
140142

141-
struct WeakSKDNotificationHandler {
143+
struct WeakSKDNotificationHandler: Sendable {
142144
weak private(set) var value: SKDNotificationHandler?
143145
init(_ value: SKDNotificationHandler) {
144146
self.value = value

Sources/SourceKitD/SKDResponse.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@ import Musl
2121
import CRT
2222
#endif
2323

24-
public final class SKDResponse {
25-
public let response: sourcekitd_api_response_t
24+
public final class SKDResponse: Sendable {
25+
/// - Note: `sourcekitd_api_response_t` is a typedef for `void *`, so we can't mark it as sendable. But we know that
26+
/// it stays alive until we deinit this `SKDResponse`. We also require that only a single `SKDResponse` may manage
27+
/// the `sourcekitd_api_response_t`, so raw response cannot be modified or disposed in any other way.
28+
private nonisolated(unsafe) let response: sourcekitd_api_response_t
2629
public let sourcekitd: SourceKitD
2730

31+
/// Creates a new `SKDResponse` that exclusively manages the raw `sourcekitd_api_response_t`.
32+
///
33+
/// - Important: When this `SKDResponse` object gets destroyed, it will dispose the response. It is thus illegal to
34+
/// have two `SDKResponse` objects managing the same `sourcekitd_api_response_t`.
2835
public init(_ response: sourcekitd_api_response_t, sourcekitd: SourceKitD) {
2936
self.response = response
3037
self.sourcekitd = sourcekitd

Sources/SourceKitD/SourceKitD.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import SKSupport
2323
///
2424
/// *Implementors* are expected to handle initialization and shutdown, e.g. during `init` and
2525
/// `deinit` or by wrapping an existing sourcekitd session that outlives this object.
26-
public protocol SourceKitD: AnyObject {
26+
public protocol SourceKitD: AnyObject, Sendable {
2727
/// The sourcekitd API functions.
2828
var api: sourcekitd_api_functions_t { get }
2929

@@ -37,10 +37,10 @@ public protocol SourceKitD: AnyObject {
3737
var values: sourcekitd_api_values { get }
3838

3939
/// Adds a new notification handler, which will be weakly referenced.
40-
func addNotificationHandler(_ handler: SKDNotificationHandler)
40+
func addNotificationHandler(_ handler: SKDNotificationHandler) async
4141

4242
/// Removes a previously registered notification handler.
43-
func removeNotificationHandler(_ handler: SKDNotificationHandler)
43+
func removeNotificationHandler(_ handler: SKDNotificationHandler) async
4444

4545
/// Log the given request.
4646
///
@@ -115,6 +115,6 @@ extension SourceKitD {
115115
}
116116

117117
/// A sourcekitd notification handler in a class to allow it to be uniquely referenced.
118-
public protocol SKDNotificationHandler: AnyObject {
118+
public protocol SKDNotificationHandler: AnyObject, Sendable {
119119
func notification(_: SKDResponse) -> Void
120120
}

Sources/SourceKitD/SourceKitDRegistry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public actor SourceKitDRegistry {
4949
/// Returns the existing SourceKitD for the given path, or creates it and registers it.
5050
public func getOrAdd(
5151
_ key: AbsolutePath,
52-
create: () throws -> SourceKitD
52+
create: @Sendable () throws -> SourceKitD
5353
) rethrows -> SourceKitD {
5454
if let existing = active[key] {
5555
return existing

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ public actor SwiftLanguageServer: ToolchainLanguageServer {
232232

233233
extension SwiftLanguageServer {
234234

235-
public func initialize(_ initialize: InitializeRequest) throws -> InitializeResult {
236-
sourcekitd.addNotificationHandler(self)
235+
public func initialize(_ initialize: InitializeRequest) async throws -> InitializeResult {
236+
await sourcekitd.addNotificationHandler(self)
237237

238238
return InitializeResult(
239239
capabilities: ServerCapabilities(
@@ -287,7 +287,7 @@ extension SwiftLanguageServer {
287287
}
288288

289289
public func shutdown() async {
290-
self.sourcekitd.removeNotificationHandler(self)
290+
await self.sourcekitd.removeNotificationHandler(self)
291291
}
292292

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

Tests/SourceKitDTests/SourceKitDTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final class SourceKitDTests: XCTestCase {
5151
defer {
5252
_fixLifetime(handler1)
5353
}
54-
sourcekitd.addNotificationHandler(handler1)
54+
await sourcekitd.addNotificationHandler(handler1)
5555

5656
let expectation2 = expectation(description: "handler 2")
5757
let handler2 = ClosureNotificationHandler { response in
@@ -63,7 +63,7 @@ final class SourceKitDTests: XCTestCase {
6363
defer {
6464
_fixLifetime(handler2)
6565
}
66-
sourcekitd.addNotificationHandler(handler2)
66+
await sourcekitd.addNotificationHandler(handler2)
6767

6868
let args = SKDRequestArray(sourcekitd: sourcekitd)
6969
if case .darwin? = Platform.current,

0 commit comments

Comments
 (0)