Skip to content

Commit dfe244a

Browse files
authored
Merge pull request #366 from ahoppen/clangd-crash-recovery
Re-enable clang crash recovery tests
2 parents e64e207 + e5dedd5 commit dfe244a

File tree

3 files changed

+36
-45
lines changed

3 files changed

+36
-45
lines changed

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer {
7575
/// A callback with which `ClangLanguageServer` can request its owner to reopen all documents in case it has crashed.
7676
private let reopenDocuments: (ToolchainLanguageServer) -> Void
7777

78+
/// While `clangd` is running, its PID.
79+
private var clangdPid: Int32?
80+
7881
/// Creates a language server for the given client referencing the clang binary at the given path.
7982
public init(
8083
client: Connection,
@@ -138,12 +141,12 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer {
138141
process.terminationHandler = { [weak self] process in
139142
log("clangd exited: \(process.terminationReason) \(process.terminationStatus)")
140143
connectionToClangd.close()
141-
if process.terminationStatus != 0 {
142-
if let self = self {
143-
self.queue.async {
144-
self.state = .connectionInterrupted
145-
self.restartClangd()
146-
}
144+
guard let self = self else { return }
145+
self.queue.async {
146+
self.clangdPid = nil
147+
if process.terminationStatus != 0 {
148+
self.state = .connectionInterrupted
149+
self.restartClangd()
147150
}
148151
}
149152
}
@@ -153,6 +156,7 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer {
153156
} else {
154157
process.launch()
155158
}
159+
self.clangdPid = process.processIdentifier
156160
}
157161

158162
/// Restart `clangd` after it has crashed.
@@ -249,6 +253,15 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer {
249253
self.clangd.send(notification)
250254
}
251255
}
256+
257+
func _crash() {
258+
self.queue.async {
259+
// Since `clangd` doesn't have a method to crash it, kill it.
260+
if let pid = self.clangdPid {
261+
kill(pid, SIGKILL)
262+
}
263+
}
264+
}
252265

253266
/// Forward the given `request` to `clangd` by asynchronously switching to `queue` for a thread-safe access to `clangd`.
254267
private func forwardRequestToClangdOnQueue<R>(_ request: Request<R>, _ handler: ((LSPResult<R.Response>) -> Void)? = nil) {

Sources/SourceKitLSP/ToolchainLanguageServer.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,7 @@ public protocol ToolchainLanguageServer: AnyObject {
7979
// MARK: - Other
8080

8181
func executeCommand(_ req: Request<ExecuteCommandRequest>)
82+
83+
/// Crash the language server. Should be used for crash recovery testing only.
84+
func _crash()
8285
}

Tests/SourceKitDTests/CrashRecoveryTests.swift

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ final class CrashRecoveryTests: XCTestCase {
108108
/// Crashes clangd and waits for it to restart
109109
/// - Parameters:
110110
/// - ws: The workspace for which the clangd server shall be crashed
111-
/// - loc: A test location that points to the first line of a given file. This line needs to be otherwise empty.
112-
private func crashClangd(for ws: SKTibsTestWorkspace, firstLineLoc loc: TestLocation) {
113-
let clangdServer = ws.testServer.server!._languageService(for: loc.docUri, .cpp, in: ws.testServer.server!.workspace!)!
111+
/// - document: The URI of a C/C++/... document in the workspace
112+
private func crashClangd(for ws: SKTibsTestWorkspace, document docUri: DocumentURI) {
113+
let clangdServer = ws.testServer.server!._languageService(for: docUri, .cpp, in: ws.testServer.server!.workspace!)!
114114

115115
let clangdCrashed = self.expectation(description: "clangd crashed")
116116
let clangdRestarted = self.expectation(description: "clangd restarted")
@@ -126,21 +126,13 @@ final class CrashRecoveryTests: XCTestCase {
126126
}
127127
}
128128

129-
// Add a pragma to crash clang
130-
let addCrashPragma = TextDocumentContentChangeEvent(range: loc.position..<loc.position, rangeLength: 0, text: "#pragma clang __debug crash\n")
131-
ws.sk.send(DidChangeTextDocumentNotification(textDocument: VersionedTextDocumentIdentifier(loc.docUri, version: 3), contentChanges: [addCrashPragma]))
129+
clangdServer._crash()
132130

133131
self.wait(for: [clangdCrashed], timeout: 5)
134-
135-
// Once clangds has crashed, remove the pragma again to allow it to restart
136-
let removeCrashPragma = TextDocumentContentChangeEvent(range: loc.position..<Position(line: 1, utf16index: 0), rangeLength: 28, text: "")
137-
ws.sk.send(DidChangeTextDocumentNotification(textDocument: VersionedTextDocumentIdentifier(loc.docUri, version: 4), contentChanges: [removeCrashPragma]))
138-
139132
self.wait(for: [clangdRestarted], timeout: 30)
140133
}
141134

142135
func testClangdCrashRecovery() throws {
143-
throw XCTSkip("failing on rebranch - rdar://73717447")
144136
try XCTSkipUnless(longTestsEnabled)
145137

146138
let ws = try! staticSourceKitTibsWorkspace(name: "ClangCrashRecovery")!
@@ -167,7 +159,7 @@ final class CrashRecoveryTests: XCTestCase {
167159

168160
// Crash clangd
169161

170-
crashClangd(for: ws, firstLineLoc: loc)
162+
crashClangd(for: ws, document: loc.docUri)
171163

172164
// Check that we have re-opened the document with the correct in-memory state
173165

@@ -178,7 +170,6 @@ final class CrashRecoveryTests: XCTestCase {
178170
}
179171

180172
func testClangdCrashRecoveryReopensWithCorrectBuildSettings() throws {
181-
throw XCTSkip("failing on rebranch - rdar://73717447")
182173
try XCTSkipUnless(longTestsEnabled)
183174

184175
let ws = try! staticSourceKitTibsWorkspace(name: "ClangCrashRecoveryBuildSettings")!
@@ -199,7 +190,7 @@ final class CrashRecoveryTests: XCTestCase {
199190

200191
// Crash clangd
201192

202-
crashClangd(for: ws, firstLineLoc: loc)
193+
crashClangd(for: ws, document: loc.docUri)
203194

204195
// Check that we have re-opened the document with the correct build settings
205196
// If we did not recover the correct build settings, document highlight would
@@ -212,7 +203,6 @@ final class CrashRecoveryTests: XCTestCase {
212203
}
213204

214205
func testPreventClangdCrashLoop() throws {
215-
throw XCTSkip("failing on rebranch - rdar://73717447")
216206
try XCTSkipUnless(longTestsEnabled)
217207

218208
let ws = try! staticSourceKitTibsWorkspace(name: "ClangCrashRecovery")!
@@ -231,53 +221,38 @@ final class CrashRecoveryTests: XCTestCase {
231221

232222
let clangdCrashed = self.expectation(description: "clangd crashed")
233223
clangdCrashed.assertForOverFulfill = false
234-
// assertForOverFulfill is not working on Linux (SR-12575). Manually keep track if we have already called fulfill on the expectation
235-
var clangdCrashedFulfilled = false
236224

237225
let clangdRestartedFirstTime = self.expectation(description: "clangd restarted for the first time")
238-
239226
let clangdRestartedSecondTime = self.expectation(description: "clangd restarted for the second time")
240-
clangdRestartedSecondTime.assertForOverFulfill = false
241-
// assertForOverFulfill is not working on Linux (SR-12575). Manually keep track if we have already called fulfill on the expectation
242-
var clangdRestartedSecondTimeFulfilled = false
243-
227+
244228
var clangdHasRestartedFirstTime = false
245229

246230
clangdServer.addStateChangeHandler { (oldState, newState) in
247231
switch newState {
248232
case .connectionInterrupted:
249-
if !clangdCrashedFulfilled {
250-
clangdCrashed.fulfill()
251-
clangdCrashedFulfilled = true
252-
}
233+
clangdCrashed.fulfill()
253234
case .connected:
254235
if !clangdHasRestartedFirstTime {
255236
clangdRestartedFirstTime.fulfill()
256237
clangdHasRestartedFirstTime = true
257238
} else {
258-
if !clangdRestartedSecondTimeFulfilled {
259-
clangdRestartedSecondTime.fulfill()
260-
clangdRestartedSecondTimeFulfilled = true
261-
}
239+
clangdRestartedSecondTime.fulfill()
262240
}
263241
default:
264242
break
265243
}
266244
}
267245

268-
// Add a pragma to crash clang
269-
270-
let addCrashPragma = TextDocumentContentChangeEvent(range: loc.position..<loc.position, rangeLength: 0, text: "#pragma clang __debug crash\n")
271-
ws.sk.send(DidChangeTextDocumentNotification(textDocument: VersionedTextDocumentIdentifier(loc.docUri, version: 3), contentChanges: [addCrashPragma]))
246+
clangdServer._crash()
272247

273248
self.wait(for: [clangdCrashed], timeout: 5)
274-
275-
// Clangd has crashed for the first time. Leave the pragma there to crash clangd once again.
276-
277249
self.wait(for: [clangdRestartedFirstTime], timeout: 30)
278250
// Clangd has restarted. Note the date so we can check that the second restart doesn't happen too quickly.
279251
let firstRestartDate = Date()
280-
252+
253+
// Crash clangd again. This time, it should only restart after a delay.
254+
clangdServer._crash()
255+
281256
self.wait(for: [clangdRestartedSecondTime], timeout: 30)
282257
XCTAssert(Date().timeIntervalSince(firstRestartDate) > 5, "Clangd restarted too quickly after crashing twice in a row. We are not preventing crash loops.")
283258
}

0 commit comments

Comments
 (0)