Skip to content

Commit b5d7186

Browse files
Fix queue feedback and clean up memory
1 parent 0fe73ef commit b5d7186

File tree

3 files changed

+95
-53
lines changed

3 files changed

+95
-53
lines changed

CodeEdit/Features/LSP/Registry/InstallationQueueManager.swift

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,81 +16,93 @@ class InstallationQueueManager {
1616
/// Queue of pending installations
1717
private var installationQueue: [(RegistryItem, (Result<Void, Error>) -> Void)] = []
1818
/// Currently running installations
19-
private var runningInstallations: Int = 0
19+
private var runningInstallations: Set<String> = []
2020
/// Installation status dictionary
2121
private var installationStatus: [String: PackageInstallationStatus] = [:]
2222

23-
private init() {}
24-
2523
/// Add a package to the installation queue
2624
func queueInstallation(package: RegistryItem, completion: @escaping (Result<Void, Error>) -> Void) {
27-
installationStatus[package.name] = .queued
28-
installationQueue.append((package, completion))
29-
processNextInstallations()
25+
// If we're already at max capacity and this isn't already running, mark as queued
26+
if runningInstallations.count >= maxConcurrentInstallations && !runningInstallations.contains(package.name) {
27+
installationStatus[package.name] = .queued
28+
installationQueue.append((package, completion))
3029

31-
// Notify UI that package is queued
30+
// Notify UI that package is queued
31+
DispatchQueue.main.async {
32+
NotificationCenter.default.post(
33+
name: .installationStatusChanged,
34+
object: nil,
35+
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.queued]
36+
)
37+
}
38+
} else {
39+
startInstallation(package: package, completion: completion)
40+
}
41+
}
42+
43+
/// Starts the actual installation process for a package
44+
private func startInstallation(package: RegistryItem, completion: @escaping (Result<Void, Error>) -> Void) {
45+
installationStatus[package.name] = .installing
46+
runningInstallations.insert(package.name)
47+
48+
// Notify UI that installation is now in progress
3249
DispatchQueue.main.async {
3350
NotificationCenter.default.post(
3451
name: .installationStatusChanged,
3552
object: nil,
36-
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.queued]
53+
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installing]
3754
)
3855
}
56+
57+
Task {
58+
do {
59+
try await RegistryManager.shared.installPackage(package: package)
60+
61+
// Notify UI that installation is complete
62+
installationStatus[package.name] = .installed
63+
DispatchQueue.main.async {
64+
NotificationCenter.default.post(
65+
name: .installationStatusChanged,
66+
object: nil,
67+
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installed]
68+
)
69+
completion(.success(()))
70+
}
71+
} catch {
72+
// Notify UI that installation failed
73+
installationStatus[package.name] = .failed(error)
74+
DispatchQueue.main.async {
75+
NotificationCenter.default.post(
76+
name: .installationStatusChanged,
77+
object: nil,
78+
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.failed(error)]
79+
)
80+
completion(.failure(error))
81+
}
82+
}
83+
84+
runningInstallations.remove(package.name)
85+
processNextInstallations()
86+
}
3987
}
4088

4189
/// Process next installations from the queue if possible
4290
private func processNextInstallations() {
43-
while runningInstallations < maxConcurrentInstallations && !installationQueue.isEmpty {
91+
while runningInstallations.count < maxConcurrentInstallations && !installationQueue.isEmpty {
4492
let (package, completion) = installationQueue.removeFirst()
45-
runningInstallations += 1
46-
installationStatus[package.name] = .installing
47-
48-
// Notify UI that installation is now in progress
49-
DispatchQueue.main.async {
50-
NotificationCenter.default.post(
51-
name: .installationStatusChanged,
52-
object: nil,
53-
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installing]
54-
)
93+
if runningInstallations.contains(package.name) {
94+
continue
5595
}
5696

57-
Task {
58-
do {
59-
try await RegistryManager.shared.installPackage(package: package)
60-
61-
// Notify UI that installation is complete
62-
installationStatus[package.name] = .installed
63-
DispatchQueue.main.async {
64-
NotificationCenter.default.post(
65-
name: .installationStatusChanged,
66-
object: nil,
67-
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installed]
68-
)
69-
completion(.success(()))
70-
}
71-
} catch {
72-
// Notify UI that installation failed
73-
installationStatus[package.name] = .failed(error)
74-
DispatchQueue.main.async {
75-
NotificationCenter.default.post(
76-
name: .installationStatusChanged,
77-
object: nil,
78-
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.failed(error)]
79-
)
80-
completion(.failure(error))
81-
}
82-
}
83-
84-
runningInstallations -= 1
85-
processNextInstallations()
86-
}
97+
startInstallation(package: package, completion: completion)
8798
}
8899
}
89100

