@@ -482,8 +482,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
482
482
self . filesDependenciesUpdatedDebouncer = Debouncer (
483
483
debounceDuration: . milliseconds( 500 ) ,
484
484
combineResults: { $0. union ( $1) } ,
485
- makeCall: {
486
- [ weak self] ( filesWithUpdatedDependencies) in
485
+ makeCall: { [ weak self] ( filesWithUpdatedDependencies) in
487
486
guard let self, let delegate = await self . delegate else {
488
487
logger. fault ( " Not calling filesDependenciesUpdated because no delegate exists in SwiftPMBuildServer " )
489
488
return
@@ -502,8 +501,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
502
501
self . filesBuildSettingsChangedDebouncer = Debouncer (
503
502
debounceDuration: . milliseconds( 20 ) ,
504
503
combineResults: { $0. union ( $1) } ,
505
- makeCall: {
506
- [ weak self] ( filesWithChangedBuildSettings) in
504
+ makeCall: { [ weak self] ( filesWithChangedBuildSettings) in
507
505
guard let self, let delegate = await self . delegate else {
508
506
logger. fault ( " Not calling fileBuildSettingsChanged because no delegate exists in SwiftPMBuildServer " )
509
507
return
@@ -670,31 +668,76 @@ package actor BuildServerManager: QueueBasedMessageHandler {
670
668
}
671
669
672
670
private func didChangeBuildTarget( notification: OnBuildTargetDidChangeNotification ) async {
673
- let updatedTargets : Set < BuildTargetIdentifier > ? =
671
+ let changedTargets : Set < BuildTargetIdentifier > ? =
674
672
if let changes = notification. changes {
675
673
Set ( changes. map ( \. target) )
676
674
} else {
677
675
nil
678
676
}
679
- self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
680
- guard let updatedTargets else {
681
- // All targets might have changed
682
- return true
677
+ await self . buildTargetsDidChange ( . didChangeBuildTargets( changedTargets: changedTargets) )
678
+ }
679
+
680
+ private enum BuildTargetsChange {
681
+ case didChangeBuildTargets( changedTargets: Set < BuildTargetIdentifier > ? )
682
+ case buildTargetsReceivedResultAfterTimeout(
683
+ request: WorkspaceBuildTargetsRequest ,
684
+ newResult: [ BuildTargetIdentifier : BuildTargetInfo ]
685
+ )
686
+ case sourceFilesReceivedResultAfterTimeout(
687
+ request: BuildTargetSourcesRequest ,
688
+ newResult: BuildTargetSourcesResponse
689
+ )
690
+ }
691
+
692
+ /// Update the cached state in `BuildServerManager` because new data was received from the BSP server.
693
+ ///
694
+ /// This handles a few seemingly unrelated reasons to ensure that we think about which caches to invalidate in the
695
+ /// other scenarios as well, when making changes in here.
696
+ private func buildTargetsDidChange( _ stateChange: BuildTargetsChange ) async {
697
+ let changedTargets : Set < BuildTargetIdentifier > ?
698
+
699
+ switch stateChange {
700
+ case . didChangeBuildTargets( let changedTargetsValue) :
701
+ changedTargets = changedTargetsValue
702
+ self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
703
+ guard let changedTargets else {
704
+ // All targets might have changed
705
+ return true
706
+ }
707
+ return changedTargets. contains ( cacheKey. target)
683
708
}
684
- return updatedTargets . contains ( cacheKey . target )
685
- }
686
- self . cachedBuildTargets . clearAll ( isolation : self )
687
- self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
688
- guard let updatedTargets else {
689
- // All targets might have changed
690
- return true
709
+ self . cachedBuildTargets . clearAll ( isolation : self )
710
+ self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
711
+ guard let changedTargets else {
712
+ // All targets might have changed
713
+ return true
714
+ }
715
+ return !changedTargets . intersection ( cacheKey . targets ) . isEmpty
691
716
}
692
- return !updatedTargets. intersection ( cacheKey. targets) . isEmpty
693
- }
717
+ case . buildTargetsReceivedResultAfterTimeout( let request, let newResult) :
718
+ changedTargets = nil
719
+
720
+ // Caches not invalidated:
721
+ // - cachedAdjustedSourceKitOptions: We would not have requested SourceKit options for targets that we didn't
722
+ // know about. Even if we did, the build server now telling us about the target should not change the options of
723
+ // the file within the target
724
+ // - cachedTargetSources: Similar to cachedAdjustedSourceKitOptions, we would not have requested sources for
725
+ // targets that we didn't know about and if we did, they wouldn't be affected
726
+ self . cachedBuildTargets. set ( request, to: newResult)
727
+ case . sourceFilesReceivedResultAfterTimeout( let request, let newResult) :
728
+ changedTargets = Set ( request. targets)
729
+
730
+ // Caches not invalidated:
731
+ // - cachedAdjustedSourceKitOptions: Same as for buildTargetsReceivedResultAfterTimeout.
732
+ // - cachedBuildTargets: Getting a result for the source files in a target doesn't change anything about the
733
+ // target's existence.
734
+ self . cachedTargetSources. set ( request, to: newResult)
735
+ }
736
+ // Clear caches that capture global state and are affected by all changes
694
737
self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
695
738
self . scheduleRecomputeCopyFileMap ( )
696
739
697
- await delegate? . buildTargetsChanged ( notification . changes )
740
+ await delegate? . buildTargetsChanged ( changedTargets )
698
741
await filesBuildSettingsChangedDebouncer. scheduleCall ( Set ( watchedFiles. keys) )
699
742
}
700
743
@@ -896,6 +939,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
896
939
previousUpdateTask? . cancel ( )
897
940
await orLog ( " Re-computing copy file map " ) {
898
941
let sourceFilesAndDirectories = try await self . sourceFilesAndDirectories ( )
942
+ try Task . checkCancellation ( )
899
943
var copiedFileMap : [ DocumentURI : DocumentURI ] = [ : ]
900
944
for (file, fileInfo) in sourceFilesAndDirectories. files {
901
945
for copyDestination in fileInfo. copyDestinations {
@@ -1022,7 +1066,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1022
1066
if fallbackAfterTimeout {
1023
1067
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
1024
1068
return try await self . buildSettingsFromBuildServer ( for: document, in: target, language: language)
1025
- } resultReceivedAfterTimeout: {
1069
+ } resultReceivedAfterTimeout: { _ in
1026
1070
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
1027
1071
}
1028
1072
} else {
@@ -1080,7 +1124,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1080
1124
} else {
1081
1125
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
1082
1126
await self . canonicalTarget ( for: mainFile)
1083
- } resultReceivedAfterTimeout: {
1127
+ } resultReceivedAfterTimeout: { _ in
1084
1128
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
1085
1129
}
1086
1130
}
@@ -1234,28 +1278,39 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1234
1278
1235
1279
let request = WorkspaceBuildTargetsRequest ( )
1236
1280
let result = try await cachedBuildTargets. get ( request, isolation: self ) { request in
1237
- let buildTargets = try await buildServerAdapter. send ( request) . targets
1238
- let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1239
- var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1240
- result. reserveCapacity ( buildTargets. count)
1241
- for buildTarget in buildTargets {
1242
- guard result [ buildTarget. id] == nil else {
1243
- logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1244
- continue
1245
- }
1246
- let depth : Int
1247
- if let d = depths [ buildTarget. id] {
1248
- depth = d
1249
- } else {
1250
- logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1251
- depth = 0
1281
+ let result = try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1282
+ let buildTargets = try await buildServerAdapter. send ( request) . targets
1283
+ let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1284
+ var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1285
+ result. reserveCapacity ( buildTargets. count)
1286
+ for buildTarget in buildTargets {
1287
+ guard result [ buildTarget. id] == nil else {
1288
+ logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1289
+ continue
1290
+ }
1291
+ let depth : Int
1292
+ if let d = depths [ buildTarget. id] {
1293
+ depth = d
1294
+ } else {
1295
+ logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1296
+ depth = 0
1297
+ }
1298
+ result [ buildTarget. id] = BuildTargetInfo (
1299
+ target: buildTarget,
1300
+ depth: depth,
1301
+ dependents: dependents [ buildTarget. id] ?? [ ]
1302
+ )
1252
1303
}
1253
- result [ buildTarget . id ] = BuildTargetInfo (
1254
- target : buildTarget ,
1255
- depth : depth ,
1256
- dependents : dependents [ buildTarget . id ] ?? [ ]
1304
+ return result
1305
+ } resultReceivedAfterTimeout : { newResult in
1306
+ await self . buildTargetsDidChange (
1307
+ . buildTargetsReceivedResultAfterTimeout ( request : request , newResult : newResult )
1257
1308
)
1258
1309
}
1310
+ guard let result else {
1311
+ logger. error ( " Failed to get targets of workspace within timeout " )
1312
+ return [ : ]
1313
+ }
1259
1314
return result
1260
1315
}
1261
1316
return result
@@ -1288,7 +1343,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1288
1343
}
1289
1344
1290
1345
let response = try await cachedTargetSources. get ( request, isolation: self ) { request in
1291
- try await buildServerAdapter. send ( request)
1346
+ try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1347
+ return try await buildServerAdapter. send ( request)
1348
+ } resultReceivedAfterTimeout: { newResult in
1349
+ await self . buildTargetsDidChange ( . sourceFilesReceivedResultAfterTimeout( request: request, newResult: newResult) )
1350
+ } ?? BuildTargetSourcesResponse ( items: [ ] )
1292
1351
}
1293
1352
return response. items
1294
1353
}
0 commit comments