Skip to content

Commit 7451406

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 fc11caf commit 7451406

15 files changed

+154
-111
lines changed

Sources/SKTestSupport/SkipUnless.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,14 @@ 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,
269+
documentUrl: nil,
269270
fileContents: nil
270271
)
271272
return response[sourcekitd.keys.useNewAPI] == 1

Sources/SourceKitD/SourceKitD.swift

+54-1
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,59 @@ package actor SourceKitD {
258258
notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler })
259259
}
260260

261+
private struct ContextualRequest {
262+
enum Kind {
263+
case editorOpen
264+
case codeCompleteOpen
265+
}
266+
let kind: Kind
267+
let request: SKDRequestDictionary
268+
}
269+
270+
private var contextualRequests: [URL: [ContextualRequest]] = [:]
271+
272+
private func recordContextualRequest(
273+
requestUid: sourcekitd_api_uid_t,
274+
request: SKDRequestDictionary,
275+
documentUrl: URL?
276+
) {
277+
guard let documentUrl else {
278+
return
279+
}
280+
switch requestUid {
281+
case requests.editorOpen:
282+
contextualRequests[documentUrl] = [ContextualRequest(kind: .editorOpen, request: request)]
283+
case requests.editorClose:
284+
contextualRequests[documentUrl] = nil
285+
case requests.codeCompleteOpen:
286+
contextualRequests[documentUrl, default: []].removeAll(where: { $0.kind == .codeCompleteOpen })
287+
contextualRequests[documentUrl, default: []].append(ContextualRequest(kind: .codeCompleteOpen, request: request))
288+
case requests.codeCompleteClose:
289+
contextualRequests[documentUrl, default: []].removeAll(where: { $0.kind == .codeCompleteOpen })
290+
if contextualRequests[documentUrl]?.isEmpty ?? false {
291+
contextualRequests[documentUrl] = nil
292+
}
293+
default:
294+
break
295+
}
296+
}
297+
261298
/// - Parameters:
262299
/// - request: The request to send to sourcekitd.
263300
/// - timeout: The maximum duration how long to wait for a response. If no response is returned within this time,
264301
/// declare the request as having timed out.
265302
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
266303
/// will be logged.
267304
package func send(
305+
_ requestUid: KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t>,
268306
_ request: SKDRequestDictionary,
269307
timeout: Duration,
308+
documentUrl: URL?,
270309
fileContents: String?
271310
) async throws -> SKDResponseDictionary {
311+
request.set(keys.request, to: requests[keyPath: requestUid])
312+
recordContextualRequest(requestUid: requests[keyPath: requestUid], request: request, documentUrl: documentUrl)
313+
272314
let sourcekitdResponse = try await withTimeout(timeout) {
273315
return try await withCancellableCheckedThrowingContinuation { (continuation) -> SourceKitDRequestHandle? in
274316
logger.info(
@@ -308,13 +350,24 @@ package actor SourceKitD {
308350

309351
guard let dict = sourcekitdResponse.value else {
310352
if sourcekitdResponse.error == .connectionInterrupted {
311-
let log = """
353+
var log = """
312354
Request:
313355
\(request.description)
314356
315357
File contents:
316358
\(fileContents ?? "<nil>")
317359
"""
360+
361+
if let documentUrl {
362+
let contextualRequests = (contextualRequests[documentUrl] ?? []).filter { $0.request !== request }
363+
for (index, contextualRequest) in contextualRequests.enumerated() {
364+
log += """
365+
366+
Contextual request \(index + 1) / \(contextualRequests.count):
367+
\(contextualRequest.request.description)
368+
"""
369+
}
370+
}
318371
let chunks = splitLongMultilineMessage(message: log)
319372
for (index, chunk) in chunks.enumerated() {
320373
logger.fault(

Sources/SourceKitLSP/Rename.swift

+3-6
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.buildSettings(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.buildSettings(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

+24-17
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,20 @@ class CodeCompletionSession {
263263
self.clientSupportsDocumentationResolve =
264264
clientCapabilities.textDocument?.completion?.completionItem?.resolveSupport?.properties.contains("documentation")
265265
?? false
266+
}
266267

268+
private func send(
269+
sourceKitDRequest requestUid: KeyPath<sourcekitd_api_requests, sourcekitd_api_uid_t> & Sendable,
270+
_ request: SKDRequestDictionary,
271+
snapshot: DocumentSnapshot?
272+
) async throws -> SKDResponseDictionary {
273+
try await sourcekitd.send(
274+
requestUid,
275+
request,
276+
timeout: options.sourcekitdRequestTimeoutOrDefault,
277+
documentUrl: snapshot?.uri.arbitrarySchemeURL,
278+
fileContents: snapshot?.text
279+
)
267280
}
268281

269282
private func open(
@@ -278,7 +291,6 @@ class CodeCompletionSession {
278291

279292
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
280293
let req = sourcekitd.dictionary([
281-
keys.request: sourcekitd.requests.codeCompleteOpen,
282294
keys.line: sourcekitdPosition.line,
283295
keys.column: sourcekitdPosition.utf8Column,
284296
keys.name: uri.pseudoPath,
@@ -287,11 +299,7 @@ class CodeCompletionSession {
287299
keys.codeCompleteOptions: optionsDictionary(filterText: filterText),
288300
])
289301

290-
let dict = try await sourcekitd.send(
291-
req,
292-
timeout: options.sourcekitdRequestTimeoutOrDefault,
293-
fileContents: snapshot.text
294-
)
302+
let dict = try await send(sourceKitDRequest: \.codeCompleteOpen, req, snapshot: snapshot)
295303
self.state = .open
296304

297305
guard let completions: SKDResponseArray = dict[keys.results] else {
@@ -317,19 +325,14 @@ class CodeCompletionSession {
317325
logger.info("Updating code completion session: \(self.description) filter=\(filterText)")
318326
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
319327
let req = sourcekitd.dictionary([
320-
keys.request: sourcekitd.requests.codeCompleteUpdate,
321328
keys.line: sourcekitdPosition.line,
322329
keys.column: sourcekitdPosition.utf8Column,
323330
keys.name: uri.pseudoPath,
324331
keys.sourceFile: uri.pseudoPath,
325332
keys.codeCompleteOptions: optionsDictionary(filterText: filterText),
326333
])
327334

328-
let dict = try await sourcekitd.send(
329-
req,
330-
timeout: options.sourcekitdRequestTimeoutOrDefault,
331-
fileContents: snapshot.text
332-
)
335+
let dict = try await send(sourceKitDRequest: \.codeCompleteUpdate, req, snapshot: snapshot)
333336
guard let completions: SKDResponseArray = dict[keys.results] else {
334337
return CompletionList(isIncomplete: false, items: [])
335338
}
@@ -370,15 +373,14 @@ class CodeCompletionSession {
370373
case .open:
371374
let sourcekitdPosition = snapshot.sourcekitdPosition(of: self.position)
372375
let req = sourcekitd.dictionary([
373-
keys.request: sourcekitd.requests.codeCompleteClose,
374376
keys.line: sourcekitdPosition.line,
375377
keys.column: sourcekitdPosition.utf8Column,
376378
keys.sourceFile: snapshot.uri.pseudoPath,
377379
keys.name: snapshot.uri.pseudoPath,
378380
keys.codeCompleteOptions: [keys.useNewAPI: 1],
379381
])
380382
logger.info("Closing code completion session: \(self.description)")
381-
_ = try? await sourcekitd.send(req, timeout: options.sourcekitdRequestTimeoutOrDefault, fileContents: nil)
383+
_ = try? await send(sourceKitDRequest: \.codeCompleteClose, req, snapshot: nil)
382384
self.state = .closed
383385
}
384386
}
@@ -548,11 +550,16 @@ class CodeCompletionSession {
548550
var item = item
549551
if let itemId = CompletionItemData(fromLSPAny: item.data)?.itemId {
550552
let req = sourcekitd.dictionary([
551-
sourcekitd.keys.request: sourcekitd.requests.codeCompleteDocumentation,
552-
sourcekitd.keys.identifier: itemId,
553+
sourcekitd.keys.identifier: itemId
553554
])
554555
let documentationResponse = await orLog("Retrieving documentation for completion item") {
555-
try await sourcekitd.send(req, timeout: timeout, fileContents: nil)
556+
try await sourcekitd.send(
557+
\.codeCompleteDocumentation,
558+
req,
559+
timeout: timeout,
560+
documentUrl: nil,
561+
fileContents: nil
562+
)
556563
}
557564
if let docString: String = documentationResponse?[sourcekitd.keys.docBrief] {
558565
item.documentation = .markupContent(MarkupContent(kind: .markdown, value: docString))

Sources/SourceKitLSP/Swift/CursorInfo.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ extension SwiftLanguageService {
147147
let keys = self.keys
148148

149149
let skreq = sourcekitd.dictionary([
150-
keys.request: requests.cursorInfo,
151150
keys.cancelOnSubsequentRequest: 0,
152151
keys.offset: offsetRange.lowerBound,
153152
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
@@ -160,7 +159,7 @@ extension SwiftLanguageService {
160159

161160
appendAdditionalParameters?(skreq)
162161

163-
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
162+
let dict = try await send(sourcekitdRequest: \.cursorInfo, skreq, snapshot: snapshot)
164163

165164
var cursorInfoResults: [CursorInfo] = []
166165
if let cursorInfo = CursorInfo(dict, documentManager: documentManager, sourcekitd: sourcekitd) {

Sources/SourceKitLSP/Swift/DiagnosticReportManager.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ actor DiagnosticReportManager {
113113
let keys = self.keys
114114

115115
let skreq = sourcekitd.dictionary([
116-
keys.request: requests.diagnostics,
117116
keys.sourceFile: snapshot.uri.sourcekitdSourceFile,
118117
keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath,
119118
keys.compilerArgs: compilerArgs as [SKDRequestValue],
@@ -122,8 +121,10 @@ actor DiagnosticReportManager {
122121
let dict: SKDResponseDictionary
123122
do {
124123
dict = try await self.sourcekitd.send(
124+
\.diagnostics,
125125
skreq,
126126
timeout: options.sourcekitdRequestTimeoutOrDefault,
127+
documentUrl: snapshot.uri.arbitrarySchemeURL,
127128
fileContents: snapshot.text
128129
)
129130
} catch SKDError.requestFailed(let sourcekitdError) {

Sources/SourceKitLSP/Swift/GeneratedInterfaceManager.swift

+12-10
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ actor GeneratedInterfaceManager {
6767
let sourcekitd = swiftLanguageService.sourcekitd
6868
for documentToClose in documentsToClose {
6969
await orLog("Closing generated interface") {
70-
_ = try await swiftLanguageService.sendSourcekitdRequest(
70+
_ = try await swiftLanguageService.send(
71+
sourcekitdRequest: \.editorClose,
7172
sourcekitd.dictionary([
72-
sourcekitd.keys.request: sourcekitd.requests.editorClose,
7373
sourcekitd.keys.name: documentToClose,
7474
sourcekitd.keys.cancelBuilds: 0,
7575
]),
76-
fileContents: nil
76+
snapshot: nil
7777
)
7878
}
7979
}
@@ -114,7 +114,6 @@ actor GeneratedInterfaceManager {
114114

115115
let keys = sourcekitd.keys
116116
let skreq = sourcekitd.dictionary([
117-
keys.request: sourcekitd.requests.editorOpenInterface,
118117
keys.moduleName: document.moduleName,
119118
keys.groupName: document.groupName,
120119
keys.name: document.sourcekitdDocumentName,
@@ -123,7 +122,7 @@ actor GeneratedInterfaceManager {
123122
.compilerArgs as [SKDRequestValue]?,
124123
])
125124

126-
let dict = try await swiftLanguageService.sendSourcekitdRequest(skreq, fileContents: nil)
125+
let dict = try await swiftLanguageService.send(sourcekitdRequest: \.editorOpenInterface, skreq, snapshot: nil)
127126

128127
guard let contents: String = dict[keys.sourceText] else {
129128
throw ResponseError.unknown("sourcekitd response is missing sourceText")
@@ -133,13 +132,13 @@ actor GeneratedInterfaceManager {
133132
// Another request raced us to create the generated interface. Discard what we computed here and return the cached
134133
// value.
135134
await orLog("Closing generated interface created during race") {
136-
_ = try await swiftLanguageService.sendSourcekitdRequest(
135+
_ = try await swiftLanguageService.send(
136+
sourcekitdRequest: \.editorClose,
137137
sourcekitd.dictionary([
138-
keys.request: sourcekitd.requests.editorClose,
139138
keys.name: document.sourcekitdDocumentName,
140139
keys.cancelBuilds: 0,
141140
]),
142-
fileContents: nil
141+
snapshot: nil
143142
)
144143
}
145144
return cached
@@ -191,12 +190,15 @@ actor GeneratedInterfaceManager {
191190
let sourcekitd = swiftLanguageService.sourcekitd
192191
let keys = sourcekitd.keys
193192
let skreq = sourcekitd.dictionary([
194-
keys.request: sourcekitd.requests.editorFindUSR,
195193
keys.sourceFile: document.sourcekitdDocumentName,
196194
keys.usr: usr,
197195
])
198196

199-
let dict = try await swiftLanguageService.sendSourcekitdRequest(skreq, fileContents: details.snapshot.text)
197+
let dict = try await swiftLanguageService.send(
198+
sourcekitdRequest: \.editorFindUSR,
199+
skreq,
200+
snapshot: details.snapshot
201+
)
200202
guard let offset: Int = dict[keys.offset] else {
201203
throw ResponseError.unknown("Missing key 'offset'")
202204
}

Sources/SourceKitLSP/Swift/MacroExpansion.swift

+1-5
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ actor MacroExpansionManager {
125125
let length = snapshot.utf8OffsetRange(of: range).count
126126

127127
let skreq = swiftLanguageService.sourcekitd.dictionary([
128-
keys.request: swiftLanguageService.requests.semanticRefactoring,
129128
// Preferred name for e.g. an extracted variable.
130129
// Empty string means sourcekitd chooses a name automatically.
131130
keys.name: "",
@@ -139,10 +138,7 @@ actor MacroExpansionManager {
139138
keys.compilerArgs: buildSettings?.compilerArgs as [SKDRequestValue]?,
140139
])
141140

142-
let dict = try await swiftLanguageService.sendSourcekitdRequest(
143-
skreq,
144-
fileContents: snapshot.text
145-
)
141+
let dict = try await swiftLanguageService.send(sourcekitdRequest: \.semanticRefactoring, skreq, snapshot: snapshot)
146142
guard let expansions = [RefactoringEdit](dict, snapshot, keys) else {
147143
throw SemanticRefactoringError.noEditsNeeded(snapshot.uri)
148144
}

Sources/SourceKitLSP/Swift/RefactoringResponse.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ extension SwiftLanguageService {
117117
let utf8Column = snapshot.lineTable.utf8ColumnAt(line: line, utf16Column: utf16Column)
118118

119119
let skreq = sourcekitd.dictionary([
120-
keys.request: self.requests.semanticRefactoring,
121120
// Preferred name for e.g. an extracted variable.
122121
// Empty string means sourcekitd chooses a name automatically.
123122
keys.name: "",
@@ -131,7 +130,7 @@ extension SwiftLanguageService {
131130
as [SKDRequestValue]?,
132131
])
133132

134-
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
133+
let dict = try await send(sourcekitdRequest: \.semanticRefactoring, skreq, snapshot: snapshot)
135134
guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
136135
throw SemanticRefactoringError.noEditsNeeded(uri)
137136
}

0 commit comments

Comments
 (0)