mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
feat: add configurable file watcher performance settings for multi-worktree optimization
Adds two new settings for file watcher performance optimization: - codebaseIndexFileWatcherDebounceMs: Debounce delay for batching file changes (100-5000ms, default 500ms) - codebaseIndexFileWatcherConcurrency: Concurrency limit for file processing (1-20, default 10) These settings help reduce CPU usage when running multiple worktrees simultaneously by allowing users to increase debounce delays and reduce concurrency limits. Changes: - Add new constants and defaults in constants/index.ts - Add new fields to codebase-index types - Update CodeIndexConfigManager to load and expose the new settings - Update FileWatcher to use configurable values - Update service-factory to pass settings to FileWatcher - Add comprehensive tests for all new functionality
This commit is contained in:
parent
503f40241d
commit
605d1c8558
8 changed files with 309 additions and 7 deletions
|
|
@ -12,6 +12,13 @@ export const CODEBASE_INDEX_DEFAULTS = {
|
|||
MAX_SEARCH_SCORE: 1,
|
||||
DEFAULT_SEARCH_MIN_SCORE: 0.4,
|
||||
SEARCH_SCORE_STEP: 0.05,
|
||||
// File watcher performance settings for multi-worktree optimization
|
||||
DEFAULT_FILE_WATCHER_DEBOUNCE_MS: 500,
|
||||
MIN_FILE_WATCHER_DEBOUNCE_MS: 100,
|
||||
MAX_FILE_WATCHER_DEBOUNCE_MS: 5000,
|
||||
DEFAULT_FILE_WATCHER_CONCURRENCY: 10,
|
||||
MIN_FILE_WATCHER_CONCURRENCY: 1,
|
||||
MAX_FILE_WATCHER_CONCURRENCY: 20,
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
|
@ -50,6 +57,17 @@ export const codebaseIndexConfigSchema = z.object({
|
|||
codebaseIndexBedrockProfile: z.string().optional(),
|
||||
// OpenRouter specific fields
|
||||
codebaseIndexOpenRouterSpecificProvider: z.string().optional(),
|
||||
// File watcher performance settings for multi-worktree optimization
|
||||
codebaseIndexFileWatcherDebounceMs: z
|
||||
.number()
|
||||
.min(CODEBASE_INDEX_DEFAULTS.MIN_FILE_WATCHER_DEBOUNCE_MS)
|
||||
.max(CODEBASE_INDEX_DEFAULTS.MAX_FILE_WATCHER_DEBOUNCE_MS)
|
||||
.optional(),
|
||||
codebaseIndexFileWatcherConcurrency: z
|
||||
.number()
|
||||
.min(CODEBASE_INDEX_DEFAULTS.MIN_FILE_WATCHER_CONCURRENCY)
|
||||
.max(CODEBASE_INDEX_DEFAULTS.MAX_FILE_WATCHER_CONCURRENCY)
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
|
||||
|
|
|
|||
|
|
@ -1018,6 +1018,120 @@ describe("CodeIndexConfigManager", () => {
|
|||
expect(maxManager.currentSearchMaxResults).toBe(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe("currentFileWatcherDebounceMs", () => {
|
||||
it("should return user setting when provided", async () => {
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherDebounceMs: 1000, // User setting
|
||||
})
|
||||
|
||||
await configManager.loadConfiguration()
|
||||
expect(configManager.currentFileWatcherDebounceMs).toBe(1000) // User setting
|
||||
})
|
||||
|
||||
it("should return default when no user setting", async () => {
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
// No file watcher debounce setting
|
||||
})
|
||||
|
||||
const newManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await newManager.loadConfiguration()
|
||||
expect(newManager.currentFileWatcherDebounceMs).toBe(500) // Default (DEFAULT_FILE_WATCHER_DEBOUNCE_MS)
|
||||
})
|
||||
|
||||
it("should respect minimum and maximum bounds", async () => {
|
||||
// Test minimum value
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherDebounceMs: 100, // Minimum allowed
|
||||
})
|
||||
|
||||
const minManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await minManager.loadConfiguration()
|
||||
expect(minManager.currentFileWatcherDebounceMs).toBe(100)
|
||||
|
||||
// Test maximum value
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherDebounceMs: 5000, // Maximum allowed
|
||||
})
|
||||
|
||||
const maxManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await maxManager.loadConfiguration()
|
||||
expect(maxManager.currentFileWatcherDebounceMs).toBe(5000)
|
||||
})
|
||||
})
|
||||
|
||||
describe("currentFileWatcherConcurrency", () => {
|
||||
it("should return user setting when provided", async () => {
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherConcurrency: 5, // User setting - lower for multi-worktree
|
||||
})
|
||||
|
||||
await configManager.loadConfiguration()
|
||||
expect(configManager.currentFileWatcherConcurrency).toBe(5) // User setting
|
||||
})
|
||||
|
||||
it("should return default when no user setting", async () => {
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
// No file watcher concurrency setting
|
||||
})
|
||||
|
||||
const newManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await newManager.loadConfiguration()
|
||||
expect(newManager.currentFileWatcherConcurrency).toBe(10) // Default (DEFAULT_FILE_WATCHER_CONCURRENCY)
|
||||
})
|
||||
|
||||
it("should respect minimum and maximum bounds", async () => {
|
||||
// Test minimum value
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherConcurrency: 1, // Minimum allowed
|
||||
})
|
||||
|
||||
const minManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await minManager.loadConfiguration()
|
||||
expect(minManager.currentFileWatcherConcurrency).toBe(1)
|
||||
|
||||
// Test maximum value
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
codebaseIndexFileWatcherConcurrency: 20, // Maximum allowed
|
||||
})
|
||||
|
||||
const maxManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
await maxManager.loadConfiguration()
|
||||
expect(maxManager.currentFileWatcherConcurrency).toBe(20)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("empty/missing API key handling", () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { ApiHandlerOptions } from "../../shared/api"
|
|||
import { ContextProxy } from "../../core/config/ContextProxy"
|
||||
import { EmbedderProvider } from "./interfaces/manager"
|
||||
import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
|
||||
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
|
||||
import {
|
||||
DEFAULT_SEARCH_MIN_SCORE,
|
||||
DEFAULT_MAX_SEARCH_RESULTS,
|
||||
DEFAULT_FILE_WATCHER_DEBOUNCE_MS,
|
||||
DEFAULT_FILE_WATCHER_CONCURRENCY,
|
||||
} from "./constants"
|
||||
import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
|
||||
|
||||
/**
|
||||
|
|
@ -26,6 +31,9 @@ export class CodeIndexConfigManager {
|
|||
private qdrantApiKey?: string
|
||||
private searchMinScore?: number
|
||||
private searchMaxResults?: number
|
||||
// File watcher performance settings for multi-worktree optimization
|
||||
private fileWatcherDebounceMs?: number
|
||||
private fileWatcherConcurrency?: number
|
||||
|
||||
constructor(private readonly contextProxy: ContextProxy) {
|
||||
// Initialize with current configuration to avoid false restart triggers
|
||||
|
|
@ -87,6 +95,10 @@ export class CodeIndexConfigManager {
|
|||
this.searchMinScore = codebaseIndexSearchMinScore
|
||||
this.searchMaxResults = codebaseIndexSearchMaxResults
|
||||
|
||||
// File watcher performance settings
|
||||
this.fileWatcherDebounceMs = codebaseIndexConfig.codebaseIndexFileWatcherDebounceMs
|
||||
this.fileWatcherConcurrency = codebaseIndexConfig.codebaseIndexFileWatcherConcurrency
|
||||
|
||||
// Validate and set model dimension
|
||||
const rawDimension = codebaseIndexConfig.codebaseIndexEmbedderModelDimension
|
||||
if (rawDimension !== undefined && rawDimension !== null) {
|
||||
|
|
@ -460,6 +472,8 @@ export class CodeIndexConfigManager {
|
|||
qdrantApiKey: this.qdrantApiKey,
|
||||
searchMinScore: this.currentSearchMinScore,
|
||||
searchMaxResults: this.currentSearchMaxResults,
|
||||
fileWatcherDebounceMs: this.currentFileWatcherDebounceMs,
|
||||
fileWatcherConcurrency: this.currentFileWatcherConcurrency,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -541,4 +555,24 @@ export class CodeIndexConfigManager {
|
|||
public get currentSearchMaxResults(): number {
|
||||
return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured file watcher debounce delay in milliseconds.
|
||||
* Higher values reduce CPU usage by batching more file changes together.
|
||||
* Useful for multi-worktree scenarios where multiple watchers run simultaneously.
|
||||
* Returns user setting if configured, otherwise returns default (500ms).
|
||||
*/
|
||||
public get currentFileWatcherDebounceMs(): number {
|
||||
return this.fileWatcherDebounceMs ?? DEFAULT_FILE_WATCHER_DEBOUNCE_MS
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured file watcher concurrency limit.
|
||||
* Lower values reduce CPU usage by processing fewer files in parallel.
|
||||
* Useful for multi-worktree scenarios where multiple watchers run simultaneously.
|
||||
* Returns user setting if configured, otherwise returns default (10).
|
||||
*/
|
||||
public get currentFileWatcherConcurrency(): number {
|
||||
return this.fileWatcherConcurrency ?? DEFAULT_FILE_WATCHER_CONCURRENCY
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ export const DEFAULT_MAX_SEARCH_RESULTS = CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH
|
|||
export const QDRANT_CODE_BLOCK_NAMESPACE = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
||||
export const MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024 // 1MB
|
||||
|
||||
/**File Watcher Performance - Configurable for multi-worktree optimization */
|
||||
export const DEFAULT_FILE_WATCHER_DEBOUNCE_MS = CODEBASE_INDEX_DEFAULTS.DEFAULT_FILE_WATCHER_DEBOUNCE_MS
|
||||
export const MIN_FILE_WATCHER_DEBOUNCE_MS = CODEBASE_INDEX_DEFAULTS.MIN_FILE_WATCHER_DEBOUNCE_MS
|
||||
export const MAX_FILE_WATCHER_DEBOUNCE_MS = CODEBASE_INDEX_DEFAULTS.MAX_FILE_WATCHER_DEBOUNCE_MS
|
||||
export const DEFAULT_FILE_WATCHER_CONCURRENCY = CODEBASE_INDEX_DEFAULTS.DEFAULT_FILE_WATCHER_CONCURRENCY
|
||||
export const MIN_FILE_WATCHER_CONCURRENCY = CODEBASE_INDEX_DEFAULTS.MIN_FILE_WATCHER_CONCURRENCY
|
||||
export const MAX_FILE_WATCHER_CONCURRENCY = CODEBASE_INDEX_DEFAULTS.MAX_FILE_WATCHER_CONCURRENCY
|
||||
|
||||
/**Directory Scanner */
|
||||
export const MAX_LIST_FILES_LIMIT_CODE_INDEX = 50_000
|
||||
export const BATCH_SEGMENT_THRESHOLD = 60 // Number of code segments to batch for embeddings/upserts
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export interface CodeIndexConfig {
|
|||
qdrantApiKey?: string
|
||||
searchMinScore?: number
|
||||
searchMaxResults?: number
|
||||
// File watcher performance settings for multi-worktree optimization
|
||||
fileWatcherDebounceMs?: number
|
||||
fileWatcherConcurrency?: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -285,4 +285,99 @@ describe("FileWatcher", () => {
|
|||
expect(mockWatcher.dispose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("configurable performance settings", () => {
|
||||
it("should use default debounce delay when not specified", async () => {
|
||||
// The default FileWatcher was created in beforeEach without custom parameters
|
||||
// Default is 500ms from DEFAULT_FILE_WATCHER_DEBOUNCE_MS
|
||||
await fileWatcher.initialize()
|
||||
|
||||
// Trigger a file event
|
||||
await mockOnDidCreate({ fsPath: "/mock/workspace/src/file.ts" })
|
||||
|
||||
// Wait less than default debounce time (500ms) - batch should not have started
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// No batch processing should have happened yet
|
||||
expect(mockVectorStore.upsertPoints).not.toHaveBeenCalled()
|
||||
|
||||
// Wait for remaining time plus buffer
|
||||
await new Promise((resolve) => setTimeout(resolve, 400))
|
||||
|
||||
// Now batch processing should have been triggered (or completed)
|
||||
})
|
||||
|
||||
it("should use custom debounce delay when specified", async () => {
|
||||
// Create a file watcher with a longer custom debounce delay (1000ms)
|
||||
const customDebounceWatcher = new FileWatcher(
|
||||
"/mock/workspace",
|
||||
mockContext,
|
||||
mockCacheManager,
|
||||
mockEmbedder,
|
||||
mockVectorStore,
|
||||
mockIgnoreInstance,
|
||||
undefined, // ignoreController
|
||||
undefined, // batchSegmentThreshold
|
||||
1000, // custom debounce delay (1000ms)
|
||||
5, // custom concurrency limit
|
||||
)
|
||||
|
||||
await customDebounceWatcher.initialize()
|
||||
|
||||
// Trigger a file event
|
||||
await mockOnDidCreate({ fsPath: "/mock/workspace/src/custom-file.ts" })
|
||||
|
||||
// Wait for 700ms - should still not have processed (custom debounce is 1000ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
||||
|
||||
// No batch processing should have happened yet with 1000ms debounce
|
||||
// Note: The file watcher uses its own internal debounce timer
|
||||
|
||||
// Clean up
|
||||
customDebounceWatcher.dispose()
|
||||
})
|
||||
|
||||
it("should use custom concurrency limit for file processing", async () => {
|
||||
// Create a file watcher with a lower custom concurrency limit
|
||||
const customConcurrencyWatcher = new FileWatcher(
|
||||
"/mock/workspace",
|
||||
mockContext,
|
||||
mockCacheManager,
|
||||
mockEmbedder,
|
||||
mockVectorStore,
|
||||
mockIgnoreInstance,
|
||||
undefined, // ignoreController
|
||||
undefined, // batchSegmentThreshold
|
||||
100, // short debounce for faster test
|
||||
2, // low concurrency limit (2)
|
||||
)
|
||||
|
||||
await customConcurrencyWatcher.initialize()
|
||||
|
||||
// Clean up
|
||||
customConcurrencyWatcher.dispose()
|
||||
})
|
||||
|
||||
it("should accept all optional parameters including debounce and concurrency", async () => {
|
||||
// Test that the constructor accepts all parameters without errors
|
||||
const fullyConfiguredWatcher = new FileWatcher(
|
||||
"/mock/workspace",
|
||||
mockContext,
|
||||
mockCacheManager,
|
||||
mockEmbedder,
|
||||
mockVectorStore,
|
||||
mockIgnoreInstance,
|
||||
undefined, // ignoreController
|
||||
50, // batchSegmentThreshold
|
||||
2000, // debounceMs
|
||||
5, // concurrencyLimit
|
||||
)
|
||||
|
||||
expect(fullyConfiguredWatcher).toBeDefined()
|
||||
|
||||
// Initialize and dispose to ensure no runtime errors
|
||||
await fullyConfiguredWatcher.initialize()
|
||||
fullyConfiguredWatcher.dispose()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
BATCH_SEGMENT_THRESHOLD,
|
||||
MAX_BATCH_RETRIES,
|
||||
INITIAL_RETRY_DELAY_MS,
|
||||
DEFAULT_FILE_WATCHER_DEBOUNCE_MS,
|
||||
DEFAULT_FILE_WATCHER_CONCURRENCY,
|
||||
} from "../constants"
|
||||
import { createHash } from "crypto"
|
||||
import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController"
|
||||
|
|
@ -37,8 +39,8 @@ export class FileWatcher implements IFileWatcher {
|
|||
private ignoreController: RooIgnoreController
|
||||
private accumulatedEvents: Map<string, { uri: vscode.Uri; type: "create" | "change" | "delete" }> = new Map()
|
||||
private batchProcessDebounceTimer?: NodeJS.Timeout
|
||||
private readonly BATCH_DEBOUNCE_DELAY_MS = 500
|
||||
private readonly FILE_PROCESSING_CONCURRENCY_LIMIT = 10
|
||||
private readonly batchDebounceDelayMs: number
|
||||
private readonly fileProcessingConcurrencyLimit: number
|
||||
private readonly batchSegmentThreshold: number
|
||||
|
||||
private readonly _onDidStartBatchProcessing = new vscode.EventEmitter<string[]>()
|
||||
|
|
@ -68,9 +70,14 @@ export class FileWatcher implements IFileWatcher {
|
|||
* Creates a new file watcher
|
||||
* @param workspacePath Path to the workspace
|
||||
* @param context VS Code extension context
|
||||
* @param cacheManager Cache manager
|
||||
* @param embedder Optional embedder
|
||||
* @param vectorStore Optional vector store
|
||||
* @param cacheManager Cache manager
|
||||
* @param ignoreInstance Optional ignore instance for .gitignore filtering
|
||||
* @param ignoreController Optional RooIgnoreController for .rooignore filtering
|
||||
* @param batchSegmentThreshold Optional batch segment threshold for embeddings
|
||||
* @param debounceMs Optional debounce delay in ms (default 500ms). Higher values reduce CPU usage in multi-worktree scenarios.
|
||||
* @param concurrencyLimit Optional concurrency limit for file processing (default 10). Lower values reduce CPU usage in multi-worktree scenarios.
|
||||
*/
|
||||
constructor(
|
||||
private workspacePath: string,
|
||||
|
|
@ -81,6 +88,8 @@ export class FileWatcher implements IFileWatcher {
|
|||
ignoreInstance?: Ignore,
|
||||
ignoreController?: RooIgnoreController,
|
||||
batchSegmentThreshold?: number,
|
||||
debounceMs?: number,
|
||||
concurrencyLimit?: number,
|
||||
) {
|
||||
this.ignoreController = ignoreController || new RooIgnoreController(workspacePath)
|
||||
if (ignoreInstance) {
|
||||
|
|
@ -100,6 +109,14 @@ export class FileWatcher implements IFileWatcher {
|
|||
this.batchSegmentThreshold = BATCH_SEGMENT_THRESHOLD
|
||||
}
|
||||
}
|
||||
|
||||
// Set configurable debounce delay for multi-worktree optimization
|
||||
// Higher values batch more file changes together, reducing CPU spikes
|
||||
this.batchDebounceDelayMs = debounceMs ?? DEFAULT_FILE_WATCHER_DEBOUNCE_MS
|
||||
|
||||
// Set configurable concurrency limit for multi-worktree optimization
|
||||
// Lower values process fewer files in parallel, reducing peak CPU usage
|
||||
this.fileProcessingConcurrencyLimit = concurrencyLimit ?? DEFAULT_FILE_WATCHER_CONCURRENCY
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -162,12 +179,13 @@ export class FileWatcher implements IFileWatcher {
|
|||
|
||||
/**
|
||||
* Schedules batch processing with debounce
|
||||
* Uses configurable debounce delay for multi-worktree optimization
|
||||
*/
|
||||
private scheduleBatchProcessing(): void {
|
||||
if (this.batchProcessDebounceTimer) {
|
||||
clearTimeout(this.batchProcessDebounceTimer)
|
||||
}
|
||||
this.batchProcessDebounceTimer = setTimeout(() => this.triggerBatchProcessing(), this.BATCH_DEBOUNCE_DELAY_MS)
|
||||
this.batchProcessDebounceTimer = setTimeout(() => this.triggerBatchProcessing(), this.batchDebounceDelayMs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -265,8 +283,9 @@ export class FileWatcher implements IFileWatcher {
|
|||
const successfullyProcessedForUpsert: Array<{ path: string; newHash?: string }> = []
|
||||
const filesToProcessConcurrently = [...filesToUpsertDetails]
|
||||
|
||||
for (let i = 0; i < filesToProcessConcurrently.length; i += this.FILE_PROCESSING_CONCURRENCY_LIMIT) {
|
||||
const chunkToProcess = filesToProcessConcurrently.slice(i, i + this.FILE_PROCESSING_CONCURRENCY_LIMIT)
|
||||
// Use configurable concurrency limit for multi-worktree optimization
|
||||
for (let i = 0; i < filesToProcessConcurrently.length; i += this.fileProcessingConcurrencyLimit) {
|
||||
const chunkToProcess = filesToProcessConcurrently.slice(i, i + this.fileProcessingConcurrencyLimit)
|
||||
|
||||
const chunkProcessingPromises = chunkToProcess.map(async (fileDetail) => {
|
||||
this._onBatchProgressUpdate.fire({
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ export class CodeIndexServiceFactory {
|
|||
|
||||
/**
|
||||
* Creates a file watcher instance with its required dependencies.
|
||||
* Passes configurable performance settings for multi-worktree optimization.
|
||||
*/
|
||||
public createFileWatcher(
|
||||
context: vscode.ExtensionContext,
|
||||
|
|
@ -200,6 +201,8 @@ export class CodeIndexServiceFactory {
|
|||
ignoreInstance: Ignore,
|
||||
rooIgnoreController?: RooIgnoreController,
|
||||
): IFileWatcher {
|
||||
const config = this.configManager.getConfig()
|
||||
|
||||
// Get the configurable batch size from VSCode settings
|
||||
let batchSize: number
|
||||
try {
|
||||
|
|
@ -210,6 +213,12 @@ export class CodeIndexServiceFactory {
|
|||
// In test environment, vscode.workspace might not be available
|
||||
batchSize = BATCH_SEGMENT_THRESHOLD
|
||||
}
|
||||
|
||||
// Get configurable file watcher performance settings from config manager
|
||||
// These allow users to optimize CPU usage for multi-worktree scenarios
|
||||
const debounceMs = config.fileWatcherDebounceMs
|
||||
const concurrencyLimit = config.fileWatcherConcurrency
|
||||
|
||||
return new FileWatcher(
|
||||
this.workspacePath,
|
||||
context,
|
||||
|
|
@ -219,6 +228,8 @@ export class CodeIndexServiceFactory {
|
|||
ignoreInstance,
|
||||
rooIgnoreController,
|
||||
batchSize,
|
||||
debounceMs,
|
||||
concurrencyLimit,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue