Skip to content

Commit 69ab356

Browse files
committed
Log contextual requests that affect sourcekitd’s global state
This way we can log them when a sourcekitd request crashes and we can thus replay these contextual requests when diagnosing the crash.
1 parent 863c0e2 commit 69ab356

18 files changed

+146
-114
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ var targets: [Target] = [
429429
dependencies: [
430430
"BuildSystemIntegration",
431431
"CSKTestSupport",
432+
"Csourcekitd",
432433
"InProcessClient",
433434
"LanguageServerProtocol",
434435
"LanguageServerProtocolExtensions",

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,11 @@ package actor SkipUnless {
259259
)
260260
do {
261261
let response = try await sourcekitd.send(
262+
\.codeCompleteSetPopularAPI,
262263
sourcekitd.dictionary([
263-
sourcekitd.keys.request: sourcekitd.requests.codeCompleteSetPopularAPI,
264264
sourcekitd.keys.codeCompleteOptions: [
265265
sourcekitd.keys.useNewAPI: 1
266-
],
266+
]
267267
]),
268268
timeout: defaultTimeoutDuration
269269
)

Sources/SKTestSupport/SourceKitD+send.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,23 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
package import Csourcekitd
1314
package import SourceKitD
1415

1516
extension SourceKitD {
1617
/// Convenience overload of the `send` function for testing that doesn't restart sourcekitd if it does not respond
1718
/// and doesn't pass any file contents.
1819
package func send(
20+
_ requestUid: KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t>,
1921
_ request: SKDRequestDictionary,
20-
timeout: Duration
22+
timeout: Duration = defaultTimeoutDuration
2123
) async throws -> SKDResponseDictionary {
2224
return try await self.send(
25+
requestUid,
2326
request,
2427
timeout: timeout,
2528
restartTimeout: .seconds(60 * 60 * 24),
29+
documentUrl: nil,
2630
fileContents: nil
2731
)
2832
}

Sources/SourceKitD/SourceKitD.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,18 +315,60 @@ package actor SourceKitD {
315315
}
316316
}
317317

318+
private struct ContextualRequest {
319+
enum Kind {
320+
case editorOpen
321+
case codeCompleteOpen
322+
}
323+
let kind: Kind
324+
let request: SKDRequestDictionary
325+
}
326+
327+
private var contextualRequests: [URL: [ContextualRequest]] = [:]
328+
329+
private func recordContextualRequest(
330+
requestUid: sourcekitd_api_uid_t,
331+
request: SKDRequestDictionary,
332+
documentUrl: URL?
333+
) {
334+
guard let documentUrl else {
335+
return
336+
}
337+
switch requestUid {
338+
case requests.editorOpen:
339+
contextualRequests[documentUrl] = [ContextualRequest(kind: .editorOpen, request: request)]
340+
case requests.editorClose:
341+
contextualRequests[documentUrl] = nil
342+
case requests.codeCompleteOpen:
343+
contextualRequests[documentUrl, default: []].removeAll(where: { $0.kind == .codeCompleteOpen })
344+
contextualRequests[documentUrl, default: []].append(ContextualRequest(kind: .codeCompleteOpen, request: request))
345+
case requests.codeCompleteClose:
346+
contextualRequests[documentUrl, default: []].removeAll(where: { $0.kind == .codeCompleteOpen })
347+
if contextualRequests[documentUrl]?.isEmpty ?? false {
348+
contextualRequests[documentUrl] = nil
349+
}
350+
default:
351+
break
352+
}
353+
}
354+
318355
/// - Parameters:
319356
/// - request: The request to send to sourcekitd.
320357
/// - timeout: The maximum duration how long to wait for a response. If no response is returned within this time,
321358
/// declare the request as having timed out.
322359
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
323360
/// will be logged.
324361
package func send(
362+
_ requestUid: KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t>,
325363
_ request: SKDRequestDictionary,
326364
timeout: Duration,
327365
restartTimeout: Duration,
366+
documentUrl: URL?,
328367
fileContents: String?
329368
) async throws -> SKDResponseDictionary {
369+
request.set(keys.request, to: requests[keyPath: requestUid])
370+
recordContextualRequest(requestUid: requests[keyPath: requestUid], request: request, documentUrl: documentUrl)
371+
330372
let sourcekitdResponse = try await withTimeout(timeout) {
331373
let restartTimeoutHandle = TimeoutHandle()
332374
do {
@@ -387,13 +429,24 @@ package actor SourceKitD {
387429

388430
guard let dict = sourcekitdResponse.value else {
389431
if sourcekitdResponse.error == .connectionInterrupted {
390-
let log = """
432+
var log = """
391433
Request:
392434
\(request.description)
393435
394436
File contents:
395437
\(fileContents ?? "<nil>")
396438
"""
439+
440+
if let documentUrl {
441+
let contextualRequests = (contextualRequests[documentUrl] ?? []).filter { $0.request !== request }
442+
for (index, contextualRequest) in contextualRequests.enumerated() {
443+
log += """
444+
445+
Contextual request \(index + 1) / \(contextualRequests.count):
446+
\(contextualRequest.request.description)
447+
"""
448+
}
449+
}
397450
let chunks = splitLongMultilineMessage(message: log)
398451
for (index, chunk) in chunks.enumerated() {
399452
logger.fault(
@@ -414,10 +467,14 @@ package actor SourceKitD {
414467
}
415468

416469
package func crash() async {
417-
let req = dictionary([
418-
keys.request: requests.crashWithExit
419-
])
420-
_ = try? await send(req, timeout: .seconds(60), restartTimeout: .seconds(24 * 60 * 60), fileContents: nil)
470+
_ = try? await send(
471+
\.crashWithExit,
472+
dictionary([:]),
473+
timeout: .seconds(60),
474+
restartTimeout: .seconds(24 * 60 * 60),
475+
documentUrl: nil,
476+
fileContents: nil
477+
)
421478
}
422479
}
423480

Sources/SourceKitLSP/Rename.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,6 @@ extension SwiftLanguageService {
353353
}
354354

355355
let req = sourcekitd.dictionary([
356-
keys.request: sourcekitd.requests.nameTranslation,
357356
keys.sourceFile: snapshot.uri.pseudoPath,
358357
keys.compilerArgs: await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
359358
as [SKDRequestValue]?,
@@ -363,7 +362,7 @@ extension SwiftLanguageService {
363362
keys.argNames: sourcekitd.array(name.parameters.map { $0.stringOrWildcard }),
364363
])
365364

366-
let response = try await sendSourcekitdRequest(req, fileContents: snapshot.text)
365+
let response = try await send(sourcekitdRequest: \.nameTranslation, req, snapshot: snapshot)
367366

368367
guard let isZeroArgSelector: Int = response[keys.isZeroArgSelector],
369368
let selectorPieces: SKDResponseArray = response[keys.selectorPieces]
@@ -405,7 +404,6 @@ extension SwiftLanguageService {
405404
name: String
406405
) async throws -> String {
407406
let req = sourcekitd.dictionary([
408-
keys.request: sourcekitd.requests.nameTranslation,
409407
keys.sourceFile: snapshot.uri.pseudoPath,
410408
keys.compilerArgs: await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
411409
as [SKDRequestValue]?,
@@ -421,7 +419,7 @@ extension SwiftLanguageService {
421419
req.set(keys.baseName, to: name)
422420
}
423421

424-
let response = try await sendSourcekitdRequest(req, fileContents: snapshot.text)
422+
let response = try await send(sourcekitdRequest: \.nameTranslation, req, snapshot: snapshot)
425423

426424
guard let baseName: String = response[keys.baseName] else {
427425
throw NameTranslationError.malformedClangToSwiftTranslateNameResponse(response)
@@ -886,15 +884,14 @@ extension SwiftLanguageService {
886884
)
887885

888886
let skreq = sourcekitd.dictionary([
889-
keys.request: requests.findRenameRanges,
890887
keys.sourceFile: snapshot.uri.pseudoPath,
891888
// find-syntactic-rename-ranges is a syntactic sourcekitd request that doesn't use the in-memory file snapshot.
892889
// We need to send the source text again.
893890
keys.sourceText: snapshot.text,
894891
keys.renameLocations: locations,
895892
])
896893

897-
let syntacticRenameRangesResponse = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
894+
let syntacticRenameRangesResponse = try await send(sourcekitdRequest: \.findRenameRanges, skreq, snapshot: snapshot)
898895
guard let categorizedRanges: SKDResponseArray = syntacticRenameRangesResponse[keys.categorizedRanges] else {
899896
throw ResponseError.internalError("sourcekitd did not return categorized ranges")
900897
}

Sources/SourceKitLSP/Swift/CodeCompletionSession.swift

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ class CodeCompletionSession {
264264
self.clientSupportsDocumentationResolve =
265265
clientCapabilities.textDocument?.completion?.completionItem?.resolveSupport?.properties.contains("documentation")
266266
?? false
267-
268267
}
269268

270269
private func open(
@@ -279,7 +278,6 @@ class CodeCompletionSession {
279278

280279
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
281280
let req = sourcekitd.dictionary([
282-
keys.request: sourcekitd.requests.codeCompleteOpen,
283281
keys.line: sourcekitdPosition.line,
284282
keys.column: sourcekitdPosition.utf8Column,
285283
keys.name: uri.pseudoPath,
@@ -288,7 +286,7 @@ class CodeCompletionSession {
288286
keys.codeCompleteOptions: optionsDictionary(filterText: filterText),
289287
])
290288

291-
let dict = try await sendSourceKitdRequest(req, snapshot: snapshot)
289+
let dict = try await send(sourceKitDRequest: \.codeCompleteOpen, req, snapshot: snapshot)
292290
self.state = .open
293291

294292
guard let completions: SKDResponseArray = dict[keys.results] else {
@@ -314,15 +312,14 @@ class CodeCompletionSession {
314312
logger.info("Updating code completion session: \(self.description) filter=\(filterText)")
315313
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
316314
let req = sourcekitd.dictionary([
317-
keys.request: sourcekitd.requests.codeCompleteUpdate,
318315
keys.line: sourcekitdPosition.line,
319316
keys.column: sourcekitdPosition.utf8Column,
320317
keys.name: uri.pseudoPath,
321318
keys.sourceFile: uri.pseudoPath,
322319
keys.codeCompleteOptions: optionsDictionary(filterText: filterText),
323320
])
324321

325-
let dict = try await sendSourceKitdRequest(req, snapshot: snapshot)
322+
let dict = try await send(sourceKitDRequest: \.codeCompleteUpdate, req, snapshot: snapshot)
326323
guard let completions: SKDResponseArray = dict[keys.results] else {
327324
return CompletionList(isIncomplete: false, items: [])
328325
}
@@ -363,29 +360,31 @@ class CodeCompletionSession {
363360
case .open:
364361
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
365362
let req = sourcekitd.dictionary([
366-
keys.request: sourcekitd.requests.codeCompleteClose,
367363
keys.line: sourcekitdPosition.line,
368364
keys.column: sourcekitdPosition.utf8Column,
369365
keys.sourceFile: snapshot.uri.pseudoPath,
370366
keys.name: snapshot.uri.pseudoPath,
371367
keys.codeCompleteOptions: [keys.useNewAPI: 1],
372368
])
373369
logger.info("Closing code completion session: \(self.description)")
374-
_ = try? await sendSourceKitdRequest(req, snapshot: nil)
370+
_ = try? await send(sourceKitDRequest: \.codeCompleteClose, req, snapshot: nil)
375371
self.state = .closed
376372
}
377373
}
378374

379375
// MARK: - Helpers
380376

381-
private func sendSourceKitdRequest(
377+
private func send(
378+
sourceKitDRequest requestUid: KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t> & Sendable,
382379
_ request: SKDRequestDictionary,
383380
snapshot: DocumentSnapshot?
384381
) async throws -> SKDResponseDictionary {
385382
try await sourcekitd.send(
383+
requestUid,
386384
request,
387385
timeout: options.sourcekitdRequestTimeoutOrDefault,
388386
restartTimeout: options.semanticServiceRestartTimeoutOrDefault,
387+
documentUrl: snapshot?.uri.arbitrarySchemeURL,
389388
fileContents: snapshot?.text
390389
)
391390
}
@@ -560,11 +559,17 @@ class CodeCompletionSession {
560559
var item = item
561560
if let itemId = CompletionItemData(fromLSPAny: item.data)?.itemId {
562561
let req = sourcekitd.dictionary([
563-
sourcekitd.keys.request: sourcekitd.requests.codeCompleteDocumentation,
564-
sourcekitd.keys.identifier: itemId,
562+
sourcekitd.keys.identifier: itemId
565563
])
566564
let documentationResponse = await orLog("Retrieving documentation for completion item") {
567-
try await sourcekitd.send(req, timeout: timeout, restartTimeout: restartTimeout, fileContents: nil)
565+
try await sourcekitd.send(
566+
\.codeCompleteDocumentation,
567+
req,
568+
timeout: timeout,
569+
restartTimeout: restartTimeout,
570+
documentUrl: nil,
571+
fileContents: nil
572+
)
568573
}
569574
if let docString: String = documentationResponse?[sourcekitd.keys.docBrief] {
570575
item.documentation = .markupContent(MarkupContent(kind: .markdown, value: docString))

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ extension SwiftLanguageService {
158158
let keys = self.keys
159159

160160
let skreq = sourcekitd.dictionary([
161-
keys.request: requests.cursorInfo,
162161
keys.cancelOnSubsequentRequest: 0,
163162
keys.offset: offsetRange.lowerBound,
164163
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
@@ -170,7 +169,7 @@ extension SwiftLanguageService {
170169

171170
appendAdditionalParameters?(skreq)
172171

173-
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
172+
let dict = try await send(sourcekitdRequest: \.cursorInfo, skreq, snapshot: snapshot)
174173

175174
var cursorInfoResults: [CursorInfo] = []
176175
if let cursorInfo = CursorInfo(dict, snapshot: snapshot, documentManager: documentManager, sourcekitd: sourcekitd) {

Sources/SourceKitLSP/Swift/DiagnosticReportManager.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ actor DiagnosticReportManager {
107107
let keys = self.keys
108108

109109
let skreq = sourcekitd.dictionary([
110-
keys.request: requests.diagnostics,
111110
keys.sourceFile: snapshot.uri.sourcekitdSourceFile,
112111
keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath,
113112
keys.compilerArgs: compilerArgs as [SKDRequestValue],
@@ -116,9 +115,11 @@ actor DiagnosticReportManager {
116115
let dict: SKDResponseDictionary
117116
do {
118117
dict = try await self.sourcekitd.send(
118+
\.diagnostics,
119119
skreq,
120120
timeout: options.sourcekitdRequestTimeoutOrDefault,
121121
restartTimeout: options.semanticServiceRestartTimeoutOrDefault,
122+
documentUrl: snapshot.uri.arbitrarySchemeURL,
122123
fileContents: snapshot.text
123124
)
124125
} catch SKDError.requestFailed(let sourcekitdError) {

0 commit comments

Comments
 (0)