Skip to content

Commit fde7d70

Browse files
committed
Revert "Merge pull request #2137 from ahoppen/terminate-unresponsive-clangd-sourcekitd"
This change is too risky for 6.2 at this point.
1 parent 958a07a commit fde7d70

File tree

19 files changed

+84
-377
lines changed

19 files changed

+84
-377
lines changed

Documentation/Configuration File.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,3 @@ The structure of the file is currently not guaranteed to be stable. Options may
5858
- `swiftPublishDiagnosticsDebounceDuration: number`: The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and sending a `PublishDiagnosticsNotification`.
5959
- `workDoneProgressDebounceDuration: number`: When a task is started that should be displayed to the client as a work done progress, how many milliseconds to wait before actually starting the work done progress. This prevents flickering of the work done progress in the client for short-lived index tasks which end within this duration.
6060
- `sourcekitdRequestTimeout: number`: The maximum duration that a sourcekitd request should be allowed to execute before being declared as timed out. In general, editors should cancel requests that they are no longer interested in, but in case editors don't cancel requests, this ensures that a long-running non-cancelled request is not blocking sourcekitd and thus most semantic functionality. In particular, VS Code does not cancel the semantic tokens request, which can cause a long-running AST build that blocks sourcekitd.
61-
- `semanticServiceRestartTimeout: number`: If a request to sourcekitd or clangd exceeds this timeout, we assume that the semantic service provider is hanging for some reason and won't recover. To restore semantic functionality, we terminate and restart it.

