Skip to content

Commit 2381e0d

Browse files
authored
Merge pull request #1701 from ahoppen/bsp-crash-recovery
Restart a BSP server after it has crashed
2 parents f54b143 + 2b3d733 commit 2381e0d

File tree

7 files changed

+233
-57
lines changed

7 files changed

+233
-57
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,36 @@ fileprivate extension InitializeBuildResponse {
109109
/// the `BuiltInBuildSystem` protocol. For external (aka. out-of-process, aka. BSP servers) build systems, this means
110110
/// that we need to manage the external build system's lifetime.
111111
private enum BuildSystemAdapter {
112-
case builtIn(BuiltInBuildSystemAdapter)
112+
case builtIn(BuiltInBuildSystemAdapter, connectionToBuildSystem: any Connection)
113113
case external(ExternalBuildSystemAdapter)
114+
115+
/// Send a notification to the build server.
116+
func send(_ notification: some NotificationType) async {
117+
switch self {
118+
case .builtIn(_, let connectionToBuildSystem):
119+
connectionToBuildSystem.send(notification)
120+
case .external(let external):
121+
await external.send(notification)
122+
}
123+
}
124+
125+
/// Send a request to the build server.
126+
func send<Request: RequestType>(_ request: Request) async throws -> Request.Response {
127+
switch self {
128+
case .builtIn(_, let connectionToBuildSystem):
129+
return try await connectionToBuildSystem.send(request)
130+
case .external(let external):
131+
return try await external.send(request)
132+
}
133+
}
114134
}
115135

116136
private extension BuildSystemKind {
117137
private static func createBuiltInBuildSystemAdapter(
118138
projectRoot: AbsolutePath,
119139
messagesToSourceKitLSPHandler: any MessageHandler,
120140
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
121-
) async -> (buildSystemAdapter: BuildSystemAdapter, connectionToBuildSystem: any Connection)? {
141+
) async -> BuildSystemAdapter? {
122142
let connectionToSourceKitLSP = LocalConnection(receiverName: "BuildSystemManager")
123143
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
124144

@@ -136,7 +156,7 @@ private extension BuildSystemKind {
136156
)
137157
let connectionToBuildSystem = LocalConnection(receiverName: "Build system")
138158
connectionToBuildSystem.start(handler: buildSystemAdapter)
139-
return (.builtIn(buildSystemAdapter), connectionToBuildSystem)
159+
return .builtIn(buildSystemAdapter, connectionToBuildSystem: connectionToBuildSystem)
140160
}
141161

142162
/// Create a `BuildSystemAdapter` that manages a build system of this kind and return a connection that can be used
@@ -146,7 +166,7 @@ private extension BuildSystemKind {
146166
options: SourceKitLSPOptions,
147167
buildSystemTestHooks testHooks: BuildSystemTestHooks,
148168
messagesToSourceKitLSPHandler: any MessageHandler
149-
) async -> (buildSystemAdapter: BuildSystemAdapter, connectionToBuildSystem: any Connection)? {
169+
) async -> BuildSystemAdapter? {
150170
switch self {
151171
case .buildServer(projectRoot: let projectRoot):
152172
let buildSystem = await orLog("Creating external build system") {
@@ -160,7 +180,7 @@ private extension BuildSystemKind {
160180
return nil
161181
}
162182
logger.log("Created external build server at \(projectRoot.pathString)")
163-
return (.external(buildSystem), buildSystem.connectionToBuildServer)
183+
return .external(buildSystem)
164184
case .compilationDatabase(projectRoot: let projectRoot):
165185
return await Self.createBuiltInBuildSystemAdapter(
166186
projectRoot: projectRoot,
@@ -217,32 +237,26 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
217237
/// get `fileBuildSettingsChanged` and `filesDependenciesUpdated` callbacks.
218238
private var watchedFiles: [DocumentURI: (mainFile: DocumentURI, language: Language)] = [:]
219239

220-
/// The connection through which the `BuildSystemManager` can send requests to the build system.
221-
///
222-
/// Access to this property should generally go through the non-underscored version, which waits until the build
223-
/// server is initialized so that no messages can be sent to the BSP server before initialization finishes.
224-
private var _connectionToBuildSystem: Connection?
240+
/// The build system adapter that is used to answer build system queries.
241+
private var buildSystemAdapter: BuildSystemAdapter?
225242

226-
/// The connection to the build system. Will not yield the connection until the build server is initialized,
227-
/// preventing accidental sending of messages to the build server before it is initialized.
228-
private var connectionToBuildSystem: Connection? {
243+
/// The build system adapter after initialization finishes. When sending messages to the BSP server, this should be
244+
/// preferred over `buildSystemAdapter` because no messages must be sent to the build server before initialization
245+
/// finishes.
246+
private var buildSystemAdapterAfterInitialized: BuildSystemAdapter? {
229247
get async {
230-
// Wait until initialization finishes.
231248
_ = await initializeResult.value
232-
return _connectionToBuildSystem
249+
return buildSystemAdapter
233250
}
234251
}
235252

236-
/// The build system adapter that is used to answer build system queries.
237-
private var buildSystemAdapter: BuildSystemAdapter?
238-
239253
/// If the underlying build system is a `TestBuildSystem`, return it. Otherwise, `nil`
240254
///
241255
/// - Important: For testing purposes only.
242256
package var testBuildSystem: TestBuildSystem? {
243257
get async {
244258
switch buildSystemAdapter {
245-
case .builtIn(let builtInBuildSystemAdapter): return await builtInBuildSystemAdapter.testBuildSystem
259+
case .builtIn(let builtInBuildSystemAdapter, _): return await builtInBuildSystemAdapter.testBuildSystem
246260
case .external: return nil
247261
case nil: return nil
248262
}
@@ -327,14 +341,12 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
327341
self.toolchainRegistry = toolchainRegistry
328342
self.options = options
329343
self.projectRoot = buildSystemKind?.projectRoot
330-
let buildSystemAdapterAndConnection = await buildSystemKind?.createBuildSystemAdapter(
344+
self.buildSystemAdapter = await buildSystemKind?.createBuildSystemAdapter(
331345
toolchainRegistry: toolchainRegistry,
332346
options: options,
333347
buildSystemTestHooks: buildSystemTestHooks,
334348
messagesToSourceKitLSPHandler: WeakMessageHandler(self)
335349
)
336-
self.buildSystemAdapter = buildSystemAdapterAndConnection?.buildSystemAdapter
337-
self._connectionToBuildSystem = buildSystemAdapterAndConnection?.connectionToBuildSystem
338350

339351
// The debounce duration of 500ms was chosen arbitrarily without any measurements.
340352
self.filesDependenciesUpdatedDebouncer = Debouncer(
@@ -355,15 +367,15 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
355367
// TODO: Forward file watch patterns from this initialize request to the client
356368
// (https://github.com/swiftlang/sourcekit-lsp/issues/1671)
357369
initializeResult = Task { () -> InitializeBuildResponse? in
358-
guard let _connectionToBuildSystem else {
370+
guard let buildSystemAdapter else {
359371
return nil
360372
}
361373
guard let buildSystemKind else {
362374
logger.fault("If we have a connectionToBuildSystem, we must have had a buildSystemKind")
363375
return nil
364376
}
365377
let initializeResponse = await orLog("Initializing build system") {
366-
try await _connectionToBuildSystem.send(
378+
try await buildSystemAdapter.send(
367379
InitializeBuildRequest(
368380
displayName: "SourceKit-LSP",
369381
version: "",
@@ -389,27 +401,26 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
389401
underlyingBuildSystem: legacyBuildServer,
390402
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP
391403
)
392-
self.buildSystemAdapter = .builtIn(adapter)
393404
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server")
394405
connectionToBuildSystem.start(handler: adapter)
395-
self._connectionToBuildSystem = connectionToBuildSystem
406+
self.buildSystemAdapter = .builtIn(adapter, connectionToBuildSystem: connectionToBuildSystem)
396407
}
397-
_connectionToBuildSystem.send(OnBuildInitializedNotification())
408+
await buildSystemAdapter.send(OnBuildInitializedNotification())
398409
return initializeResponse
399410
}
400411
}
401412

402413
deinit {
403414
// Shut down the build server before closing the connection to it
404-
Task { [connectionToBuildSystem = _connectionToBuildSystem, initializeResult] in
405-
guard let connectionToBuildSystem else {
415+
Task { [buildSystemAdapter, initializeResult] in
416+
guard let buildSystemAdapter else {
406417
return
407418
}
408419
// We are accessing the raw connection to the build server, so we need to ensure that it has been initialized here
409420
_ = await initializeResult?.value
410421
await orLog("Sending shutdown request to build server") {
411-
_ = try await connectionToBuildSystem.send(BuildShutdownRequest())
412-
connectionToBuildSystem.send(OnBuildExitNotification())
422+
_ = try await buildSystemAdapter.send(BuildShutdownRequest())
423+
await buildSystemAdapter.send(OnBuildExitNotification())
413424
}
414425
}
415426
}
@@ -655,7 +666,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
655666
in target: BuildTargetIdentifier,
656667
language: Language
657668
) async throws -> FileBuildSettings? {
658-
guard let connectionToBuildSystem = await connectionToBuildSystem else {
669+
guard let buildSystemAdapter = await buildSystemAdapterAfterInitialized else {
659670
return nil
660671
}
661672
let request = TextDocumentSourceKitOptionsRequest(
@@ -670,7 +681,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
670681
// very quickly from `settings(for:language:)`.
671682
// https://github.com/apple/sourcekit-lsp/issues/1181
672683
let response = try await cachedSourceKitOptions.get(request) { request in
673-
try await connectionToBuildSystem.send(request)
684+
try await buildSystemAdapter.send(request)
674685
}
675686
guard let response else {
676687
return nil
@@ -713,7 +724,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
713724
else {
714725
return nil
715726
}
716-
if await connectionToBuildSystem == nil {
727+
if buildSystemAdapter == nil {
717728
// If there is no build system and we only have the fallback build system, we will never get real build settings.
718729
// Consider the build settings non-fallback.
719730
settings.isFallback = false
@@ -768,7 +779,9 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
768779

769780
package func waitForUpToDateBuildGraph() async {
770781
await orLog("Waiting for build system updates") {
771-
let _: VoidResponse? = try await connectionToBuildSystem?.send(WorkspaceWaitForBuildSystemUpdatesRequest())
782+
let _: VoidResponse? = try await buildSystemAdapterAfterInitialized?.send(
783+
WorkspaceWaitForBuildSystemUpdatesRequest()
784+
)
772785
}
773786
// Handle any messages the build system might have sent us while updating.
774787
await self.messageHandlingQueue.async(metadata: .stateChange) {}.valuePropagatingCancellation
@@ -836,7 +849,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
836849
}
837850

838851
package func prepare(targets: Set<BuildTargetIdentifier>) async throws {
839-
let _: VoidResponse? = try await connectionToBuildSystem?.send(
852+
let _: VoidResponse? = try await buildSystemAdapterAfterInitialized?.send(
840853
BuildTargetPrepareRequest(targets: targets.sorted { $0.uri.stringValue < $1.uri.stringValue })
841854
)
842855
await orLog("Calling fileDependenciesUpdated") {
@@ -855,13 +868,13 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
855868
}
856869

857870
private func buildTargets() async throws -> [BuildTargetIdentifier: BuildTargetInfo] {
858-
guard let connectionToBuildSystem = await connectionToBuildSystem else {
871+
guard let buildSystemAdapter = await buildSystemAdapterAfterInitialized else {
859872
return [:]
860873
}
861874

862875
let request = WorkspaceBuildTargetsRequest()
863876
let result = try await cachedBuildTargets.get(request) { request in
864-
let buildTargets = try await connectionToBuildSystem.send(request).targets
877+
let buildTargets = try await buildSystemAdapter.send(request).targets
865878
let (depths, dependents) = await self.targetDepthsAndDependents(for: buildTargets)
866879
var result: [BuildTargetIdentifier: BuildTargetInfo] = [:]
867880
result.reserveCapacity(buildTargets.count)
@@ -898,7 +911,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
898911
}
899912

900913
package func sourceFiles(in targets: Set<BuildTargetIdentifier>) async throws -> [SourcesItem] {
901-
guard let connectionToBuildSystem = await connectionToBuildSystem, !targets.isEmpty else {
914+
guard let buildSystemAdapter = await buildSystemAdapterAfterInitialized, !targets.isEmpty else {
902915
return []
903916
}
904917

@@ -916,7 +929,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
916929

917930
let request = BuildTargetSourcesRequest(targets: targets.sorted { $0.uri.stringValue < $1.uri.stringValue })
918931
let response = try await cachedTargetSources.get(request) { request in
919-
try await connectionToBuildSystem.send(request)
932+
try await buildSystemAdapter.send(request)
920933
}
921934
return response.items
922935
}
@@ -1041,8 +1054,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
10411054
// MARK: Informing BuildSystemManager about changes
10421055

10431056
package func filesDidChange(_ events: [FileEvent]) async {
1044-
if let connectionToBuildSystem = await connectionToBuildSystem {
1045-
connectionToBuildSystem.send(OnWatchedFilesDidChangeNotification(changes: events))
1057+
if let buildSystemAdapter = await buildSystemAdapterAfterInitialized {
1058+
await buildSystemAdapter.send(OnWatchedFilesDidChangeNotification(changes: events))
10461059
}
10471060

10481061
var targetsWithUpdatedDependencies: Set<BuildTargetIdentifier> = []

0 commit comments

Comments
 (0)