Skip to content

Commit 4ca2583

Browse files
authored
Merge pull request #1790 from ahoppen/notification-race
Fix race condition that caused `testMainFileChangesIfIncludeIsAdded` to fail
2 parents a22bf90 + 2270ab9 commit 4ca2583

File tree

2 files changed

+22
-15
lines changed

2 files changed

+22
-15
lines changed

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,21 +248,26 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
248248
/// - Note: This also returns any notifications sent before the call to
249249
/// `nextNotification`.
250250
package func nextNotification(timeout: Duration = .seconds(defaultTimeout)) async throws -> any NotificationType {
251-
return try await withThrowingTaskGroup(of: (any NotificationType).self) { taskGroup in
252-
taskGroup.addTask {
253-
for await notification in self.notifications {
254-
return notification
255-
}
256-
throw NotificationTimeoutError()
257-
}
258-
taskGroup.addTask {
259-
try await Task.sleep(for: timeout)
260-
throw NotificationTimeoutError()
251+
// The task that gets the next notification from `self.notifications`.
252+
let notificationYielder = Task {
253+
for await notification in self.notifications {
254+
return notification
261255
}
262-
let result = try await taskGroup.next()!
263-
taskGroup.cancelAll()
264-
return result
256+
throw NotificationTimeoutError()
257+
}
258+
// After `timeout`, we tell `notificationYielder` that we are no longer interested in its result by cancelling it.
259+
// We wait for `notificationYielder` to accept this cancellation instead of returning immediately to avoid a
260+
// situation where `notificationYielder` continues running, eats the first notification but it then never gets
261+
// delivered to the test because we already delivered a timeout.
262+
let cancellationTask = Task {
263+
try await Task.sleep(for: timeout)
264+
notificationYielder.cancel()
265+
}
266+
defer {
267+
// We have received a value and don't need the cancellationTask anymore
268+
cancellationTask.cancel()
265269
}
270+
return try await notificationYielder.value
266271
}
267272

268273
/// Await the next diagnostic notification sent to the client.

Tests/SourceKitLSPTests/MainFilesProviderTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import LanguageServerProtocol
14+
import SKLogging
1415
import SKTestSupport
1516
import SourceKitLSP
1617
import SwiftExtensions
@@ -202,10 +203,11 @@ final class MainFilesProviderTests: XCTestCase {
202203
// diagnostics.
203204
try await repeatUntilExpectedResult {
204205
let refreshedDiags = try? await project.testClient.nextDiagnosticsNotification(timeout: .seconds(1))
205-
guard let diagnostic = refreshedDiags?.diagnostics.only else {
206+
guard refreshedDiags?.diagnostics.only?.message == "Unused variable 'fromMyFancyLibrary'" else {
207+
logger.debug("Received unexpected diagnostics: \(refreshedDiags?.forLogging)")
206208
return false
207209
}
208-
return diagnostic.message == "Unused variable 'fromMyFancyLibrary'"
210+
return true
209211
}
210212
}
211213
}

0 commit comments

Comments
 (0)