mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
Merge pull request #12164 from Kilo-Org/fix-kilo-serve-busy-loop-linux
fix(cli): avoid independent worktree indexing scans
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/kilo-indexing": patch
|
||||
---
|
||||
|
||||
Wait for the primary codebase index before indexing a linked worktree, preventing large worktrees from consuming excessive CPU during startup.
|
||||
@@ -18,6 +18,7 @@ import { WorktreeOverlay } from "./worktree-overlay"
|
||||
const log = Log.create({ service: "indexing-manager" })
|
||||
const BASELINE_CHECK_INTERVAL = 1_000
|
||||
const BASELINE_SIGNATURE_INTERVAL = 30_000
|
||||
const BASELINE_PENDING = "Waiting for the primary worktree index to become available."
|
||||
|
||||
type Baseline = {
|
||||
store?: IVectorStore
|
||||
@@ -129,6 +130,12 @@ export class CodeIndexManager {
|
||||
this.clearRetryTimer()
|
||||
}
|
||||
|
||||
private waiting(): boolean {
|
||||
if (!this.baselinePath || this._baselineStore) return false
|
||||
this._stateManager.setSystemState("Standby", BASELINE_PENDING)
|
||||
return true
|
||||
}
|
||||
|
||||
private async waitForRetry(delay: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
this._retryResolve = resolve
|
||||
@@ -191,6 +198,11 @@ export class CodeIndexManager {
|
||||
try {
|
||||
await this._recreateServices()
|
||||
if (this._disposed) return
|
||||
if (this.waiting()) {
|
||||
this.resetRetryState()
|
||||
this._isRecoveringFromError = false
|
||||
return
|
||||
}
|
||||
this.emitStart(trigger)
|
||||
await this._orchestrator!.startIndexing(trigger)
|
||||
if (this._disposed) return
|
||||
@@ -344,6 +356,8 @@ export class CodeIndexManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.waiting()) return { requiresRestart }
|
||||
|
||||
const shouldStartOrRestart =
|
||||
requiresRestart || (needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))
|
||||
|
||||
@@ -365,6 +379,9 @@ export class CodeIndexManager {
|
||||
if (this._disposed) return
|
||||
if (!this.isFeatureEnabled) return
|
||||
|
||||
await this.refreshBaseline()
|
||||
if (this.waiting()) return
|
||||
|
||||
log.info("manual indexing start requested", { workspacePath: this.workspacePath })
|
||||
|
||||
const currentStatus = this.getCurrentStatus()
|
||||
@@ -453,8 +470,9 @@ export class CodeIndexManager {
|
||||
|
||||
public async searchIndex(query: string, directoryPrefix?: string): Promise<VectorStoreSearchResult[]> {
|
||||
if (!this.isFeatureEnabled) return []
|
||||
this.assertInitialized()
|
||||
await this.refreshBaseline()
|
||||
if (this.waiting()) return []
|
||||
this.assertInitialized()
|
||||
return this._searchService!.searchIndex(query, directoryPrefix)
|
||||
}
|
||||
|
||||
@@ -483,6 +501,7 @@ export class CodeIndexManager {
|
||||
this._baselineSigned = now
|
||||
if (!baseline?.store) {
|
||||
if (!this._baselineStore) this._baselineSignature = signature
|
||||
this.waiting()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -528,7 +547,7 @@ export class CodeIndexManager {
|
||||
}
|
||||
} catch (err) {
|
||||
await store.close?.()
|
||||
log.warn("shared indexing baseline is unavailable; using an independent worktree index", {
|
||||
log.info("shared indexing baseline is unavailable; waiting for the primary worktree index", {
|
||||
workspacePath: this.workspacePath,
|
||||
baselinePath: this.baselinePath,
|
||||
err,
|
||||
@@ -586,7 +605,6 @@ export class CodeIndexManager {
|
||||
fileWatcher,
|
||||
(event) => this.handleTelemetry(event),
|
||||
baseline?.overlay,
|
||||
Boolean(this.baselinePath && !baseline?.store),
|
||||
)
|
||||
const search = new CodeIndexSearchService(
|
||||
this._configManager!,
|
||||
|
||||
@@ -37,7 +37,6 @@ export class CodeIndexOrchestrator {
|
||||
private readonly fileWatcher: IFileWatcher,
|
||||
private readonly onTelemetry?: IndexingTelemetryReporter,
|
||||
private readonly overlay?: WorktreeOverlay,
|
||||
private readonly independent = false,
|
||||
) {}
|
||||
|
||||
private getTelemetryMeta(): IndexingTelemetryMeta {
|
||||
@@ -215,7 +214,7 @@ export class CodeIndexOrchestrator {
|
||||
})
|
||||
}
|
||||
|
||||
const hasExistingData = this.overlay || this.independent ? false : await this.vectorStore.hasIndexedData()
|
||||
const hasExistingData = this.overlay ? false : await this.vectorStore.hasIndexedData()
|
||||
if (!this.overlay && !hasExistingData) {
|
||||
if (!collectionCreated) await this.vectorStore.clearCollection()
|
||||
await this.cacheManager.clearCacheFile()
|
||||
|
||||
@@ -357,35 +357,6 @@ describe("CodeIndexOrchestrator telemetry", () => {
|
||||
expect(orchestrator.state).toBe("Error")
|
||||
})
|
||||
|
||||
test("rebuilds a complete independent index when the shared baseline is unavailable", async () => {
|
||||
const cache = {
|
||||
clears: 0,
|
||||
async clearCacheFile() {
|
||||
this.clears += 1
|
||||
},
|
||||
async flush() {},
|
||||
}
|
||||
const store = new Store(true, false)
|
||||
const orchestrator = new CodeIndexOrchestrator(
|
||||
createConfig(),
|
||||
new CodeIndexStateManager(),
|
||||
"/tmp/ws",
|
||||
cache as unknown as CacheManager,
|
||||
store as unknown as IVectorStore,
|
||||
new Scanner(1, 1, 1) as unknown as DirectoryScanner,
|
||||
new Watcher() as unknown as IFileWatcher,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
|
||||
await orchestrator.startIndexing("background")
|
||||
|
||||
expect(store.clearCount).toBe(1)
|
||||
expect(cache.clears).toBe(1)
|
||||
expect(orchestrator.state).toBe("Indexed")
|
||||
})
|
||||
|
||||
test("preserves cache and collection data on retryable start failures", async () => {
|
||||
const events: IndexingTelemetryEvent[] = []
|
||||
const cache = {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { mkdir } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { IndexingWorker } from "../../src/kilocode/indexing-worker-client"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
@@ -55,6 +57,65 @@ test("routes multiple directories through the shared indexing worker", async ()
|
||||
expect(failures).toEqual([])
|
||||
})
|
||||
|
||||
test("waits for the primary index instead of scanning a worktree independently", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const main = path.join(tmp.path, "main")
|
||||
const worktree = path.join(tmp.path, "worktree")
|
||||
await mkdir(main)
|
||||
await mkdir(worktree)
|
||||
await Bun.write(path.join(worktree, "file.ts"), "export function value() { return 1 }\n")
|
||||
|
||||
const requests: string[] = []
|
||||
const server = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
async fetch(req) {
|
||||
requests.push(new URL(req.url).pathname)
|
||||
const body = (await req.json()) as { input: string | string[] }
|
||||
const input = Array.isArray(body.input) ? body.input : [body.input]
|
||||
return Response.json({
|
||||
object: "list",
|
||||
model: "fixture-model",
|
||||
data: input.map((_, index) => ({ object: "embedding", index, embedding: [0.1, 0.2, 0.3] })),
|
||||
usage: { prompt_tokens: 1, total_tokens: 1 },
|
||||
})
|
||||
},
|
||||
})
|
||||
const failures: unknown[] = []
|
||||
const engine = IndexingWorker.create(worktree, tmp.path, {
|
||||
status() {},
|
||||
telemetry() {},
|
||||
warning() {},
|
||||
log() {},
|
||||
failure(err) {
|
||||
failures.push(err)
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const status = await engine.init(
|
||||
{
|
||||
enabled: true,
|
||||
embedderProvider: "openai-compatible",
|
||||
vectorStoreProvider: "lancedb",
|
||||
modelId: "fixture-model",
|
||||
modelDimension: 3,
|
||||
openAiCompatibleBaseUrl: `http://127.0.0.1:${server.port}/v1`,
|
||||
},
|
||||
main,
|
||||
)
|
||||
|
||||
expect(status.state).toBe("Standby")
|
||||
expect(status.message).toContain("primary worktree index")
|
||||
expect(requests).toEqual(["/v1/embeddings"])
|
||||
} finally {
|
||||
await engine.dispose()
|
||||
server.stop(true)
|
||||
}
|
||||
|
||||
expect(failures).toEqual([])
|
||||
})
|
||||
|
||||
test("allows same-directory recreation while disposal is pending", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const hooks = {
|
||||
|
||||
Reference in New Issue
Block a user