90101
/// Cancel an installation if it's in the queue
91102
func cancelInstallation(packageName: String) {
92103
installationQueue.removeAll { $0.0.name == packageName }
93104
installationStatus[packageName] = .cancelled
105+
runningInstallations.remove(packageName)
94106

95107
// Notify UI that installation was cancelled
96108
DispatchQueue.main.async {
@@ -100,12 +112,43 @@ class InstallationQueueManager {
100112
userInfo: ["packageName": packageName, "status": PackageInstallationStatus.cancelled]
101113
)
102114
}
115+
processNextInstallations()
103116
}
104117

105118
/// Get the current status of an installation
106119
func getInstallationStatus(packageName: String) -> PackageInstallationStatus {
107120
return installationStatus[packageName] ?? .notQueued
108121
}
122+
123+
/// Cleans up installation status by removing completed or failed installations
124+
func cleanUpInstallationStatus() {
125+
let statusKeys = installationStatus.keys.map { $0 }
126+
for packageName in statusKeys {
127+
if let status = installationStatus[packageName] {
128+
switch status {
129+
case .installed, .failed, .cancelled:
130+
installationStatus.removeValue(forKey: packageName)
131+
case .queued, .installing, .notQueued:
132+
break
133+
}
134+
}
135+
}
136+
137+
// If an item is in runningInstallations but not in an active state in the status dictionary,
138+
// it might be a stale reference
139+
let currentRunning = runningInstallations.map { $0 }
140+
for packageName in currentRunning {
141+
let status = installationStatus[packageName]
142+
if status != .installing {
143+
runningInstallations.remove(packageName)
144+
}
145+
}
146+
147+
// Check for orphaned queue items
148+
installationQueue = installationQueue.filter { item, _ in
149+
return installationStatus[item.name] == .queued
150+
}
151+
}
109152
}
110153

111154
/// Status of a package installation

CodeEdit/Features/LSP/Registry/RegistryManager.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// Created by Abe Malla on 1/29/25.
66
//
77

8-
import Combine
98
import Foundation
109
import ZIPFoundation
1110

@@ -113,7 +112,6 @@ final class RegistryManager {
113112
func removeLanguageServer(packageName: String) async throws {
114113
let packageName = packageName.removingPercentEncoding ?? packageName
115114
let packageDirectory = installPath.appending(path: packageName)
116-
print("Removing \(packageDirectory)")
117115

118116
guard FileManager.default.fileExists(atPath: packageDirectory.path) else {
119117
installedLanguageServers.removeValue(forKey: packageName)

CodeEdit/Features/Settings/Pages/Extensions/LanguageServersView.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ struct LanguageServersView: View {
1212
@State private var installationFailure: InstallationFailure?
1313
@State private var registryItems: [RegistryItem] = []
1414
@State private var isLoading = true
15-
@State private var didRemoveError = false
16-
@State private var removalFailure: InstallationFailure?
1715

1816
var body: some View {
1917
SettingsForm {
@@ -59,6 +57,9 @@ struct LanguageServersView: View {
5957
.onReceive(NotificationCenter.default.publisher(for: .RegistryUpdatedNotification)) { _ in
6058
loadRegistryItems()
6159
}
60+
.onDisappear {
61+
InstallationQueueManager.shared.cleanUpInstallationStatus()
62+
}
6263
.alert(
6364
"Installation Failed",
6465
isPresented: $didError,

0 commit comments

Comments
 (0)