Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`)
Expand Down
85 changes: 85 additions & 0 deletions src/services/code-index/__tests__/manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
})
24 changes: 15 additions & 9 deletions src/services/code-index/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In startIndexing(), the early return when _isProcessing is true may drop an indexing request rather than waiting for clearIndexData to complete. The rapid call test expects the call to wait until the current operation finishes. Consider implementing a waiting/queuing mechanism (e.g. using a simple loop or a p-wait-for) so that if _isProcessing is true, startIndexing waits until it’s false before proceeding.

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...")

Expand Down Expand Up @@ -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
}

/**
Expand All @@ -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) {
Expand All @@ -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()
Expand Down
Loading