Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@
## 2024-05-25 - Efficient Dictionary Initialization
**Learning:** Building a dictionary via a standard `for` loop by inserting elements one by one can cause unnecessary overhead due to repeated mutations. `reduce(into: [:])` is highly optimized in Swift to build collections without creating intermediate copies.
**Action:** Use `reduce(into: [:])` when constructing a dictionary from an array, particularly when uniqueness checks or transformations are required.
## 2024-05-18 - Cooperative Thread Pool Exhaustion in Bulk I/O
**Learning:** In Swift Concurrency, executing synchronous blocking operations like `FileManager.removeItem` inside a `TaskGroup` without isolation can quickly exhaust the limited cooperative thread pool (which has threads equal to CPU cores), leading to system-wide deadlock or starvation.
**Action:** When parallelizing synchronous blocking I/O calls, wrap them in `Task.detached { ... }.value` to escape the cooperative pool and execute on a background queue, and control concurrency with a sliding window iterator.
## 2024-05-18 - Escaping Cooperative Thread Pool
**Learning:** `Task.detached` executes on the global cooperative thread pool and does *not* safely offload synchronous blocking I/O. Using it with concurrent groups can still exhaust the thread pool.
**Action:** To truly move blocking work (like `FileManager.removeItem`) off the cooperative thread pool in Swift Concurrency, wrap the execution in `withCheckedThrowingContinuation` and dispatch to a background GCD queue (e.g., `DispatchQueue.global(qos: .userInitiated).async`).
57 changes: 52 additions & 5 deletions Sources/Cacheout/Cleaner/CacheCleaner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ actor CacheCleaner {
if moveToTrash {
try await trashDirectory(url)
} else {
try removeContents(of: url)
try await removeContents(of: url, fileManager: fileManager)
}
categoryFreed += result.sizeBytes
} catch {
Expand All @@ -86,7 +86,18 @@ actor CacheCleaner {
if moveToTrash {
try await trashItem(item.nodeModulesPath)
} else {
try fileManager.removeItem(at: item.nodeModulesPath)
let path = item.nodeModulesPath
let fm = fileManager
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
DispatchQueue.global(qos: .userInitiated).async {
do {
try fm.removeItem(at: path)
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
}
}
cleaned.append(("node_modules: \(item.projectName)", item.sizeBytes))
logCleanup(category: "node_modules/\(item.projectName)", bytesFreed: item.sizeBytes)
Expand Down Expand Up @@ -133,12 +144,48 @@ actor CacheCleaner {
}
}

private func removeContents(of url: URL) throws {
nonisolated private func removeContents(of url: URL, fileManager: FileManager) async throws {
let contents = try fileManager.contentsOfDirectory(
at: url, includingPropertiesForKeys: nil
)
for item in contents {
try fileManager.removeItem(at: item)

try await withThrowingTaskGroup(of: Void.self) { group in
var iterator = contents.makeIterator()
let maxConcurrency = 8

for _ in 0..<maxConcurrency {
if let item = iterator.next() {
group.addTask {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
DispatchQueue.global(qos: .userInitiated).async {
do {
try fileManager.removeItem(at: item)
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
}
}
}
}

for try await _ in group {
if let item = iterator.next() {
group.addTask {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
DispatchQueue.global(qos: .userInitiated).async {
do {
try fileManager.removeItem(at: item)
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
}
}
}
}
}
}

Expand Down
Loading