@@ -9,10 +9,22 @@ import type { CodeQLCliServer } from "../codeql-cli/cli";
99import type { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager" ;
1010import type { QueryLanguage } from "../common/query-language" ;
1111import { resolveCodeScanningQueryPack } from "../variant-analysis/code-scanning-pack" ;
12- import { withProgress } from "../common/vscode/progress" ;
12+ import type { ProgressCallback } from "../common/vscode/progress" ;
13+ import {
14+ UserCancellationException ,
15+ withProgress ,
16+ } from "../common/vscode/progress" ;
1317import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysis" ;
18+ import type { CancellationToken } from "vscode" ;
19+ import { CancellationTokenSource } from "vscode" ;
20+ import type { QlPackDetails } from "../variant-analysis/ql-pack-details" ;
1421
1522export class ModelEvaluator extends DisposableObject {
23+ // Cancellation token source to allow cancelling of the current run
24+ // before a variant analysis has been submitted. Once it has been
25+ // submitted, we use the variant analysis manager's cancellation support.
26+ private cancellationSource : CancellationTokenSource ;
27+
1628 public constructor (
1729 private readonly logger : BaseLogger ,
1830 private readonly cliServer : CodeQLCliServer ,
@@ -28,6 +40,8 @@ export class ModelEvaluator extends DisposableObject {
2840 super ( ) ;
2941
3042 this . registerToModelingEvents ( ) ;
43+
44+ this . cancellationSource = new CancellationTokenSource ( ) ;
3145 }
3246
3347 public async startEvaluation ( ) {
@@ -52,30 +66,12 @@ export class ModelEvaluator extends DisposableObject {
5266
5367 // Submit variant analysis and monitor progress
5468 return withProgress (
55- async ( progress , token ) => {
56- let variantAnalysisId : number | undefined = undefined ;
57- try {
58- variantAnalysisId =
59- await this . variantAnalysisManager . runVariantAnalysis (
60- qlPack ,
61- progress ,
62- token ,
63- false ,
64- ) ;
65- } catch ( e ) {
66- this . modelingStore . updateModelEvaluationRun ( this . dbItem , undefined ) ;
67- throw e ;
68- }
69-
70- if ( variantAnalysisId ) {
71- this . monitorVariantAnalysis ( variantAnalysisId ) ;
72- } else {
73- this . modelingStore . updateModelEvaluationRun ( this . dbItem , undefined ) ;
74- throw new Error (
75- "Unable to trigger variant analysis for evaluation run" ,
76- ) ;
77- }
78- } ,
69+ ( progress ) =>
70+ this . runVariantAnalysis (
71+ qlPack ,
72+ progress ,
73+ this . cancellationSource . token ,
74+ ) ,
7975 {
8076 title : "Run model evaluation" ,
8177 cancellable : false ,
@@ -84,13 +80,29 @@ export class ModelEvaluator extends DisposableObject {
8480 }
8581
8682 public async stopEvaluation ( ) {
87- // For now just update the store.
88- // This will be fleshed out in the near future.
89- const evaluationRun : ModelEvaluationRun = {
90- isPreparing : false ,
91- variantAnalysisId : undefined ,
92- } ;
93- this . modelingStore . updateModelEvaluationRun ( this . dbItem , evaluationRun ) ;
83+ const evaluationRun = this . modelingStore . getModelEvaluationRun ( this . dbItem ) ;
84+ if ( ! evaluationRun ) {
85+ void this . logger . log ( "No active evaluation run to stop" ) ;
86+ return ;
87+ }
88+
89+ this . cancellationSource . cancel ( ) ;
90+
91+ if ( evaluationRun . variantAnalysisId === undefined ) {
92+ // If the variant analysis has not been submitted yet, we can just
93+ // update the store.
94+ this . modelingStore . updateModelEvaluationRun ( this . dbItem , {
95+ ...evaluationRun ,
96+ isPreparing : false ,
97+ } ) ;
98+ } else {
99+ // If the variant analysis has been submitted, we need to cancel it. We
100+ // don't need to update the store here, as the event handler for
101+ // onVariantAnalysisStatusUpdated will do that for us.
102+ await this . variantAnalysisManager . cancelVariantAnalysis (
103+ evaluationRun . variantAnalysisId ,
104+ ) ;
105+ }
94106 }
95107
96108 private registerToModelingEvents ( ) {
@@ -128,6 +140,60 @@ export class ModelEvaluator extends DisposableObject {
128140 return undefined ;
129141 }
130142
143+ private async runVariantAnalysis (
144+ qlPack : QlPackDetails ,
145+ progress : ProgressCallback ,
146+ token : CancellationToken ,
147+ ) : Promise < number | void > {
148+ let result : number | void = undefined ;
149+ try {
150+ // Use Promise.race to make sure to stop the variant analysis processing when the
151+ // user has stopped the evaluation run. We can't simply rely on the cancellation token
152+ // because we haven't fully implemented cancellation support for variant analysis.
153+ // Using this approach we make sure that the process is stopped from a user's point
154+ // of view (the notification goes away too). It won't necessarily stop any tasks
155+ // that are not aware of the cancellation token.
156+ result = await Promise . race ( [
157+ this . variantAnalysisManager . runVariantAnalysis (
158+ qlPack ,
159+ progress ,
160+ token ,
161+ false ,
162+ ) ,
163+ new Promise < void > ( ( _ , reject ) => {
164+ token . onCancellationRequested ( ( ) =>
165+ reject ( new UserCancellationException ( undefined , true ) ) ,
166+ ) ;
167+ } ) ,
168+ ] ) ;
169+ } catch ( e ) {
170+ this . modelingStore . updateModelEvaluationRun ( this . dbItem , undefined ) ;
171+ if ( ! ( e instanceof UserCancellationException ) ) {
172+ throw e ;
173+ } else {
174+ return ;
175+ }
176+ } finally {
177+ // Renew the cancellation token source for the new evaluation run.
178+ // This is necessary because we don't want the next evaluation run
179+ // to start as cancelled.
180+ this . cancellationSource = new CancellationTokenSource ( ) ;
181+ }
182+
183+ // If the result is a number, it means the variant analysis was successfully submitted,
184+ // so we need to update the store and start up the monitor.
185+ if ( typeof result === "number" ) {
186+ this . modelingStore . updateModelEvaluationRun ( this . dbItem , {
187+ isPreparing : true ,
188+ variantAnalysisId : result ,
189+ } ) ;
190+ this . monitorVariantAnalysis ( result ) ;
191+ } else {
192+ this . modelingStore . updateModelEvaluationRun ( this . dbItem , undefined ) ;
193+ throw new Error ( "Unable to trigger variant analysis for evaluation run" ) ;
194+ }
195+ }
196+
131197 private monitorVariantAnalysis ( variantAnalysisId : number ) {
132198 this . push (
133199 this . variantAnalysisManager . onVariantAnalysisStatusUpdated (
0 commit comments