@@ -15,63 +15,134 @@ import { CodeQLCliServer } from "../codeql-cli/cli";
1515import { QueryRunner } from "../query-server" ;
1616import { DatabaseItem } from "../databases/local-databases" ;
1717import { Mode } from "./shared/mode" ;
18+ import { CancellationTokenSource } from "vscode" ;
19+
20+ // Limit the number of candidates we send to the model in each request
21+ // to avoid long requests.
22+ // Note that the model may return fewer than this number of candidates.
23+ const candidateBatchSize = 20 ;
1824
1925export class AutoModeler {
26+ private readonly jobs : Map < string , CancellationTokenSource > ;
27+
2028 constructor (
2129 private readonly app : App ,
2230 private readonly cliServer : CodeQLCliServer ,
2331 private readonly queryRunner : QueryRunner ,
2432 private readonly queryStorageDir : string ,
2533 private readonly databaseItem : DatabaseItem ,
34+ private readonly setInProgressMethods : (
35+ inProgressMethods : string [ ] ,
36+ ) => Promise < void > ,
2637 private readonly addModeledMethods : (
2738 modeledMethods : Record < string , ModeledMethod > ,
2839 ) => Promise < void > ,
29- ) { }
40+ ) {
41+ this . jobs = new Map < string , CancellationTokenSource > ( ) ;
42+ }
3043
3144 public async startModeling (
3245 dependency : string ,
3346 externalApiUsages : ExternalApiUsage [ ] ,
3447 modeledMethods : Record < string , ModeledMethod > ,
3548 mode : Mode ,
3649 ) : Promise < void > {
37- await this . modelDependency (
38- dependency ,
39- externalApiUsages ,
40- modeledMethods ,
41- mode ,
42- ) ;
50+ if ( this . jobs . has ( dependency ) ) {
51+ return ;
52+ }
53+
54+ const cancellationTokenSource = new CancellationTokenSource ( ) ;
55+ this . jobs . set ( dependency , cancellationTokenSource ) ;
56+
57+ try {
58+ await this . modelDependency (
59+ dependency ,
60+ externalApiUsages ,
61+ modeledMethods ,
62+ mode ,
63+ cancellationTokenSource ,
64+ ) ;
65+ } finally {
66+ this . jobs . delete ( dependency ) ;
67+ }
68+ }
69+
70+ public async stopModeling ( dependency : string ) : Promise < void > {
71+ void extLogger . log ( `Stopping modeling for dependency ${ dependency } ` ) ;
72+ const cancellationTokenSource = this . jobs . get ( dependency ) ;
73+ if ( cancellationTokenSource ) {
74+ cancellationTokenSource . cancel ( ) ;
75+ }
76+ }
77+
78+ public async stopAllModeling ( ) : Promise < void > {
79+ for ( const cancellationTokenSource of this . jobs . values ( ) ) {
80+ cancellationTokenSource . cancel ( ) ;
81+ }
4382 }
4483
4584 private async modelDependency (
4685 dependency : string ,
4786 externalApiUsages : ExternalApiUsage [ ] ,
4887 modeledMethods : Record < string , ModeledMethod > ,
4988 mode : Mode ,
89+ cancellationTokenSource : CancellationTokenSource ,
5090 ) : Promise < void > {
5191 void extLogger . log ( `Modeling dependency ${ dependency } ` ) ;
5292 await withProgress ( async ( progress ) => {
5393 const maxStep = 3000 ;
5494
55- progress ( {
56- step : 0 ,
57- maxStep,
58- message : "Retrieving usages" ,
59- } ) ;
60-
6195 // Fetch the candidates to send to the model
62- const candidateMethods = getCandidates (
96+ const allCandidateMethods = getCandidates (
6397 mode ,
6498 externalApiUsages ,
6599 modeledMethods ,
66100 ) ;
67101
68102 // If there are no candidates, there is nothing to model and we just return
69- if ( candidateMethods . length === 0 ) {
103+ if ( allCandidateMethods . length === 0 ) {
70104 void extLogger . log ( "No candidates to model. Stopping." ) ;
71105 return ;
72106 }
73107
74- await this . modelCandidates ( candidateMethods , mode , progress , maxStep ) ;
108+ // Find number of slices to make
109+ const batchNumber = Math . ceil (
110+ allCandidateMethods . length / candidateBatchSize ,
111+ ) ;
112+ try {
113+ for ( let i = 0 ; i < batchNumber ; i ++ ) {
114+ if ( cancellationTokenSource . token . isCancellationRequested ) {
115+ break ;
116+ }
117+
118+ const start = i * candidateBatchSize ;
119+ const end = start + candidateBatchSize ;
120+ const candidatesToProcess = allCandidateMethods . slice ( start , end ) ;
121+
122+ await this . setInProgressMethods (
123+ candidatesToProcess . map ( ( c ) => c . signature ) ,
124+ ) ;
125+
126+ progress ( {
127+ step : 1800 + i * 100 ,
128+ maxStep,
129+ message : `Automodeling candidates, batch ${
130+ i + 1
131+ } of ${ batchNumber } `,
132+ } ) ;
133+
134+ await this . modelCandidates (
135+ candidatesToProcess ,
136+ mode ,
137+ progress ,
138+ maxStep ,
139+ cancellationTokenSource ,
140+ ) ;
141+ }
142+ } finally {
143+ // Clear out in progress methods
144+ await this . setInProgressMethods ( [ ] ) ;
145+ }
75146 } ) ;
76147 }
77148
@@ -80,6 +151,7 @@ export class AutoModeler {
80151 mode : Mode ,
81152 progress : ProgressCallback ,
82153 maxStep : number ,
154+ cancellationTokenSource : CancellationTokenSource ,
83155 ) : Promise < void > {
84156 const usages = await runAutoModelQueries ( {
85157 mode,
@@ -89,6 +161,7 @@ export class AutoModeler {
89161 queryStorageDir : this . queryStorageDir ,
90162 databaseItem : this . databaseItem ,
91163 progress : ( update ) => progress ( { ...update , maxStep } ) ,
164+ cancellationTokenSource,
92165 } ) ;
93166 if ( ! usages ) {
94167 return ;
0 commit comments