Skip to content

Commit a703b72

Browse files
authored
Merge pull request #1354 from ahoppen/timeout-indexstore-update
Time out updating the index store after 2 minutes
2 parents 24dfbd5 + 3ae6501 commit a703b72

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

Sources/SKSupport/AsyncUtils.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,28 @@ extension Collection where Element: Sendable {
166166
}
167167
}
168168
}
169+
170+
public struct TimeoutError: Error, CustomStringConvertible {
171+
public var description: String { "Timed out" }
172+
}
173+
174+
/// Executes `body`. If it doesn't finish after `duration`, throws a `TimeoutError`.
175+
public func withTimeout<T: Sendable>(
176+
_ duration: Duration,
177+
_ body: @escaping @Sendable () async throws -> T
178+
) async throws -> T {
179+
try await withThrowingTaskGroup(of: T.self) { taskGroup in
180+
taskGroup.addTask {
181+
try await Task.sleep(for: duration)
182+
throw TimeoutError()
183+
}
184+
taskGroup.addTask {
185+
return try await body()
186+
}
187+
for try await value in taskGroup {
188+
taskGroup.cancelAll()
189+
return value
190+
}
191+
throw CancellationError()
192+
}
193+
}

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,13 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
341341
arguments: processArguments,
342342
workingDirectory: workingDirectory
343343
)
344-
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
344+
// Time out updating of the index store after 2 minutes. We don't expect any single file compilation to take longer
345+
// than 2 minutes in practice, so this indicates that the compiler has entered a loop and we probably won't make any
346+
// progress here. We will try indexing the file again when it is edited or when the project is re-opened.
347+
// 2 minutes have been chosen arbitrarily.
348+
let result = try await withTimeout(.seconds(120)) {
349+
try await process.waitUntilExitSendingSigIntOnTaskCancellation()
350+
}
345351

346352
indexProcessDidProduceResult(
347353
IndexProcessResult(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LSPTestSupport
14+
import SKSupport
15+
import XCTest
16+
17+
final class AsyncUtilsTests: XCTestCase {
18+
func testWithTimeout() async throws {
19+
let expectation = self.expectation(description: "withTimeout body finished")
20+
await assertThrowsError(
21+
try await withTimeout(.seconds(0.1)) {
22+
try? await Task.sleep(for: .seconds(10))
23+
XCTAssert(Task.isCancelled)
24+
expectation.fulfill()
25+
}
26+
) { error in
27+
XCTAssert(error is TimeoutError, "Received unexpected error \(error)")
28+
}
29+
try await fulfillmentOfOrThrow([expectation])
30+
}
31+
}

0 commit comments

Comments
 (0)