Skip to content

Commit 3f10fb3

Browse files
mjbvzCopilot
andauthored
Clean up old external ingest file sets if we have too many (#2899)
* Clean up old file sets if we have too many * Update src/platform/workspaceChunkSearch/node/codeSearch/externalIngestClient.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/platform/workspaceChunkSearch/node/codeSearch/externalIngestClient.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c0cd49a commit 3f10fb3

File tree

1 file changed

+53
-5
lines changed

1 file changed

+53
-5
lines changed

src/platform/workspaceChunkSearch/node/codeSearch/externalIngestClient.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import crypto from 'crypto';
99
import fs from 'fs';
1010
import { CancellationToken } from 'vscode-languageserver-protocol';
1111
import { Result } from '../../../../util/common/result';
12-
import { coalesce } from '../../../../util/vs/base/common/arrays';
1312
import { raceCancellationError } from '../../../../util/vs/base/common/async';
1413
import { CancellationError } from '../../../../util/vs/base/common/errors';
1514
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
@@ -167,19 +166,50 @@ export class ExternalIngestClient extends Disposable implements IExternalIngestC
167166
}
168167

169168
onProgress?.(l10n.t('Creating snapshot...'));
169+
170170
// Create snapshot - this endpoint could return 429 if you already have too many filesets
171-
let createIngestResponse: Response;
172-
try {
173-
createIngestResponse = await this.post(authToken, '/external/code/ingest', {
171+
const createIngest = async (): Promise<Response> => {
172+
return this.post(authToken, '/external/code/ingest', {
174173
fileset_name: filesetName,
175174
new_checkpoint: newCheckpoint,
176175
geo_filter: Buffer.from(geoFilter.toBytes()).toString('base64'),
177176
coded_symbols: codedSymbols,
178177
}, token);
178+
};
179+
180+
let createIngestResponse: Response;
181+
try {
182+
createIngestResponse = await createIngest();
179183
} catch (err) {
180184
throw new Error('Exception during create ingest', err);
181185
}
182186

187+
// Handle 429 by cleaning up old filesets and retrying
188+
if (createIngestResponse.status === 429) {
189+
this.logService.info('ExternalIngestClient::updateIndex(): Got 429, cleaning up old filesets...');
190+
onProgress?.(l10n.t("Too many filesets, cleaning up old ones..."));
191+
192+
await raceCancellationError(this.cleanupOldFilesets(authToken, filesetName, token), token);
193+
194+
// Retry the create ingest
195+
this.logService.info('ExternalIngestClient::updateIndex(): Retrying create ingest after cleanup...');
196+
onProgress?.(l10n.t("Retrying snapshot creation..."));
197+
try {
198+
createIngestResponse = await createIngest();
199+
} catch (err) {
200+
throw new Error('Exception during create ingest retry', err);
201+
}
202+
203+
// If we still get 429 after cleanup and retry, fail with a clear error
204+
if (createIngestResponse.status === 429) {
205+
throw new Error('Create ingest failed with 429 Too Many Requests even after cleanup.');
206+
}
207+
}
208+
209+
// Fail fast on non-OK responses before attempting to parse JSON
210+
if (!createIngestResponse.ok) {
211+
throw new Error(`Create ingest failed with status ${createIngestResponse.status}`);
212+
}
183213
interface CodedSymbolRange {
184214
readonly start: number;
185215
readonly end: number;
@@ -362,6 +392,11 @@ export class ExternalIngestClient extends Disposable implements IExternalIngestC
362392
return [];
363393
}
364394

395+
const filesets = await this.listFilesetsWithDetails(authToken, token);
396+
return filesets.map(x => x.name);
397+
}
398+
399+
private async listFilesetsWithDetails(authToken: string, token: CancellationToken): Promise<Array<{ name: string; checkpoint: string; status: string }>> {
365400
const resp = await this.apiClient.makeRequest(
366401
`${ExternalIngestClient.baseUrl}/external/code/ingest`,
367402
this.getHeaders(authToken),
@@ -371,7 +406,20 @@ export class ExternalIngestClient extends Disposable implements IExternalIngestC
371406
);
372407

373408
const body = await resp.json() as { filesets?: Array<{ name: string; checkpoint: string; status: string }>; max_filesets: number };
374-
return coalesce((body.filesets ?? []).map(x => x.name));
409+
return body.filesets ?? [];
410+
}
411+
412+
/**
413+
* Cleans up old filesets to make room for new ones.
414+
*/
415+
private async cleanupOldFilesets(authToken: string, currentFilesetName: string, token: CancellationToken): Promise<void> {
416+
const filesets = await this.listFilesetsWithDetails(authToken, token);
417+
418+
const candidates = filesets.filter(f => f.name !== currentFilesetName);
419+
const toDelete = candidates.at(-1);
420+
if (toDelete) {
421+
await this.deleteFilesetByName(authToken, toDelete.name, token);
422+
}
375423
}
376424

377425
async deleteFileset(filesetName: string, token: CancellationToken): Promise<void> {

0 commit comments

Comments
 (0)