Skip to content

Commit 0c4f659

Browse files
authored
Merge pull request #933 from ahoppen/ahoppen/log-sourcekitd-crash-info
When sourcekitd crashes, log the file contents with which it crashed and the request
2 parents 389cb91 + 1a23153 commit 0c4f659

13 files changed

+76
-49
lines changed

Sources/LSPLogging/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_library(LSPLogging STATIC
55
LoggingScope.swift
66
NonDarwinLogging.swift
77
OrLog.swift
8+
SplitLogMessage.swift
89
)
910
set_target_properties(LSPLogging PROPERTIES
1011
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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+
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
14+
///
15+
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
16+
/// the maximum message length of `os_log` and thus won't get truncated.
17+
///
18+
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
19+
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
20+
public func splitLongMultilineMessage(message: String, maxChunkSize: Int = 800) -> [String] {
21+
var chunks: [String] = []
22+
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
23+
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
24+
chunks[chunks.count - 1] += "\n" + line
25+
} else {
26+
chunks.append(String(line))
27+
}
28+
}
29+
return chunks
30+
}

Sources/SKCore/BuildSystemManager.swift

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ fileprivate actor BuildSettingsLogger {
325325
\(settings.workingDirectory ?? "<nil>")
326326
"""
327327

328-
let chunks = splitLongMultilineMessage(message: log, maxChunkSize: 800)
328+
let chunks = splitLongMultilineMessage(message: log)
329329
for (index, chunk) in chunks.enumerated() {
330330
logger.log(
331331
"""
@@ -335,23 +335,4 @@ fileprivate actor BuildSettingsLogger {
335335
)
336336
}
337337
}
338-
339-
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
340-
///
341-
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
342-
/// the maximum message length of `os_log` and thus won't get truncated.
343-
///
344-
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
345-
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
346-
private func splitLongMultilineMessage(message: String, maxChunkSize: Int) -> [String] {
347-
var chunks: [String] = []
348-
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
349-
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
350-
chunks[chunks.count - 1] += "\n" + line
351-
} else {
352-
chunks.append(String(line))
353-
}
354-
}
355-
return chunks
356-
}
357338
}

