Skip to content

Commit abea29c

Browse files
committed
Do not cache diagnostic reports for sourcekitd requests that have failed
Otherwise, we return the failed diagnostic report even after sourcekitd has been restored. Also, make sure that we don't show the `semantic editor is disabled` error to users.
1 parent b3a73f6 commit abea29c

File tree

2 files changed

+66
-12
lines changed

2 files changed

+66
-12
lines changed

Sources/SourceKitLSP/Swift/DiagnosticReportManager.swift

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import SwiftExtensions
2020
import SwiftParserDiagnostics
2121

2222
actor DiagnosticReportManager {
23-
/// A task to produce diagnostics, either from a diagnostics request to `sourcektid` or by using the built-in swift-syntax.
24-
private typealias ReportTask = RefCountedCancellableTask<RelatedFullDocumentDiagnosticReport>
23+
/// A task to produce diagnostics, either from a diagnostics request to `sourcekitd` or by using the built-in swift-syntax.
24+
private typealias ReportTask = RefCountedCancellableTask<
25+
(report: RelatedFullDocumentDiagnosticReport, cachable: Bool)
26+
>
2527

2628
private let sourcekitd: SourceKitD
2729
private let options: SourceKitLSPOptions
@@ -68,7 +70,14 @@ actor DiagnosticReportManager {
6870
buildSettings: SwiftCompileCommand?
6971
) async throws -> RelatedFullDocumentDiagnosticReport {
7072
if let reportTask = reportTask(for: snapshot.id, buildSettings: buildSettings), await !reportTask.isCancelled {
71-
return try await reportTask.value
73+
do {
74+
let cachedValue = try await reportTask.value
75+
if cachedValue.cachable {
76+
return cachedValue.report
77+
}
78+
} catch {
79+
// Do not cache failed requests
80+
}
7281
}
7382
let reportTask: ReportTask
7483
if let buildSettings, !buildSettings.isFallback {
@@ -88,7 +97,7 @@ actor DiagnosticReportManager {
8897
}
8998
}
9099
setReportTask(for: snapshot.id, buildSettings: buildSettings, reportTask: reportTask)
91-
return try await reportTask.value
100+
return try await reportTask.value.report
92101
}
93102

94103
func removeItemsFromCache(with uri: DocumentURI) async {
@@ -98,7 +107,7 @@ actor DiagnosticReportManager {
98107
private func requestReport(
99108
with snapshot: DocumentSnapshot,
100109
compilerArgs: [String]
101-
) async throws -> LanguageServerProtocol.RelatedFullDocumentDiagnosticReport {
110+
) async throws -> (report: RelatedFullDocumentDiagnosticReport, cachable: Bool) {
102111
try Task.checkCancellation()
103112

104113
let keys = self.keys
@@ -119,17 +128,23 @@ actor DiagnosticReportManager {
119128
)
120129
} catch SKDError.requestFailed(let sourcekitdError) {
121130
var errorMessage = sourcekitdError
131+
if errorMessage.contains("semantic editor is disabled") {
132+
throw SKDError.requestFailed(sourcekitdError)
133+
}
122134
if errorMessage.hasPrefix("error response (Request Failed): error: ") {
123135
errorMessage = String(errorMessage.dropFirst(40))
124136
}
125-
return RelatedFullDocumentDiagnosticReport(items: [
137+
let report = RelatedFullDocumentDiagnosticReport(items: [
126138
Diagnostic(
127139
range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 0),
128140
severity: .error,
129141
source: "SourceKit",
130142
message: "Internal SourceKit error: \(errorMessage)"
131143
)
132144
])
145+
// If generating the diagnostic report failed because of a sourcekitd problem, mark as as non-cachable because
146+
// executing the sourcekitd request again might succeed (eg. if sourcekitd has been restored after a crash).
147+
return (report, cachable: false)
133148
}
134149

135150
try Task.checkCancellation()
@@ -144,12 +159,13 @@ actor DiagnosticReportManager {
144159
)
145160
}) ?? []
146161

147-
return RelatedFullDocumentDiagnosticReport(items: diagnostics)
162+
let report = RelatedFullDocumentDiagnosticReport(items: diagnostics)
163+
return (report, cachable: true)
148164
}
149165

150166
private func requestFallbackReport(
151167
with snapshot: DocumentSnapshot
152-
) async throws -> LanguageServerProtocol.RelatedFullDocumentDiagnosticReport {
168+
) async throws -> (report: RelatedFullDocumentDiagnosticReport, cachable: Bool) {
153169
// If we don't have build settings or we only have fallback build settings,
154170
// sourcekitd won't be able to give us accurate semantic diagnostics.
155171
// Fall back to providing syntactic diagnostics from the built-in
@@ -163,7 +179,8 @@ actor DiagnosticReportManager {
163179
}
164180
return Diagnostic(diag, in: snapshot)
165181
}
166-
return RelatedFullDocumentDiagnosticReport(items: diagnostics)
182+
let report = RelatedFullDocumentDiagnosticReport(items: diagnostics)
183+
return (report, cachable: true)
167184
}
168185

169186
/// The reportTask for the given document snapshot and buildSettings.
@@ -184,10 +201,10 @@ actor DiagnosticReportManager {
184201
buildSettings: SwiftCompileCommand?,
185202
reportTask: ReportTask
186203
) {
187-
reportTaskCache.insert((snapshotID, buildSettings, reportTask), at: 0)
188-
189204
// Remove any reportTasks for old versions of this document.
190-
reportTaskCache.removeAll(where: { $0.snapshotID < snapshotID })
205+
reportTaskCache.removeAll(where: { $0.snapshotID <= snapshotID })
206+
207+
reportTaskCache.insert((snapshotID, buildSettings, reportTask), at: 0)
191208

192209
// If we still have more than `cacheSize` reportTasks, delete the ones that
193210
// were produced last. We can always re-request them on-demand.

Tests/SourceKitDTests/CrashRecoveryTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,41 @@ final class CrashRecoveryTests: XCTestCase {
312312
"Clangd restarted too quickly after crashing twice in a row. We are not preventing crash loops."
313313
)
314314
}
315+
316+
func testPullDiagnosticsAfterCrash() async throws {
317+
try SkipUnless.platformIsDarwin("Linux and Windows use in-process sourcekitd")
318+
try SkipUnless.longTestsEnabled()
319+
320+
// Ensure that we don't cache the diagnostic report from the crashed sourcekitd and don't emit any of the internal
321+
// sourcekitd errors such as `semantic editor disabled` in the diagnostic report.
322+
let testClient = try await TestSourceKitLSPClient()
323+
let uri = DocumentURI(for: .swift)
324+
testClient.openDocument(
325+
"""
326+
func test() {
327+
let x: String = 1
328+
}
329+
""",
330+
uri: uri
331+
)
332+
333+
let swiftLanguageService =
334+
await testClient.server.languageService(
335+
for: uri,
336+
.swift,
337+
in: testClient.server.workspaceForDocument(uri: uri)!
338+
) as! SwiftLanguageService
339+
340+
await swiftLanguageService.crash()
341+
342+
try await repeatUntilExpectedResult(timeout: .seconds(30)) {
343+
let diagnostics = try await testClient.send(DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri)))
344+
if diagnostics.fullReport?.items.count == 0 {
345+
return false
346+
}
347+
let item = try XCTUnwrap(diagnostics.fullReport?.items.only)
348+
XCTAssertEqual(item.message, "Cannot convert value of type 'Int' to specified type 'String'")
349+
return true
350+
}
351+
}
315352
}

0 commit comments

Comments
 (0)