From 07e9e443102157db007d00b95e39b85d7c741e0b Mon Sep 17 00:00:00 2001 From: Alexander Eyers-Taylor Date: Fri, 13 Dec 2024 17:05:00 +0000 Subject: [PATCH] Add cache of query schemas to results view (#3862) * Add cache of query schemas to results view * Update changelog * Don't call bqrsInfo with a page size of 0 * Update extensions/ql-vscode/CHANGELOG.md Co-authored-by: Andrew Eisenberg --------- Co-authored-by: Andrew Eisenberg --- extensions/ql-vscode/CHANGELOG.md | 1 + .../contextual/cached-operation.ts | 16 +++++++++++++++- .../ql-vscode/src/local-queries/results-view.ts | 14 ++++++++++++++ extensions/ql-vscode/src/run-queries-shared.ts | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 5e1c5ac390f..53dba290fc4 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -4,6 +4,7 @@ - Add a palette command that allows importing all databases directly inside of a parent folder. [#3797](https://github.com/github/vscode-codeql/pull/3797) - Only use VS Code telemetry settings instead of using `codeQL.telemetry.enableTelemetry` [#3853](https://github.com/github/vscode-codeql/pull/3853) +- Improve the performance of the results view with large numbers of results. [#3862](https://github.com/github/vscode-codeql/pull/3862) ## 1.16.1 - 6 November 2024 diff --git a/extensions/ql-vscode/src/language-support/contextual/cached-operation.ts b/extensions/ql-vscode/src/language-support/contextual/cached-operation.ts index a50e7d03b01..3c6e845d740 100644 --- a/extensions/ql-vscode/src/language-support/contextual/cached-operation.ts +++ b/extensions/ql-vscode/src/language-support/contextual/cached-operation.ts @@ -7,6 +7,7 @@ export class CachedOperation { private readonly operation: (t: string, ...args: S) => Promise; private readonly cached: Map; private readonly lru: string[]; + private generation: number; private readonly inProgressCallbacks: Map< string, Array<[(u: U) => void, (reason?: Error) => void]> @@ -17,6 +18,7 @@ export class CachedOperation { private cacheSize = 100, ) { this.operation = operation; + this.generation = 0; this.lru = []; this.inProgressCallbacks = new Map< string, @@ -46,7 +48,7 @@ export class CachedOperation { inProgressCallback.push([resolve, reject]); }); } - + const origGeneration = this.generation; // Otherwise compute the new value, but leave a callback to allow sharing work const callbacks: Array<[(u: U) => void, (reason?: Error) => void]> = []; this.inProgressCallbacks.set(t, callbacks); @@ -54,6 +56,11 @@ export class CachedOperation { const result = await this.operation(t, ...args); callbacks.forEach((f) => f[0](result)); this.inProgressCallbacks.delete(t); + if (this.generation !== origGeneration) { + // Cache was reset in the meantime so don't trust this + // result enough to cache it. + return result; + } if (this.lru.length > this.cacheSize) { const toRemove = this.lru.shift()!; this.cached.delete(toRemove); @@ -69,4 +76,11 @@ export class CachedOperation { this.inProgressCallbacks.delete(t); } } + + reset() { + this.cached.clear(); + this.lru.length = 0; + this.generation++; + this.inProgressCallbacks.clear(); + } } diff --git a/extensions/ql-vscode/src/local-queries/results-view.ts b/extensions/ql-vscode/src/local-queries/results-view.ts index 18ee93f8543..4e28e9f9c73 100644 --- a/extensions/ql-vscode/src/local-queries/results-view.ts +++ b/extensions/ql-vscode/src/local-queries/results-view.ts @@ -75,6 +75,7 @@ import type { App } from "../common/app"; import type { Disposable } from "../common/disposable-object"; import type { RawResultSet } from "../common/raw-result-types"; import type { BqrsResultSetSchema } from "../common/bqrs-cli-types"; +import { CachedOperation } from "../language-support/contextual/cached-operation"; /** * results-view.ts @@ -177,6 +178,8 @@ export class ResultsView extends AbstractWebview< // Event listeners that should be disposed of when the view is disposed. private disposableEventListeners: Disposable[] = []; + private schemaCache: CachedOperation<[], BqrsResultSetSchema[]>; + constructor( app: App, private databaseManager: DatabaseManager, @@ -206,6 +209,10 @@ export class ResultsView extends AbstractWebview< } }), ); + + this.schemaCache = new CachedOperation( + this.getResultSetSchemasImpl.bind(this), + ); } public getCommands(): ResultsViewCommands { @@ -420,6 +427,7 @@ export class ResultsView extends AbstractWebview< ); return; } + this.schemaCache.reset(); // Notify the webview that it should expect new results. await this.postMessage({ t: "resultsUpdating" }); await this._displayedQuery.completedQuery.updateSortState( @@ -610,6 +618,12 @@ export class ResultsView extends AbstractWebview< selectedTable = "", ): Promise { const resultsPath = completedQuery.getResultsPath(selectedTable); + return this.schemaCache.get(resultsPath); + } + + private async getResultSetSchemasImpl( + resultsPath: string, + ): Promise { const schemas = await this.cliServer.bqrsInfo( resultsPath, PAGE_SIZE.getValue(), diff --git a/extensions/ql-vscode/src/run-queries-shared.ts b/extensions/ql-vscode/src/run-queries-shared.ts index 6dbaa8e651b..dac447ee20b 100644 --- a/extensions/ql-vscode/src/run-queries-shared.ts +++ b/extensions/ql-vscode/src/run-queries-shared.ts @@ -244,7 +244,7 @@ export class QueryEvaluationInfo extends QueryOutputDir { */ async chooseResultSet(cliServer: CodeQLCliServer) { const resultSets = ( - await cliServer.bqrsInfo(this.resultsPaths.resultsPath, 0) + await cliServer.bqrsInfo(this.resultsPaths.resultsPath) )["result-sets"]; if (!resultSets.length) { return undefined;