Skip to content

Commit e4f03fe

Browse files
Clean Up Child Processes (#1885)
1 parent 9251301 commit e4f03fe

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

CodeEdit/AppDelegate.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88
import SwiftUI
99
import CodeEditSymbols
1010
import CodeEditSourceEditor
11+
import OSLog
1112

1213
final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
14+
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "AppDelegate")
1315
private let updater = SoftwareUpdater()
1416

1517
@Environment(\.openWindow)
1618
var openWindow
1719

20+
@LazyService var lspService: LSPService
21+
1822
func applicationDidFinishLaunching(_ notification: Notification) {
1923
setupServiceContainer()
2024
enableWindowSizeSaveOnQuit()
@@ -115,6 +119,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
115119
}
116120
}
117121

122+
/// Defers the application terminate message until we've finished cleanup.
123+
///
124+
/// All paths _must_ call `NSApplication.shared.reply(toApplicationShouldTerminate: true)` as soon as possible.
125+
///
126+
/// The two things needing deferring are:
127+
/// - Language server cancellation
128+
/// - Outstanding document changes.
129+
///
130+
/// Things that don't need deferring (happen immediately):
131+
/// - Task termination.
132+
/// These are called immediately if no documents need closing, and are called by
133+
/// ``documentController(_:didCloseAll:contextInfo:)`` if there are documents we need to defer for.
134+
///
135+
/// See ``terminateLanguageServers()`` and ``documentController(_:didCloseAll:contextInfo:)`` for deferring tasks.
118136
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
119137
let projects: [String] = CodeEditDocumentController.shared.documents
120138
.compactMap { ($0 as? WorkspaceDocument)?.fileURL?.path }
@@ -128,10 +146,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
128146
didCloseAllSelector: #selector(documentController(_:didCloseAll:contextInfo:)),
129147
contextInfo: nil
130148
)
149+
// `documentController(_:didCloseAll:contextInfo:)` will call `terminateLanguageServers()`
131150
return .terminateLater
132151
}
133152

134-
return .terminateNow
153+
terminateTasks()
154+
terminateLanguageServers()
155+
return .terminateLater
135156
}
136157

137158
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
@@ -224,7 +245,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
224245

225246
@objc
226247
func documentController(_ docController: NSDocumentController, didCloseAll: Bool, contextInfo: Any) {
227-
NSApplication.shared.reply(toApplicationShouldTerminate: didCloseAll)
248+
if didCloseAll {
249+
terminateTasks()
250+
terminateLanguageServers()
251+
}
252+
}
253+
254+
/// Terminates running language servers. Used during app termination to ensure resources are freed.
255+
private func terminateLanguageServers() {
256+
Task {
257+
await lspService.stopAllServers()
258+
await MainActor.run {
259+
NSApplication.shared.reply(toApplicationShouldTerminate: true)
260+
}
261+
}
262+
}
263+
264+
/// Terminates all running tasks. Used during app termination to ensure resources are freed.
265+
private func terminateTasks() {
266+
let documents = CodeEditDocumentController.shared.documents.compactMap({ $0 as? WorkspaceDocument })
267+
documents.forEach { workspace in
268+
workspace.taskManager?.stopAllTasks()
269+
}
228270
}
229271

230272
/// Setup all the services into a ServiceContainer for the application to use.

CodeEdit/Features/LSP/Service/LSPService.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,24 @@ final class LSPService: ObservableObject {
269269
}
270270

271271
/// Goes through all active language servers and attempts to shut them down.
272-
func stopAllServers() async throws {
273-
for key in languageClients.keys {
274-
try await stopServer(forLanguage: key.languageId, workspacePath: key.workspacePath)
272+
func stopAllServers() async {
273+
await withThrowingTaskGroup(of: Void.self) { group in
274+
for (key, server) in languageClients {
275+
group.addTask {
276+
do {
277+
try await server.shutdown()
278+
} catch {
279+
self.logger.error("Shutting down \(key.languageId.rawValue): Error \(error)")
280+
throw error
281+
}
282+
}
283+
}
284+
}
285+
languageClients.removeAll()
286+
eventListeningTasks.forEach { (_, value) in
287+
value.cancel()
275288
}
289+
eventListeningTasks.removeAll()
276290
}
277291
}
278292

0 commit comments

Comments
 (0)