@@ -482,8 +482,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
482482 self . filesDependenciesUpdatedDebouncer = Debouncer (
483483 debounceDuration: . milliseconds( 500 ) ,
484484 combineResults: { $0. union ( $1) } ,
485- makeCall: {
486- [ weak self] ( filesWithUpdatedDependencies) in
485+ makeCall: { [ weak self] ( filesWithUpdatedDependencies) in
487486 guard let self, let delegate = await self . delegate else {
488487 logger. fault ( " Not calling filesDependenciesUpdated because no delegate exists in SwiftPMBuildServer " )
489488 return
@@ -502,8 +501,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
502501 self . filesBuildSettingsChangedDebouncer = Debouncer (
503502 debounceDuration: . milliseconds( 20 ) ,
504503 combineResults: { $0. union ( $1) } ,
505- makeCall: {
506- [ weak self] ( filesWithChangedBuildSettings) in
504+ makeCall: { [ weak self] ( filesWithChangedBuildSettings) in
507505 guard let self, let delegate = await self . delegate else {
508506 logger. fault ( " Not calling fileBuildSettingsChanged because no delegate exists in SwiftPMBuildServer " )
509507 return
@@ -670,31 +668,76 @@ package actor BuildServerManager: QueueBasedMessageHandler {
670668 }
671669
672670 private func didChangeBuildTarget( notification: OnBuildTargetDidChangeNotification ) async {
673- let updatedTargets : Set < BuildTargetIdentifier > ? =
671+ let changedTargets : Set < BuildTargetIdentifier > ? =
674672 if let changes = notification. changes {
675673 Set ( changes. map ( \. target) )
676674 } else {
677675 nil
678676 }
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)
683708 }
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
691716 }
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
694737 self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
695738 self . scheduleRecomputeCopyFileMap ( )
696739
697- await delegate? . buildTargetsChanged ( notification . changes )
740+ await delegate? . buildTargetsChanged ( changedTargets )
698741 await filesBuildSettingsChangedDebouncer. scheduleCall ( Set ( watchedFiles. keys) )
699742 }
700743
@@ -896,6 +939,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
896939 previousUpdateTask? . cancel ( )
897940 await orLog ( " Re-computing copy file map " ) {
898941 let sourceFilesAndDirectories = try await self . sourceFilesAndDirectories ( )
942+ try Task . checkCancellation ( )
899943 var copiedFileMap : [ DocumentURI : DocumentURI ] = [ : ]
900944 for (file, fileInfo) in sourceFilesAndDirectories. files {
901945 for copyDestination in fileInfo. copyDestinations {
@@ -1022,7 +1066,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
10221066 if fallbackAfterTimeout {
10231067 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
10241068 return try await self . buildSettingsFromBuildServer ( for: document, in: target, language: language)
1025- } resultReceivedAfterTimeout: {
1069+ } resultReceivedAfterTimeout: { _ in
10261070 await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
10271071 }
10281072 } else {
@@ -1080,7 +1124,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
10801124 } else {
10811125 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
10821126 await self . canonicalTarget ( for: mainFile)
1083- } resultReceivedAfterTimeout: {
1127+ } resultReceivedAfterTimeout: { _ in
10841128 await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
10851129 }
10861130 }
@@ -1234,28 +1278,39 @@ package actor BuildServerManager: QueueBasedMessageHandler {
12341278
12351279 let request = WorkspaceBuildTargetsRequest ( )
12361280 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+ )
12521303 }
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 )
12571308 )
12581309 }
1310+ guard let result else {
1311+ logger. error ( " Failed to get targets of workspace within timeout " )
1312+ return [ : ]
1313+ }
12591314 return result
12601315 }
12611316 return result
@@ -1288,7 +1343,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
12881343 }
12891344
12901345 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: [ ] )
12921351 }
12931352 return response. items
12941353 }
0 commit comments