From 2f72f930d546c93fdf123a1e9d99ba72d1445665 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 05:34:52 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20parallelize=20NodeModulesSc?= =?UTF-8?q?anner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marked `findNodeModules` and `directorySize` as `nonisolated` to allow `TaskGroup` subtasks to run in parallel on the global thread pool instead of being serialized on the actor. Combined separate `fileExists` and `attributesOfItem` calls into a single `URL.resourceValues` call. Passed `FileManager` as an argument to maintain dependency injection. Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../Cacheout/Scanner/NodeModulesScanner.swift | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..7cbf3eb --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2026-05-04 - Fix Actor Serialization in TaskGroup +**Learning:** When an actor creates a `TaskGroup` and its subtasks call an `isolated` method on the same actor, execution is silently serialized on the actor's executor, destroying intended parallelism. Additionally, separate `FileManager.fileExists` and `attributesOfItem` calls create redundant syscalls. +**Action:** Mark computationally intensive or state-independent methods as `nonisolated` so they run on the global concurrent pool. Use `URL.resourceValues(forKeys:)` for combined file existence and attribute checking in a single syscall. diff --git a/Sources/Cacheout/Scanner/NodeModulesScanner.swift b/Sources/Cacheout/Scanner/NodeModulesScanner.swift index 3ed4d8c..ac28a77 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -62,7 +62,7 @@ actor NodeModulesScanner { let rootURL = home.appendingPathComponent(root) guard fileManager.fileExists(atPath: rootURL.path) else { continue } group.addTask { - await self.findNodeModules(in: rootURL, maxDepth: maxDepth) + await self.findNodeModules(in: rootURL, fileManager: self.fileManager, maxDepth: maxDepth) } } for await items in group { @@ -77,18 +77,18 @@ 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, fileManager: FileManager, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { guard currentDepth < maxDepth else { return [] } var results: [NodeModulesItem] = [] let nodeModulesURL = directory.appendingPathComponent("node_modules") // Check if this directory contains node_modules - var isDir: ObjCBool = false - if fileManager.fileExists(atPath: nodeModulesURL.path, isDirectory: &isDir), isDir.boolValue { - let size = directorySize(at: nodeModulesURL) + if let values = try? nodeModulesURL.resourceValues(forKeys: [.isDirectoryKey, .contentModificationDateKey]), + values.isDirectory == true { + let size = directorySize(at: nodeModulesURL, fileManager: fileManager) if size > 0 { - let lastMod = try? fileManager.attributesOfItem(atPath: nodeModulesURL.path)[.modificationDate] as? Date + let lastMod = values.contentModificationDate let projectName = directory.lastPathComponent results.append(NodeModulesItem( projectName: projectName, @@ -113,14 +113,14 @@ actor NodeModulesScanner { let name = item.lastPathComponent guard !Self.skipDirs.contains(name) else { continue } guard (try? item.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true else { continue } - let subResults = await findNodeModules(in: item, maxDepth: maxDepth, currentDepth: currentDepth + 1) + let subResults = await findNodeModules(in: item, fileManager: fileManager, maxDepth: maxDepth, currentDepth: currentDepth + 1) results.append(contentsOf: subResults) } return results } - private func directorySize(at url: URL) -> Int64 { + nonisolated private func directorySize(at url: URL, fileManager: FileManager) -> Int64 { var total: Int64 = 0 guard let enumerator = fileManager.enumerator( at: url,