@@ -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
@@ -898,6 +941,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
898941 previousUpdateTask? . cancel ( )
899942 await orLog ( " Re-computing copy file map " ) {
900943 let sourceFilesAndDirectories = try await self . sourceFilesAndDirectories ( )
944+ try Task . checkCancellation ( )
901945 var copiedFileMap : [ DocumentURI : DocumentURI ] = [ : ]
902946 for (file, fileInfo) in sourceFilesAndDirectories. files {
903947 for copyDestination in fileInfo. copyDestinations {
@@ -1024,7 +1068,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
10241068 if fallbackAfterTimeout {
10251069 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
10261070 return try await self . buildSettingsFromBuildServer ( for: document, in: target, language: language)
1027- } resultReceivedAfterTimeout: {
1071+ } resultReceivedAfterTimeout: { _ in
10281072 await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
10291073 }
10301074 } else {
@@ -1082,7 +1126,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
10821126 } else {
10831127 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
10841128 await self . canonicalTarget ( for: mainFile)
1085- } resultReceivedAfterTimeout: {
1129+ } resultReceivedAfterTimeout: { _ in
10861130 await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
10871131 }
10881132 }
@@ -1236,28 +1280,39 @@ package actor BuildServerManager: QueueBasedMessageHandler {
12361280
12371281 let request = WorkspaceBuildTargetsRequest ( )
12381282 let result = try await cachedBuildTargets. get ( request, isolation: self ) { request in
1239- let buildTargets = try await buildServerAdapter. send ( request) . targets
1240- let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1241- var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1242- result. reserveCapacity ( buildTargets. count)
1243- for buildTarget in buildTargets {
1244- guard result [ buildTarget. id] == nil else {
1245- logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1246- continue
1247- }
1248- let depth : Int
1249- if let d = depths [ buildTarget. id] {
1250- depth = d
1251- } else {
1252- logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1253- depth = 0
1283+ let result = try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1284+ let buildTargets = try await buildServerAdapter. send ( request) . targets
1285+ let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1286+ var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1287+ result. reserveCapacity ( buildTargets. count)
1288+ for buildTarget in buildTargets {
1289+ guard result [ buildTarget. id] == nil else {
1290+ logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1291+ continue
1292+ }
1293+ let depth : Int
1294+ if let d = depths [ buildTarget. id] {
1295+ depth = d
1296+ } else {
1297+ logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1298+ depth = 0
1299+ }
1300+ result [ buildTarget. id] = BuildTargetInfo (
1301+ target: buildTarget,
1302+ depth: depth,
1303+ dependents: dependents [ buildTarget. id] ?? [ ]
1304+ )
12541305 }
1255- result [ buildTarget . id ] = BuildTargetInfo (
1256- target : buildTarget ,
1257- depth : depth ,
1258- dependents : dependents [ buildTarget . id ] ?? [ ]
1306+ return result
1307+ } resultReceivedAfterTimeout : { newResult in
1308+ await self . buildTargetsDidChange (
1309+ . buildTargetsReceivedResultAfterTimeout ( request : request , newResult : newResult )
12591310 )
12601311 }
1312+ guard let result else {
1313+ logger. error ( " Failed to get targets of workspace within timeout " )
1314+ return [ : ]
1315+ }
12611316 return result
12621317 }
12631318 return result
@@ -1290,7 +1345,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
12901345 }
12911346
12921347 let response = try await cachedTargetSources. get ( request, isolation: self ) { request in
1293- try await buildServerAdapter. send ( request)
1348+ try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1349+ return try await buildServerAdapter. send ( request)
1350+ } resultReceivedAfterTimeout: { newResult in
1351+ await self . buildTargetsDidChange ( . sourceFilesReceivedResultAfterTimeout( request: request, newResult: newResult) )
1352+ } ?? BuildTargetSourcesResponse ( items: [ ] )
12941353 }
12951354 return response. items
12961355 }
0 commit comments