Sources/BuildSystemIntegration/ExternalBuildSystemAdapter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ actor ExternalBuildSystemAdapter {
185185
protocol: bspRegistry,
186186
stderrLoggingCategory: "bsp-server-stderr",
187187
client: messagesToSourceKitLSPHandler,
188-
terminationHandler: { [weak self] terminationReason in
188+
terminationHandler: { [weak self] terminationStatus in
189189
guard let self else {
190190
return
191191
}
192-
if terminationReason != .exited(exitCode: 0) {
192+
if terminationStatus != 0 {
193193
Task {
194194
await orLog("Restarting BSP server") {
195195
try await self.handleBspServerCrash()

Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ import struct CDispatch.dispatch_fd_t
3131
/// For example, inside a language server, the `JSONRPCConnection` takes the language service implementation as its
3232
// `receiveHandler` and itself provides the client connection for sending notifications and callbacks.
3333
public final class JSONRPCConnection: Connection {
34-
public enum TerminationReason: Sendable, Equatable {
35-
/// The process on the other end of the `JSONRPCConnection` terminated with the given exit code.
36-
case exited(exitCode: Int32)
37-
38-
/// The process on the other end of the `JSONRPCConnection` terminated with a signal. The signal that it terminated
39-
/// with is not known.
40-
case uncaughtSignal
41-
}
4234

4335
/// A name of the endpoint for this connection, used for logging, e.g. `clangd`.
4436
private let name: String
@@ -206,7 +198,7 @@ public final class JSONRPCConnection: Connection {
206198
protocol messageRegistry: MessageRegistry,
207199
stderrLoggingCategory: String,
208200
client: MessageHandler,
209-
terminationHandler: @Sendable @escaping (_ terminationReason: TerminationReason) -> Void
201+
terminationHandler: @Sendable @escaping (_ terminationStatus: Int32) -> Void
210202
) throws -> (connection: JSONRPCConnection, process: Process) {
211203
let clientToServer = Pipe()
212204
let serverToClient = Pipe()
@@ -246,22 +238,10 @@ public final class JSONRPCConnection: Connection {
246238
process.terminationHandler = { process in
247239
logger.log(
248240
level: process.terminationReason == .exit ? .default : .error,
249-
"\(name) exited: \(process.terminationReason.rawValue) \(process.terminationStatus)"
241+
"\(name) exited: \(String(reflecting: process.terminationReason)) \(process.terminationStatus)"
250242
)
251243
connection.close()
252-
let terminationReason: TerminationReason
253-
switch process.terminationReason {
254-
case .exit:
255-
terminationReason = .exited(exitCode: process.terminationStatus)
256-
case .uncaughtSignal:
257-
terminationReason = .uncaughtSignal
258-
@unknown default:
259-
logger.fault(
260-
"Process terminated with unknown termination reason: \(process.terminationReason.rawValue, privacy: .public)"
261-
)
262-
terminationReason = .exited(exitCode: 0)
263-
}
264-
terminationHandler(terminationReason)
244+
terminationHandler(process.terminationStatus)
265245
}
266246
try process.run()
267247

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -422,17 +422,6 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
422422
return .seconds(120)
423423
}
424424

425-
/// If a request to sourcekitd or clangd exceeds this timeout, we assume that the semantic service provider is hanging
426-
/// for some reason and won't recover. To restore semantic functionality, we terminate and restart it.
427-
public var semanticServiceRestartTimeout: Double? = nil
428-
429-
public var semanticServiceRestartTimeoutOrDefault: Duration {
430-
if let semanticServiceRestartTimeout {
431-
return .seconds(semanticServiceRestartTimeout)
432-
}
433-
return .seconds(300)
434-
}
435-
436425
public init(
437426
swiftPM: SwiftPMOptions? = .init(),
438427
fallbackBuildSystem: FallbackBuildSystemOptions? = .init(),
@@ -450,8 +439,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
450439
experimentalFeatures: Set<ExperimentalFeature>? = nil,
451440
swiftPublishDiagnosticsDebounceDuration: Double? = nil,
452441
workDoneProgressDebounceDuration: Double? = nil,
453-
sourcekitdRequestTimeout: Double? = nil,
454-
semanticServiceRestartTimeout: Double? = nil
442+
sourcekitdRequestTimeout: Double? = nil
455443
) {
456444
self.swiftPM = swiftPM
457445
self.fallbackBuildSystem = fallbackBuildSystem
@@ -470,7 +458,6 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
470458
self.swiftPublishDiagnosticsDebounceDuration = swiftPublishDiagnosticsDebounceDuration
471459
self.workDoneProgressDebounceDuration = workDoneProgressDebounceDuration
472460
self.sourcekitdRequestTimeout = sourcekitdRequestTimeout
473-
self.semanticServiceRestartTimeout = semanticServiceRestartTimeout
474461
}
475462

476463
public init?(fromLSPAny lspAny: LSPAny?) throws {
@@ -530,8 +517,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
530517
?? base.swiftPublishDiagnosticsDebounceDuration,
531518
workDoneProgressDebounceDuration: override?.workDoneProgressDebounceDuration
532519
?? base.workDoneProgressDebounceDuration,
533-
sourcekitdRequestTimeout: override?.sourcekitdRequestTimeout ?? base.sourcekitdRequestTimeout,
534-
semanticServiceRestartTimeout: override?.semanticServiceRestartTimeout ?? base.semanticServiceRestartTimeout
520+
sourcekitdRequestTimeout: override?.sourcekitdRequestTimeout ?? base.sourcekitdRequestTimeout
535521
)
536522
}
537523

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ package actor SkipUnless {
265265
sourcekitd.keys.useNewAPI: 1
266266
],
267267
]),
268-
timeout: defaultTimeoutDuration
268+
timeout: defaultTimeoutDuration,
269+
fileContents: nil
269270
)
270271
return response[sourcekitd.keys.useNewAPI] == 1
271272
} catch {

Sources/SKTestSupport/SourceKitD+send.swift

Lines changed: 0 additions & 29 deletions
This file was deleted.

Sources/SourceKitD/SourceKitD.swift

Lines changed: 26 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,7 @@ package actor SourceKitD {
162162
/// List of notification handlers that will be called for each notification.
163163
private var notificationHandlers: [WeakSKDNotificationHandler] = []
164164

165-
/// List of hooks that should be executed and that need to finish executing before a request is sent to sourcekitd.
166-
private var preRequestHandlingHooks: [UUID: @Sendable (SKDRequestDictionary) async -> Void] = [:]
167-
168-
/// List of hooks that should be executed after a request sent to sourcekitd.
165+
/// List of hooks that should be executed for every request sent to sourcekitd.
169166
private var requestHandlingHooks: [UUID: (SKDRequestDictionary) -> Void] = [:]
170167

171168
package static func getOrCreate(
@@ -268,30 +265,6 @@ package actor SourceKitD {
268265
notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler })
269266
}
270267

271-
/// Execute `body` and invoke `hook` for every sourcekitd request that is sent during the execution time of `body`.
272-
///
273-
/// Note that `hook` will not only be executed for requests sent *by* body but this may also include sourcekitd
274-
/// requests that were sent by other clients of the same `DynamicallyLoadedSourceKitD` instance that just happen to
275-
/// send a request during that time.
276-
///
277-
/// This is intended for testing only.
278-
package func withPreRequestHandlingHook(
279-
body: () async throws -> Void,
280-
hook: @escaping @Sendable (SKDRequestDictionary) async -> Void
281-
) async rethrows {
282-
let id = UUID()
283-
preRequestHandlingHooks[id] = hook
284-
defer { preRequestHandlingHooks[id] = nil }
285-
try await body()
286-
}
287-
288-
func willSend(request: SKDRequestDictionary) async {
289-
let request = request
290-
for hook in preRequestHandlingHooks.values {
291-
await hook(request)
292-
}
293-
}
294-
295268
/// Execute `body` and invoke `hook` for every sourcekitd request that is sent during the execution time of `body`.
296269
///
297270
/// Note that `hook` will not only be executed for requests sent *by* body but this may also include sourcekitd
@@ -324,56 +297,37 @@ package actor SourceKitD {
324297
package func send(
325298
_ request: SKDRequestDictionary,
326299
timeout: Duration,
327-
restartTimeout: Duration,
328300
fileContents: String?
329301
) async throws -> SKDResponseDictionary {
330302
let sourcekitdResponse = try await withTimeout(timeout) {
331-
let restartTimeoutHandle = TimeoutHandle()
332-
do {
333-
return try await withTimeout(restartTimeout, handle: restartTimeoutHandle) {
334-
await self.willSend(request: request)
335-
return try await withCancellableCheckedThrowingContinuation { (continuation) -> SourceKitDRequestHandle? in
336-
logger.info(
337-
"""
338-
Sending sourcekitd request:
339-
\(request.forLogging)
340-
"""
341-
)
342-
var handle: sourcekitd_api_request_handle_t? = nil
343-
self.api.send_request(request.dict, &handle) { response in
344-
continuation.resume(returning: SKDResponse(response!, sourcekitd: self))
345-
}
346-
Task {
347-
await self.didSend(request: request)
348-
}
349-
if let handle {
350-
return SourceKitDRequestHandle(handle: handle)
351-
}
352-
return nil
353-
} cancel: { (handle: SourceKitDRequestHandle?) in
354-
if let handle {
355-
logger.info(
356-
"""
357-
Cancelling sourcekitd request:
358-
\(request.forLogging)
359-
"""
360-
)
361-
self.api.cancel_request(handle.handle)
362-
}
363-
}
303+
return try await withCancellableCheckedThrowingContinuation { (continuation) -> SourceKitDRequestHandle? in
304+
logger.info(
305+
"""
306+
Sending sourcekitd request:
307+
\(request.forLogging)
308+
"""
309+
)
310+
var handle: sourcekitd_api_request_handle_t? = nil
311+
self.api.send_request(request.dict, &handle) { response in
312+
continuation.resume(returning: SKDResponse(response!, sourcekitd: self))
364313
}
365-
} catch let error as TimeoutError where error.handle == restartTimeoutHandle {
366-
if !self.path.lastPathComponent.contains("InProc") {
367-
logger.fault(
368-
"Did not receive reply from sourcekitd after \(restartTimeout, privacy: .public). Terminating and restarting sourcekitd."
369-
)
370-
await self.crash()
371-
} else {
372-
logger.fault(
373-
"Did not receive reply from sourcekitd after \(restartTimeout, privacy: .public). Not terminating sourcekitd because it is run in-process."
314+
Task {
315+
await self.didSend(request: request)
316+
}
317+
if let handle {
318+
return SourceKitDRequestHandle(handle: handle)
319+
}
320+
return nil
321+
} cancel: { (handle: SourceKitDRequestHandle?) in
322+
if let handle {
323+
logger.info(
324+
"""
325+
Cancelling sourcekitd request:
326+
\(request.forLogging)
327+
"""
374328
)
329+
self.api.cancel_request(handle.handle)
375330
}
376-
throw error
377331
}
378332
}
379333

@@ -412,13 +366,6 @@ package actor SourceKitD {
412366

413367
return dict
414368
}
415-
416-
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)
421-
}
422369
}
423370

424371
/// A sourcekitd notification handler in a class to allow it to be uniquely referenced.

Sources/SourceKitD/SourceKitDRegistry.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ package actor SourceKitDRegistry<SourceKitDType: AnyObject> {
3030
private var active: [URL: (pluginPaths: PluginPaths?, sourcekitd: SourceKitDType)] = [:]
3131

3232
/// Instances that have been unregistered, but may be resurrected if accessed before destruction.
33-
private var cemetery: [URL: (pluginPaths: PluginPaths?, sourcekitd: WeakSourceKitD<SourceKitDType>)] = [:]
33+
private var cemetary: [URL: (pluginPaths: PluginPaths?, sourcekitd: WeakSourceKitD<SourceKitDType>)] = [:]
3434

3535
/// Initialize an empty registry.
3636
package init() {}
@@ -49,8 +49,8 @@ package actor SourceKitDRegistry<SourceKitDType: AnyObject> {
4949
}
5050
return existing.sourcekitd
5151
}
52-
if let resurrected = cemetery[key], let resurrectedSourcekitD = resurrected.sourcekitd.value {
53-
cemetery[key] = nil
52+
if let resurrected = cemetary[key], let resurrectedSourcekitD = resurrected.sourcekitd.value {
53+
cemetary[key] = nil
5454
if resurrected.pluginPaths != pluginPaths {
5555
logger.fault(
5656
"Already created SourceKitD with plugin paths \(resurrected.pluginPaths?.forLogging), now requesting incompatible plugin paths \(pluginPaths.forLogging)"
@@ -74,8 +74,8 @@ package actor SourceKitDRegistry<SourceKitDType: AnyObject> {
7474
package func remove(_ key: URL) -> SourceKitDType? {
7575
let existing = active.removeValue(forKey: key)
7676
if let existing = existing {
77-
assert(self.cemetery[key]?.sourcekitd.value == nil)
78-
cemetery[key] = (existing.pluginPaths, WeakSourceKitD(value: existing.sourcekitd))
77+
assert(self.cemetary[key]?.sourcekitd.value == nil)
78+
cemetary[key] = (existing.pluginPaths, WeakSourceKitD(value: existing.sourcekitd))
7979
}
8080
return existing?.sourcekitd
8181
}

0 commit comments

Comments
 (0)