@@ -465,8 +465,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
465
465
self . filesDependenciesUpdatedDebouncer = Debouncer (
466
466
debounceDuration: . milliseconds( 500 ) ,
467
467
combineResults: { $0. union ( $1) } ,
468
- makeCall: {
469
- [ weak self] ( filesWithUpdatedDependencies) in
468
+ makeCall: { [ weak self] ( filesWithUpdatedDependencies) in
470
469
guard let self, let delegate = await self . delegate else {
471
470
logger. fault ( " Not calling filesDependenciesUpdated because no delegate exists in SwiftPMBuildServer " )
472
471
return
@@ -485,8 +484,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
485
484
self . filesBuildSettingsChangedDebouncer = Debouncer (
486
485
debounceDuration: . milliseconds( 20 ) ,
487
486
combineResults: { $0. union ( $1) } ,
488
- makeCall: {
489
- [ weak self] ( filesWithChangedBuildSettings) in
487
+ makeCall: { [ weak self] ( filesWithChangedBuildSettings) in
490
488
guard let self, let delegate = await self . delegate else {
491
489
logger. fault ( " Not calling fileBuildSettingsChanged because no delegate exists in SwiftPMBuildServer " )
492
490
return
@@ -653,30 +651,76 @@ package actor BuildServerManager: QueueBasedMessageHandler {
653
651
}
654
652
655
653
private func didChangeBuildTarget( notification: OnBuildTargetDidChangeNotification ) async {
656
- let updatedTargets : Set < BuildTargetIdentifier > ? =
654
+ let changedTargets : Set < BuildTargetIdentifier > ? =
657
655
if let changes = notification. changes {
658
656
Set ( changes. map ( \. target) )
659
657
} else {
660
658
nil
661
659
}
662
- self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
663
- guard let updatedTargets else {
664
- // All targets might have changed
665
- return true
660
+ await self . buildTargetsDidChange ( . didChangeBuildTargets( changedTargets: changedTargets) )
661
+ }
662
+
663
+ private enum BuildTargetsChange {
664
+ case didChangeBuildTargets( changedTargets: Set < BuildTargetIdentifier > ? )
665
+ case buildTargetsReceivedResultAfterTimeout(
666
+ request: WorkspaceBuildTargetsRequest ,
667
+ newResult: [ BuildTargetIdentifier : BuildTargetInfo ]
668
+ )
669
+ case sourceFilesReceivedResultAfterTimeout(
670
+ request: BuildTargetSourcesRequest ,
671
+ newResult: BuildTargetSourcesResponse
672
+ )
673
+ }
674
+
675
+ /// Update the cached state in `BuildServerManager` because new data was received from the BSP server.
676
+ ///
677
+ /// This handles a few seemingly unrelated reasons to ensure that we think about which caches to invalidate in the
678
+ /// other scenarios as well, when making changes in here.
679
+ private func buildTargetsDidChange( _ stateChange: BuildTargetsChange ) async {
680
+ let changedTargets : Set < BuildTargetIdentifier > ?
681
+
682
+ switch stateChange {
683
+ case . didChangeBuildTargets( let changedTargetsValue) :
684
+ changedTargets = changedTargetsValue
685
+ self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
686
+ guard let changedTargets else {
687
+ // All targets might have changed
688
+ return true
689
+ }
690
+ return changedTargets. contains ( cacheKey. target)
666
691
}
667
- return updatedTargets . contains ( cacheKey . target )
668
- }
669
- self . cachedBuildTargets . clearAll ( isolation : self )
670
- self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
671
- guard let updatedTargets else {
672
- // All targets might have changed
673
- return true
692
+ self . cachedBuildTargets . clearAll ( isolation : self )
693
+ self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
694
+ guard let changedTargets else {
695
+ // All targets might have changed
696
+ return true
697
+ }
698
+ return !changedTargets . intersection ( cacheKey . targets ) . isEmpty
674
699
}
675
- return !updatedTargets. intersection ( cacheKey. targets) . isEmpty
676
- }
677
- self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
678
-
679
- await delegate? . buildTargetsChanged ( notification. changes)
700
+ self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
701
+
702
+ case . buildTargetsReceivedResultAfterTimeout( let request, let newResult) :
703
+ changedTargets = nil
704
+
705
+ // Caches not invalidated:
706
+ // - cachedAdjustedSourceKitOptions: We would not have requested SourceKit options for targets that we didn't
707
+ // know about. Even if we did, the build server now telling us about the target should not change the options of
708
+ // the file within the target
709
+ // - cachedTargetSources: Similar to cachedAdjustedSourceKitOptions, we would not have requested sources for
710
+ // targets that we didn't know about and if we did, they wouldn't be affected
711
+ cachedSourceFilesAndDirectories. clearAll ( isolation: self )
712
+ self . cachedBuildTargets. set ( request, to: newResult)
713
+ case . sourceFilesReceivedResultAfterTimeout( let request, let newResult) :
714
+ changedTargets = Set ( request. targets)
715
+
716
+ // Caches not invalidated:
717
+ // - cachedAdjustedSourceKitOptions: Same as for buildTargetsReceivedResultAfterTimeout.
718
+ // - cachedBuildTargets: Getting a result for the source files in a target doesn't change anything about the
719
+ // target's existence.
720
+ self . cachedTargetSources. set ( request, to: newResult)
721
+ cachedSourceFilesAndDirectories. clearAll ( isolation: self )
722
+ }
723
+ await delegate? . buildTargetsChanged ( changedTargets)
680
724
await filesBuildSettingsChangedDebouncer. scheduleCall ( Set ( watchedFiles. keys) )
681
725
}
682
726
@@ -967,7 +1011,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
967
1011
if fallbackAfterTimeout {
968
1012
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
969
1013
return try await self . buildSettingsFromBuildServer ( for: document, in: target, language: language)
970
- } resultReceivedAfterTimeout: {
1014
+ } resultReceivedAfterTimeout: { _ in
971
1015
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
972
1016
}
973
1017
} else {
@@ -1025,7 +1069,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1025
1069
} else {
1026
1070
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
1027
1071
await self . canonicalTarget ( for: mainFile)
1028
- } resultReceivedAfterTimeout: {
1072
+ } resultReceivedAfterTimeout: { _ in
1029
1073
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
1030
1074
}
1031
1075
}
@@ -1179,28 +1223,39 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1179
1223
1180
1224
let request = WorkspaceBuildTargetsRequest ( )
1181
1225
let result = try await cachedBuildTargets. get ( request, isolation: self ) { request in
1182
- let buildTargets = try await buildServerAdapter. send ( request) . targets
1183
- let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1184
- var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1185
- result. reserveCapacity ( buildTargets. count)
1186
- for buildTarget in buildTargets {
1187
- guard result [ buildTarget. id] == nil else {
1188
- logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1189
- continue
1190
- }
1191
- let depth : Int
1192
- if let d = depths [ buildTarget. id] {
1193
- depth = d
1194
- } else {
1195
- logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1196
- depth = 0
1226
+ let result = try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1227
+ let buildTargets = try await buildServerAdapter. send ( request) . targets
1228
+ let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1229
+ var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1230
+ result. reserveCapacity ( buildTargets. count)
1231
+ for buildTarget in buildTargets {
1232
+ guard result [ buildTarget. id] == nil else {
1233
+ logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1234
+ continue
1235
+ }
1236
+ let depth : Int
1237
+ if let d = depths [ buildTarget. id] {
1238
+ depth = d
1239
+ } else {
1240
+ logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1241
+ depth = 0
1242
+ }
1243
+ result [ buildTarget. id] = BuildTargetInfo (
1244
+ target: buildTarget,
1245
+ depth: depth,
1246
+ dependents: dependents [ buildTarget. id] ?? [ ]
1247
+ )
1197
1248
}
1198
- result [ buildTarget . id ] = BuildTargetInfo (
1199
- target : buildTarget ,
1200
- depth : depth ,
1201
- dependents : dependents [ buildTarget . id ] ?? [ ]
1249
+ return result
1250
+ } resultReceivedAfterTimeout : { newResult in
1251
+ await self . buildTargetsDidChange (
1252
+ . buildTargetsReceivedResultAfterTimeout ( request : request , newResult : newResult )
1202
1253
)
1203
1254
}
1255
+ guard let result else {
1256
+ logger. error ( " Failed to get targets of workspace within timeout " )
1257
+ return [ : ]
1258
+ }
1204
1259
return result
1205
1260
}
1206
1261
return result
@@ -1233,7 +1288,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1233
1288
}
1234
1289
1235
1290
let response = try await cachedTargetSources. get ( request, isolation: self ) { request in
1236
- try await buildServerAdapter. send ( request)
1291
+ try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1292
+ return try await buildServerAdapter. send ( request)
1293
+ } resultReceivedAfterTimeout: { newResult in
1294
+ await self . buildTargetsDidChange ( . sourceFilesReceivedResultAfterTimeout( request: request, newResult: newResult) )
1295
+ } ?? BuildTargetSourcesResponse ( items: [ ] )
1237
1296
}
1238
1297
return response. items
1239
1298
}
0 commit comments