Sources/SourceKitD/SourceKitD.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ extension SourceKitD {
6565

6666
// MARK: - Convenience API for requests.
6767

68-
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
68+
/// - Parameters:
69+
/// - req: The request to send to sourcekitd.
70+
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
71+
/// will be logged.
72+
public func send(_ req: SKDRequestDictionary, fileContents: String?) async throws -> SKDResponseDictionary {
6973
logRequest(req)
7074

7175
let signposter = logger.makeSignposter()
@@ -86,6 +90,24 @@ extension SourceKitD {
8690

8791
guard let dict = sourcekitdResponse.value else {
8892
signposter.endInterval("sourcekitd-request", signposterState, "Error")
93+
if sourcekitdResponse.error == .connectionInterrupted {
94+
let log = """
95+
Request:
96+
\(req.description)
97+
98+
File contents:
99+
\(fileContents ?? "<nil>")
100+
"""
101+
let chunks = splitLongMultilineMessage(message: log)
102+
for (index, chunk) in chunks.enumerated() {
103+
logger.fault(
104+
"""
105+
sourcekitd crashed (\(index + 1)/\(chunks.count))
106+
\(chunk)
107+
"""
108+
)
109+
}
110+
}
89111
throw sourcekitdResponse.error!
90112
}
91113

@@ -95,9 +117,7 @@ extension SourceKitD {
95117
}
96118

97119
private func logRequest(_ request: SKDRequestDictionary) {
98-
// FIXME: Ideally we could log the request key here at the info level but the dictionary is
99-
// readonly.
100-
logger.log(
120+
logger.info(
101121
"""
102122
Sending sourcekitd request:
103123
\(request.forLogging)

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -666,12 +666,7 @@ extension SourceKitServer: MessageHandler {
666666
}
667667

668668
private func handleImpl(_ notification: some NotificationType, from clientID: ObjectIdentifier) async {
669-
logger.log(
670-
"""
671-
Received notification
672-
\(notification.forLogging)
673-
"""
674-
)
669+
logger.log("Received notification: \(notification.forLogging)")
675670

676671
switch notification {
677672
case let notification as InitializedNotification:

Sources/SourceKitLSP/Swift/CodeCompletionSession.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class CodeCompletionSession {
197197
req[keys.compilerargs] = compileCommand.compilerArgs
198198
}
199199

200-
let dict = try await sourcekitd.send(req)
200+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
201201
self.state = .open
202202

203203
guard let completions: SKDResponseArray = dict[keys.results] else {
@@ -230,7 +230,7 @@ class CodeCompletionSession {
230230
req[keys.name] = uri.pseudoPath
231231
req[keys.codecomplete_options] = optionsDictionary(filterText: filterText, options: options)
232232

233-
let dict = try await sourcekitd.send(req)
233+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
234234
guard let completions: SKDResponseArray = dict[keys.results] else {
235235
return CompletionList(isIncomplete: false, items: [])
236236
}
@@ -275,7 +275,7 @@ class CodeCompletionSession {
275275
req[keys.offset] = self.utf8StartOffset
276276
req[keys.name] = self.snapshot.uri.pseudoPath
277277
logger.info("Closing code completion session: \(self, privacy: .private)")
278-
_ = try? await sourcekitd.send(req)
278+
_ = try? await sourcekitd.send(req, fileContents: nil)
279279
self.state = .closed
280280
}
281281
}

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ extension SwiftLanguageServer {
107107

108108
appendAdditionalParameters?(skreq)
109109

110-
let dict = try await self.sourcekitd.send(skreq)
110+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
111111

112112
guard let kind: sourcekitd_uid_t = dict[keys.kind] else {
113113
// Nothing to report.

Sources/SourceKitLSP/Swift/OpenInterface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ extension SwiftLanguageServer {
8282
skreq[keys.compilerargs] = compileCommand.compilerArgs
8383
}
8484

85-
let dict = try await self.sourcekitd.send(skreq)
85+
let dict = try await self.sourcekitd.send(skreq, fileContents: nil)
8686
return InterfaceInfo(contents: dict[keys.sourcetext] ?? "")
8787
}
8888

@@ -103,7 +103,7 @@ extension SwiftLanguageServer {
103103
skreq[keys.sourcefile] = uri.pseudoPath
104104
skreq[keys.usr] = symbol
105105

106-
let dict = try await self.sourcekitd.send(skreq)
106+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
107107
if let offset: Int = dict[keys.offset],
108108
let position = snapshot.positionOf(utf8Offset: offset)
109109
{

Sources/SourceKitLSP/Swift/SemanticRefactoring.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ extension SwiftLanguageServer {
155155
skreq[keys.compilerargs] = compileCommand.compilerArgs
156156
}
157157

158-
let dict = try await self.sourcekitd.send(skreq)
158+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
159159
guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
160160
throw SemanticRefactoringError.noEditsNeeded(uri)
161161
}

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension SwiftLanguageServer {
3131
// FIXME: SourceKit should probably cache this for us.
3232
skreq[keys.compilerargs] = buildSettings.compilerArgs
3333

34-
let dict = try await sourcekitd.send(skreq)
34+
let dict = try await sourcekitd.send(skreq, fileContents: snapshot.text)
3535

3636
guard let skTokens: SKDResponseArray = dict[keys.semantic_tokens] else {
3737
return nil

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ extension SwiftLanguageServer {
262262
public func _crash() async {
263263
let req = SKDRequestDictionary(sourcekitd: sourcekitd)
264264
req[sourcekitd.keys.request] = sourcekitd.requests.crash_exit
265-
_ = try? await sourcekitd.send(req)
265+
_ = try? await sourcekitd.send(req, fileContents: nil)
266266
}
267267

268268
// MARK: - Build System Integration
@@ -276,7 +276,7 @@ extension SwiftLanguageServer {
276276
let closeReq = SKDRequestDictionary(sourcekitd: self.sourcekitd)
277277
closeReq[keys.request] = self.requests.editor_close
278278
closeReq[keys.name] = path
279-
_ = try? await self.sourcekitd.send(closeReq)
279+
_ = try? await self.sourcekitd.send(closeReq, fileContents: nil)
280280

281281
let openReq = SKDRequestDictionary(sourcekitd: self.sourcekitd)
282282
openReq[keys.request] = self.requests.editor_open
@@ -286,7 +286,7 @@ extension SwiftLanguageServer {
286286
openReq[keys.compilerargs] = compileCmd.compilerArgs
287287
}
288288

289-
_ = try? await self.sourcekitd.send(openReq)
289+
_ = try? await self.sourcekitd.send(openReq, fileContents: snapshot.text)
290290

291291
publishDiagnosticsIfNeeded(for: snapshot.uri)
292292
}
@@ -336,7 +336,7 @@ extension SwiftLanguageServer {
336336
req[keys.compilerargs] = compilerArgs
337337
}
338338

339-
_ = try? await self.sourcekitd.send(req)
339+
_ = try? await self.sourcekitd.send(req, fileContents: snapshot.text)
340340
publishDiagnosticsIfNeeded(for: note.textDocument.uri)
341341
}
342342

@@ -354,7 +354,7 @@ extension SwiftLanguageServer {
354354
req[keys.request] = self.requests.editor_close
355355
req[keys.name] = uri.pseudoPath
356356

357-
_ = try? await self.sourcekitd.send(req)
357+
_ = try? await self.sourcekitd.send(req, fileContents: nil)
358358
}
359359

360360
/// Cancels any in-flight tasks to send a `PublishedDiagnosticsNotification` after edits.
@@ -464,7 +464,7 @@ extension SwiftLanguageServer {
464464
req[keys.length] = edit.length
465465
req[keys.sourcetext] = edit.replacement
466466
do {
467-
_ = try await self.sourcekitd.send(req)
467+
_ = try await self.sourcekitd.send(req, fileContents: nil)
468468
} catch {
469469
fatalError("failed to apply edit")
470470
}
@@ -639,7 +639,7 @@ extension SwiftLanguageServer {
639639
skreq[keys.compilerargs] = compileCommand.compilerArgs
640640
}
641641

642-
let dict = try await self.sourcekitd.send(skreq)
642+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
643643

644644
guard let results: SKDResponseArray = dict[self.keys.results] else {
645645
return []
@@ -1090,7 +1090,7 @@ extension SwiftLanguageServer {
10901090
// FIXME: SourceKit should probably cache this for us.
10911091
skreq[keys.compilerargs] = buildSettings.compilerArgs
10921092

1093-
let dict = try await self.sourcekitd.send(skreq)
1093+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
10941094

10951095
try Task.checkCancellation()
10961096
guard (try? documentManager.latestSnapshot(req.textDocument.uri).id) == snapshot.id else {

Sources/SourceKitLSP/Swift/VariableTypeInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ extension SwiftLanguageServer {
104104
skreq[keys.compilerargs] = compileCommand.compilerArgs
105105
}
106106

107-
let dict = try await self.sourcekitd.send(skreq)
107+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
108108
guard let skVariableTypeInfos: SKDResponseArray = dict[keys.variable_type_list] else {
109109
return []
110110
}

Tests/SourceKitDTests/SourceKitDTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ final class SourceKitDTests: XCTestCase {
8888
args.append(path)
8989
req[keys.compilerargs] = args
9090

91-
_ = try await sourcekitd.send(req)
91+
_ = try await sourcekitd.send(req, fileContents: nil)
9292

9393
try await fulfillmentOfOrThrow([expectation1, expectation2])
9494

9595
let close = SKDRequestDictionary(sourcekitd: sourcekitd)
9696
close[keys.request] = sourcekitd.requests.editor_close
9797
close[keys.name] = path
98-
_ = try await sourcekitd.send(close)
98+
_ = try await sourcekitd.send(close, fileContents: nil)
9999
}
100100
}
101101

0 commit comments

Comments
 (0)