From de09f66e96600989303b7f7120193aa87d68c999 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 05:21:37 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Make=20NodeModulesScanner?= =?UTF-8?q?=20truly=20concurrent=20and=20reduce=20syscalls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../Cacheout/Scanner/NodeModulesScanner.swift | 21 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..e8bc0d5 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-05 - TaskGroup parallelism and URL.resourceValues +**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. Also, `URL.resourceValues(forKeys:)` can perform a single optimized syscall instead of separate `FileManager.fileExists` and `FileManager.attributesOfItem` calls. +**Action:** Mark computationally intensive or state-independent methods as `static` or `nonisolated` so they run on the global concurrent pool, and pass dependencies (like `FileManager` and `skipDirs`) as arguments. Use `URL.resourceValues(forKeys:)` for checking directory existence and attributes simultaneously. diff --git a/Sources/Cacheout/Scanner/NodeModulesScanner.swift b/Sources/Cacheout/Scanner/NodeModulesScanner.swift index 3ed4d8c..70dbd47 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -55,14 +55,15 @@ actor NodeModulesScanner { func scan(maxDepth: Int = 6) async -> [NodeModulesItem] { let home = fileManager.homeDirectoryForCurrentUser var allItems: [NodeModulesItem] = [] + let currentFileManager = self.fileManager // Scan each search root in parallel await withTaskGroup(of: [NodeModulesItem].self) { group in for root in Self.searchRoots { let rootURL = home.appendingPathComponent(root) - guard fileManager.fileExists(atPath: rootURL.path) else { continue } + guard currentFileManager.fileExists(atPath: rootURL.path) else { continue } group.addTask { - await self.findNodeModules(in: rootURL, maxDepth: maxDepth) + await Self.findNodeModules(in: rootURL, fileManager: currentFileManager, skipDirs: Self.skipDirs, maxDepth: maxDepth) } } for await items in group { @@ -77,25 +78,23 @@ actor NodeModulesScanner { .sorted { $0.sizeBytes > $1.sizeBytes } } - private func findNodeModules(in directory: URL, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { + private static func findNodeModules(in directory: URL, fileManager: FileManager, skipDirs: Set, 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 projectName = directory.lastPathComponent results.append(NodeModulesItem( projectName: projectName, projectPath: directory, nodeModulesPath: nodeModulesURL, sizeBytes: size, - lastModified: lastMod + lastModified: values.contentModificationDate )) } // Don't recurse into projects that have node_modules — they won't have nested projects @@ -111,16 +110,16 @@ actor NodeModulesScanner { for item in contents { let name = item.lastPathComponent - guard !Self.skipDirs.contains(name) else { continue } + guard !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, skipDirs: skipDirs, maxDepth: maxDepth, currentDepth: currentDepth + 1) results.append(contentsOf: subResults) } return results } - private func directorySize(at url: URL) -> Int64 { + private static func directorySize(at url: URL, fileManager: FileManager) -> Int64 { var total: Int64 = 0 guard let enumerator = fileManager.enumerator( at: url,