From 37d504b044bb126dad6ac98c980a1433406b922b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 03:46:09 +0000 Subject: [PATCH] Fix TaskGroup serialization in NodeModulesScanner Added the `nonisolated` keyword to `findNodeModules` and `directorySize` in `NodeModulesScanner` to prevent execution serialization on the actor's executor. This allows these computationally intensive methods to run truly concurrently on the global cooperative thread pool when invoked from within a TaskGroup. Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/bolt.md | 3 +++ Sources/Cacheout/Scanner/NodeModulesScanner.swift | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..5ad3160 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-04 - TaskGroup Serialization in Actors +**Learning:** In Swift Concurrency, when an actor creates a `TaskGroup` and its subtasks call an `isolated` method on the same actor, execution is serialized on the actor's executor, destroying parallelism. +**Action:** Mark computationally intensive or state-independent methods called from `TaskGroup` subtasks as `nonisolated` (like `NodeModulesScanner.findNodeModules`) so they run concurrently on the global pool. diff --git a/Sources/Cacheout/Scanner/NodeModulesScanner.swift b/Sources/Cacheout/Scanner/NodeModulesScanner.swift index 3ed4d8c..0306f0e 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -77,7 +77,8 @@ actor NodeModulesScanner { .sorted { $0.sizeBytes > $1.sizeBytes } } - private func findNodeModules(in directory: URL, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { + nonisolated private func findNodeModules(in directory: URL, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { + let fileManager = FileManager.default guard currentDepth < maxDepth else { return [] } var results: [NodeModulesItem] = [] @@ -120,7 +121,8 @@ actor NodeModulesScanner { return results } - private func directorySize(at url: URL) -> Int64 { + nonisolated private func directorySize(at url: URL) -> Int64 { + let fileManager = FileManager.default var total: Int64 = 0 guard let enumerator = fileManager.enumerator( at: url,