diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..b58b7de --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Actor Serialization in TaskGroup +**Learning:** Using an `actor` to manage a `withTaskGroup` where child tasks invoke synchronous, blocking I/O (like `FileManager` operations) directly on the actor inadvertently serializes the tasks on the actor's context, preventing true parallelism and drastically reducing throughput. +**Action:** Change the `actor` to a `struct` for stateless components interacting with thread-safe dependencies (like `FileManager.default`) to allow tasks to execute concurrently across multiple threads. diff --git a/Sources/Cacheout/Scanner/CacheScanner.swift b/Sources/Cacheout/Scanner/CacheScanner.swift index 3ce3e9c..857cb41 100644 --- a/Sources/Cacheout/Scanner/CacheScanner.swift +++ b/Sources/Cacheout/Scanner/CacheScanner.swift @@ -26,7 +26,14 @@ import Foundation -actor CacheScanner { +// ⚡ Bolt Optimization: +// Changed from `actor` to `struct` because this component is stateless and only +// interacts with thread-safe dependencies (FileManager.default). +// Using an actor with `withTaskGroup` where child tasks invoke methods on `self` +// causes the tasks to serialize on the actor's context, destroying parallelism. +// As a struct, the child tasks can now execute concurrently across threads, +// significantly improving scan throughput for large file systems. +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..09003bc 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -28,7 +28,13 @@ import Foundation -actor NodeModulesScanner { +// ⚡ Bolt Optimization: +// Changed from `actor` to `struct` to prevent task serialization. This component +// is stateless (only holds thread-safe FileManager.default). In an actor, child +// tasks in `withTaskGroup` calling methods on `self` are forced to run sequentially. +// As a struct, the recursive filesystem traversals can run truly concurrently +// across multiple threads, drastically reducing scan time for deep hierarchies. +struct NodeModulesScanner { private let fileManager = FileManager.default /// Common directories where developers keep projects