diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index a4d9dafecf..01e7fb979a 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1440,7 +1440,7 @@ export const webviewMessageHandler = async ( await manager.initialize(provider.contextProxy) } - manager.startIndexing() + await manager.startIndexing() } } catch (error) { provider.log(`Error starting indexing: ${error instanceof Error ? error.message : String(error)}`) diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index 10eaeacef7..b0e177b92b 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -115,4 +115,89 @@ describe("CodeIndexManager - handleExternalSettingsChange regression", () => { await expect(manager.handleExternalSettingsChange()).resolves.not.toThrow() }) }) + + describe("clearIndexData and startIndexing sequence", () => { + it("should allow startIndexing immediately after clearIndexData completes", async () => { + // Mock the required dependencies + const mockConfigManager = { + loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: false }), + isFeatureEnabled: true, + isFeatureConfigured: true, + } + const mockOrchestrator = { + clearIndexData: vitest.fn().mockResolvedValue(undefined), + startIndexing: vitest.fn().mockResolvedValue(undefined), + stopWatcher: vitest.fn(), + } + const mockCacheManager = { + clearCacheFile: vitest.fn().mockResolvedValue(undefined), + } + + // Set up the manager with mocked dependencies + ;(manager as any)._configManager = mockConfigManager + ;(manager as any)._orchestrator = mockOrchestrator + ;(manager as any)._searchService = {} + ;(manager as any)._cacheManager = mockCacheManager + + // Mock the feature state + vitest.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true) + vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true) + + // Verify manager is considered initialized + expect(manager.isInitialized).toBe(true) + + // Test the sequence: clearIndexData followed by startIndexing + await manager.clearIndexData() + expect(mockOrchestrator.clearIndexData).toHaveBeenCalled() + expect(mockCacheManager.clearCacheFile).toHaveBeenCalled() + + // This should not throw an error about being in processing state + await expect(manager.startIndexing()).resolves.not.toThrow() + expect(mockOrchestrator.startIndexing).toHaveBeenCalled() + }) + + it("should handle rapid clearIndexData and startIndexing calls", async () => { + // Mock the required dependencies + const mockConfigManager = { + loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: false }), + isFeatureEnabled: true, + isFeatureConfigured: true, + } + const mockOrchestrator = { + clearIndexData: vitest + .fn() + .mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 100))), + startIndexing: vitest.fn().mockResolvedValue(undefined), + stopWatcher: vitest.fn(), + } + const mockCacheManager = { + clearCacheFile: vitest.fn().mockResolvedValue(undefined), + } + + // Set up the manager with mocked dependencies + ;(manager as any)._configManager = mockConfigManager + ;(manager as any)._orchestrator = mockOrchestrator + ;(manager as any)._searchService = {} + ;(manager as any)._cacheManager = mockCacheManager + + // Mock the feature state + vitest.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true) + vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true) + + // Test rapid sequence: start clearIndexData and immediately call startIndexing + const clearPromise = manager.clearIndexData() + + // Wait a bit to ensure clearIndexData has started but not finished + await new Promise((resolve) => setTimeout(resolve, 50)) + + // This should wait for clearIndexData to complete before proceeding + const startPromise = manager.startIndexing() + + // Both should complete successfully + await Promise.all([clearPromise, startPromise]) + + expect(mockOrchestrator.clearIndexData).toHaveBeenCalled() + expect(mockOrchestrator.startIndexing).toHaveBeenCalled() + }) + }) }) diff --git a/src/services/code-index/orchestrator.ts b/src/services/code-index/orchestrator.ts index 5784f0dcfc..67da95a782 100644 --- a/src/services/code-index/orchestrator.ts +++ b/src/services/code-index/orchestrator.ts @@ -93,18 +93,22 @@ export class CodeIndexOrchestrator { return } - if ( - this._isProcessing || - (this.stateManager.state !== "Standby" && - this.stateManager.state !== "Error" && - this.stateManager.state !== "Indexed") - ) { + if (this._isProcessing) { console.warn( - `[CodeIndexOrchestrator] Start rejected: Already processing or in state ${this.stateManager.state}.`, + `[CodeIndexOrchestrator] Start rejected: Already processing (state: ${this.stateManager.state}).`, ) return } + if ( + this.stateManager.state !== "Standby" && + this.stateManager.state !== "Error" && + this.stateManager.state !== "Indexed" + ) { + console.warn(`[CodeIndexOrchestrator] Start rejected: Invalid state ${this.stateManager.state}.`) + return + } + this._isProcessing = true this.stateManager.setSystemState("Indexing", "Initializing services...") @@ -179,7 +183,7 @@ export class CodeIndexOrchestrator { if (this.stateManager.state !== "Error") { this.stateManager.setSystemState("Standby", "File watcher stopped.") } - this._isProcessing = false + // Note: Don't reset _isProcessing here as it may be managed by calling methods } /** @@ -190,7 +194,8 @@ export class CodeIndexOrchestrator { this._isProcessing = true try { - await this.stopWatcher() + // Stop the watcher first + this.stopWatcher() try { if (this.configManager.isFeatureConfigured) { @@ -201,6 +206,7 @@ export class CodeIndexOrchestrator { } catch (error: any) { console.error("[CodeIndexOrchestrator] Failed to clear vector collection:", error) this.stateManager.setSystemState("Error", `Failed to clear vector collection: ${error.message}`) + return // Exit early on error, _isProcessing will be reset in finally } await this.cacheManager.clearCacheFile()