fix: prevent codebase indexing from getting stuck

- Add 5-minute timeout for the entire indexing process
- Implement progress monitoring to detect stuck state (no progress for 2 minutes)
- Automatically recover from stuck state with clear error messages
- Clean up timeouts and monitors properly on completion or error
- Fixes #9193
This commit is contained in:
Roo Code 2025-11-12 08:33:49 +00:00
parent 69d4efc335
commit 61dd49db79

View file

@ -15,6 +15,8 @@ import { t } from "../../i18n"
export class CodeIndexOrchestrator {
private _fileWatcherSubscriptions: vscode.Disposable[] = []
private _isProcessing: boolean = false
private _lastProgressUpdate: number = 0
private _progressMonitorInterval: NodeJS.Timeout | undefined
constructor(
private readonly configManager: CodeIndexConfigManager,
@ -127,7 +129,23 @@ export class CodeIndexOrchestrator {
// This helps us decide whether to preserve cache on error
let indexingStarted = false
// Add timeout for the entire indexing process (5 minutes)
const INDEXING_TIMEOUT_MS = 5 * 60 * 1000
let indexingTimeout: NodeJS.Timeout | undefined
try {
// Set up timeout to prevent indefinite stuck state
indexingTimeout = setTimeout(() => {
console.error("[CodeIndexOrchestrator] Indexing timeout - process took too long")
this.stateManager.setSystemState("Error", "Indexing timeout - process took too long. Please try again.")
this._isProcessing = false
// Clean up any ongoing operations
this.stopWatcher()
this._stopProgressMonitor()
}, INDEXING_TIMEOUT_MS)
// Start progress monitoring to detect stuck state
this._startProgressMonitor()
const collectionCreated = await this.vectorStore.initialize()
// Successfully connected to Qdrant
@ -158,11 +176,13 @@ export class CodeIndexOrchestrator {
const handleFileParsed = (fileBlockCount: number) => {
cumulativeBlocksFoundSoFar += fileBlockCount
this._lastProgressUpdate = Date.now()
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
}
const handleBlocksIndexed = (indexedCount: number) => {
cumulativeBlocksIndexed += indexedCount
this._lastProgressUpdate = Date.now()
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
}
@ -198,6 +218,9 @@ export class CodeIndexOrchestrator {
// Mark indexing as complete after successful incremental scan
await this.vectorStore.markIndexingComplete()
// Stop progress monitor on successful completion
this._stopProgressMonitor()
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
} else {
// No existing data or collection was just created - do a full scan
@ -212,11 +235,13 @@ export class CodeIndexOrchestrator {
const handleFileParsed = (fileBlockCount: number) => {
cumulativeBlocksFoundSoFar += fileBlockCount
this._lastProgressUpdate = Date.now()
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
}
const handleBlocksIndexed = (indexedCount: number) => {
cumulativeBlocksIndexed += indexedCount
this._lastProgressUpdate = Date.now()
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
}
@ -281,7 +306,20 @@ export class CodeIndexOrchestrator {
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
}
// Clear timeout and stop progress monitor on successful completion
if (indexingTimeout) {
clearTimeout(indexingTimeout)
indexingTimeout = undefined
}
this._stopProgressMonitor()
} catch (error: any) {
// Clear timeout on error
if (indexingTimeout) {
clearTimeout(indexingTimeout)
indexingTimeout = undefined
}
console.error("[CodeIndexOrchestrator] Error during indexing:", error)
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
error: error instanceof Error ? error.message : String(error),
@ -324,6 +362,12 @@ export class CodeIndexOrchestrator {
)
this.stopWatcher()
} finally {
// Always clear timeout, stop progress monitor, and clear processing flag
if (indexingTimeout) {
clearTimeout(indexingTimeout)
indexingTimeout = undefined
}
this._stopProgressMonitor()
this._isProcessing = false
}
}
@ -335,6 +379,7 @@ export class CodeIndexOrchestrator {
this.fileWatcher.dispose()
this._fileWatcherSubscriptions.forEach((sub) => sub.dispose())
this._fileWatcherSubscriptions = []
this._stopProgressMonitor()
if (this.stateManager.state !== "Error") {
this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.fileWatcherStopped"))
@ -384,4 +429,45 @@ export class CodeIndexOrchestrator {
public get state(): IndexingState {
return this.stateManager.state
}
/**
* Starts monitoring progress to detect stuck indexing
*/
private _startProgressMonitor(): void {
this._lastProgressUpdate = Date.now()
this._stopProgressMonitor() // Clear any existing monitor
// Check every 30 seconds if progress has been made
const PROGRESS_CHECK_INTERVAL = 30 * 1000
// Consider stuck if no progress for 2 minutes
const STUCK_THRESHOLD = 2 * 60 * 1000
this._progressMonitorInterval = setInterval(() => {
const timeSinceLastProgress = Date.now() - this._lastProgressUpdate
if (this._isProcessing && timeSinceLastProgress > STUCK_THRESHOLD) {
console.error(
`[CodeIndexOrchestrator] Indexing appears stuck - no progress for ${timeSinceLastProgress / 1000} seconds`,
)
// Set error state and stop processing
this.stateManager.setSystemState(
"Error",
"Indexing appears stuck with no progress. Please try restarting the indexing process.",
)
this._isProcessing = false
this.stopWatcher()
}
}, PROGRESS_CHECK_INTERVAL)
}
/**
* Stops the progress monitor
*/
private _stopProgressMonitor(): void {
if (this._progressMonitorInterval) {
clearInterval(this._progressMonitorInterval)
this._progressMonitorInterval = undefined
}
}
}