From b20f5c434a248bc3f11caefbb5a6b84c88985ee2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 05:34:23 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Replace=20actors=20with=20s?= =?UTF-8?q?tructs=20for=20truly=20parallel=20file=20scanning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Swift's structured concurrency, using `actor` to isolate a `withTaskGroup` inadvertently serializes its child tasks if they inherit the actor context. Since `CacheScanner` and `NodeModulesScanner` only rely on thread-safe dependencies (`FileManager`), converting them to `struct`s prevents this serialization and allows parallel file-system scanning as originally intended. Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/bolt.md | 3 +++ Sources/Cacheout/Scanner/CacheScanner.swift | 8 ++++---- Sources/Cacheout/Scanner/NodeModulesScanner.swift | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..2f39305 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-03-30 - Replace actors with structs for parallel TaskGroups +**Learning:** In Swift structured concurrency, using an `actor` to manage a `withTaskGroup` where tasks invoke synchronous, blocking I/O (like `FileManager` operations) directly on the actor inadvertently serializes the tasks, preventing parallelism. +**Action:** For stateless components interacting with thread-safe dependencies (like `FileManager`), use `struct`s or `nonisolated` methods to allow tasks in a `TaskGroup` to execute concurrently across threads. diff --git a/Sources/Cacheout/Scanner/CacheScanner.swift b/Sources/Cacheout/Scanner/CacheScanner.swift index 3ce3e9c..07ec84b 100644 --- a/Sources/Cacheout/Scanner/CacheScanner.swift +++ b/Sources/Cacheout/Scanner/CacheScanner.swift @@ -1,13 +1,13 @@ /// # CacheScanner — Parallel Cache Category Scanner /// -/// An `actor` that scans all registered cache categories concurrently using +/// A `struct` that scans all registered cache categories concurrently using /// Swift's structured concurrency (`TaskGroup`). Each category is scanned in /// its own child task for maximum parallelism. /// /// ## Thread Safety /// -/// Uses the `actor` isolation model to ensure thread-safe access to internal state. -/// All public methods are `async` and can be called from any concurrency context. +/// Uses `struct` instead of `actor` to prevent inadvertently serializing +/// synchronous `FileManager` operations inside the `TaskGroup`. /// /// ## Disk Size Calculation /// @@ -26,7 +26,7 @@ import Foundation -actor CacheScanner { +struct CacheScanner { private let fileManager = FileManager.default func scanAll(_ categories: [CacheCategory]) async -> [ScanResult] { diff --git a/Sources/Cacheout/Scanner/NodeModulesScanner.swift b/Sources/Cacheout/Scanner/NodeModulesScanner.swift index 3ed4d8c..2dce2ce 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -1,6 +1,6 @@ /// # NodeModulesScanner — Recursive node_modules Finder /// -/// An `actor` that recursively searches common developer project directories +/// A `struct` that recursively searches common developer project directories /// for `node_modules` folders. Designed to find abandoned or stale dependencies /// that consume significant disk space. /// @@ -28,7 +28,7 @@ import Foundation -actor NodeModulesScanner { +struct NodeModulesScanner { private let fileManager = FileManager.default /// Common directories where developers keep projects