Skip to content

Commit 2eb26fc

Browse files
committed
fix: prevent Ollama indexing from freezing at 69%
- Add dynamic timeout for batch embedding operations based on batch size - Improve error handling and recovery in batch processing - Ensure errors are properly thrown to prevent silent failures - Add immediate error state updates when batch processing fails - Add better logging for debugging timeout and retry scenarios Fixes #6849
1 parent ad0e33e commit 2eb26fc

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

src/i18n/locales/en/embeddings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"serviceUnavailable": "Ollama service is unavailable (status: {{status}})",
1616
"modelNotFound": "Ollama model not found: {{modelId}}",
1717
"modelNotEmbeddingCapable": "Ollama model is not embedding capable: {{modelId}}",
18-
"hostNotFound": "Ollama host not found: {{baseUrl}}"
18+
"hostNotFound": "Ollama host not found: {{baseUrl}}",
19+
"batchTimeoutError": "Ollama embedding timed out after {{timeout}} seconds while processing {{count}} texts. Consider reducing batch size or increasing timeout."
1920
},
2021
"scanner": {
2122
"unknownErrorProcessingFile": "Unknown error processing file {{filePath}}",

src/services/code-index/embedders/ollama.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
6969
// Implementing based on user's specific request structure.
7070

7171
// Add timeout to prevent indefinite hanging
72+
// Use a longer timeout for batch operations as they can take more time
73+
const batchTimeout = Math.max(OLLAMA_EMBEDDING_TIMEOUT_MS, texts.length * 2000) // At least 2 seconds per text
7274
const controller = new AbortController()
73-
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS)
75+
const timeoutId = setTimeout(() => controller.abort(), batchTimeout)
7476

7577
const response = await fetch(url, {
7678
method: "POST",
@@ -125,7 +127,15 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
125127

126128
// Handle specific error types with better messages
127129
if (error.name === "AbortError") {
128-
throw new Error(t("embeddings:validation.connectionFailed"))
130+
// More specific timeout error message for batch operations
131+
const timeoutMessage =
132+
texts.length > 1
133+
? t("embeddings:ollama.batchTimeoutError", {
134+
count: texts.length,
135+
timeout: Math.round(Math.max(OLLAMA_EMBEDDING_TIMEOUT_MS, texts.length * 2000) / 1000),
136+
})
137+
: t("embeddings:validation.connectionFailed")
138+
throw new Error(timeoutMessage)
129139
} else if (error.message?.includes("fetch failed") || error.code === "ECONNREFUSED") {
130140
throw new Error(t("embeddings:ollama.serviceNotRunning", { baseUrl: this.baseUrl }))
131141
} else if (error.code === "ENOTFOUND") {

src/services/code-index/orchestrator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,13 @@ export class CodeIndexOrchestrator {
138138

139139
const handleFileParsed = (fileBlockCount: number) => {
140140
cumulativeBlocksFoundSoFar += fileBlockCount
141+
// Update progress immediately when blocks are found
141142
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
142143
}
143144

144145
const handleBlocksIndexed = (indexedCount: number) => {
145146
cumulativeBlocksIndexed += indexedCount
147+
// Update progress immediately when blocks are indexed
146148
this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
147149
}
148150

@@ -154,6 +156,8 @@ export class CodeIndexOrchestrator {
154156
batchError,
155157
)
156158
batchErrors.push(batchError)
159+
// Update state to show error immediately
160+
this.stateManager.setSystemState("Error", `Indexing error: ${batchError.message}`)
157161
},
158162
handleBlocksIndexed,
159163
handleFileParsed,

src/services/code-index/processors/scanner.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,29 @@ export class DirectoryScanner implements IDirectoryScanner {
385385
}
386386
// --- End Deletion Step ---
387387

388-
// Create embeddings for batch
389-
const { embeddings } = await this.embedder.createEmbeddings(batchTexts)
388+
// Create embeddings for batch with better error context
389+
let embeddings: number[][]
390+
try {
391+
const response = await this.embedder.createEmbeddings(batchTexts)
392+
embeddings = response.embeddings
393+
} catch (embeddingError: any) {
394+
// Log specific details about the embedding failure
395+
console.error(
396+
`[DirectoryScanner] Embedding creation failed for batch of ${batchTexts.length} texts:`,
397+
embeddingError,
398+
)
399+
400+
// Check if it's a timeout error and provide more context
401+
if (embeddingError.message?.includes("timed out") || embeddingError.message?.includes("timeout")) {
402+
throw new Error(
403+
`Embedding timeout for batch of ${batchTexts.length} texts. This may indicate the Ollama service is overloaded or the model is too slow. ${embeddingError.message}`,
404+
{ cause: embeddingError },
405+
)
406+
}
407+
408+
// Re-throw with additional context
409+
throw embeddingError
410+
}
390411

391412
// Prepare points for Qdrant
392413
const points = batchBlocks.map((block, index) => {
@@ -420,7 +441,7 @@ export class DirectoryScanner implements IDirectoryScanner {
420441
} catch (error) {
421442
lastError = error as Error
422443
console.error(
423-
`[DirectoryScanner] Error processing batch (attempt ${attempts}) in workspace ${scanWorkspace}:`,
444+
`[DirectoryScanner] Error processing batch (attempt ${attempts}/${MAX_BATCH_RETRIES}) in workspace ${scanWorkspace}:`,
424445
error,
425446
)
426447
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
@@ -433,6 +454,7 @@ export class DirectoryScanner implements IDirectoryScanner {
433454

434455
if (attempts < MAX_BATCH_RETRIES) {
435456
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempts - 1)
457+
console.log(`[DirectoryScanner] Retrying batch processing in ${delay}ms...`)
436458
await new Promise((resolve) => setTimeout(resolve, delay))
437459
}
438460
}
@@ -454,6 +476,9 @@ export class DirectoryScanner implements IDirectoryScanner {
454476
),
455477
)
456478
}
479+
// CRITICAL: Throw the error to ensure it's caught by the orchestrator
480+
// This prevents the indexing from appearing to succeed when it actually failed
481+
throw lastError
457482
}
458483
}
459484
}

0 commit comments

Comments
 (0)