@@ -15,48 +15,88 @@ export type TaskRunProcessPoolOptions = {
15
15
} ;
16
16
17
17
export class TaskRunProcessPool {
18
- private availableProcesses : TaskRunProcess [ ] = [ ] ;
19
- private busyProcesses : Set < TaskRunProcess > = new Set ( ) ;
18
+ // Group processes by worker version
19
+ private availableProcessesByVersion : Map < string , TaskRunProcess [ ] > = new Map ( ) ;
20
+ private busyProcessesByVersion : Map < string , Set < TaskRunProcess > > = new Map ( ) ;
20
21
private readonly options : TaskRunProcessPoolOptions ;
21
22
private readonly maxPoolSize : number ;
22
23
private readonly maxExecutionsPerProcess : number ;
24
+ private readonly executionCountsPerProcess : Map < number , number > = new Map ( ) ;
25
+ private readonly deprecatedVersions : Set < string > = new Set ( ) ;
23
26
24
27
constructor ( options : TaskRunProcessPoolOptions ) {
25
28
this . options = options ;
26
29
this . maxPoolSize = options . maxPoolSize ?? 3 ;
27
30
this . maxExecutionsPerProcess = options . maxExecutionsPerProcess ?? 50 ;
28
31
}
29
32
33
+ deprecateVersion ( version : string ) {
34
+ this . deprecatedVersions . add ( version ) ;
35
+
36
+ logger . debug ( "[TaskRunProcessPool] Deprecating version" , { version } ) ;
37
+
38
+ const versionProcesses = this . availableProcessesByVersion . get ( version ) || [ ] ;
39
+
40
+ const processesToKill = versionProcesses . filter ( ( process ) => ! process . isExecuting ( ) ) ;
41
+ Promise . all ( processesToKill . map ( ( process ) => this . killProcess ( process ) ) ) . then ( ( ) => {
42
+ this . availableProcessesByVersion . delete ( version ) ;
43
+ } ) ;
44
+ }
45
+
30
46
async getProcess (
31
47
workerManifest : WorkerManifest ,
32
48
serverWorker : ServerBackgroundWorker ,
33
49
machineResources : MachinePresetResources ,
34
50
env ?: Record < string , string >
35
51
) : Promise < { taskRunProcess : TaskRunProcess ; isReused : boolean } > {
52
+ const version = serverWorker . version || "unknown" ;
53
+
36
54
// Try to reuse an existing process if enabled
37
55
if ( this . options . enableProcessReuse ) {
38
- const reusableProcess = this . findReusableProcess ( ) ;
56
+ const reusableProcess = this . findReusableProcess ( version ) ;
39
57
if ( reusableProcess ) {
58
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
59
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
60
+
40
61
logger . debug ( "[TaskRunProcessPool] Reusing existing process" , {
41
- availableCount : this . availableProcesses . length ,
42
- busyCount : this . busyProcesses . size ,
62
+ version,
63
+ availableCount,
64
+ busyCount,
43
65
} ) ;
44
66
45
- this . availableProcesses = this . availableProcesses . filter ( ( p ) => p !== reusableProcess ) ;
46
- this . busyProcesses . add ( reusableProcess ) ;
67
+ // Remove from available and add to busy for this version
68
+ const availableProcesses = this . availableProcessesByVersion . get ( version ) || [ ] ;
69
+ this . availableProcessesByVersion . set (
70
+ version ,
71
+ availableProcesses . filter ( ( p ) => p !== reusableProcess )
72
+ ) ;
73
+
74
+ if ( ! this . busyProcessesByVersion . has ( version ) ) {
75
+ this . busyProcessesByVersion . set ( version , new Set ( ) ) ;
76
+ }
77
+ this . busyProcessesByVersion . get ( version ) ! . add ( reusableProcess ) ;
78
+
47
79
return { taskRunProcess : reusableProcess , isReused : true } ;
48
80
} else {
81
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
82
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
83
+
49
84
logger . debug ( "[TaskRunProcessPool] No reusable process found" , {
50
- availableCount : this . availableProcesses . length ,
51
- busyCount : this . busyProcesses . size ,
85
+ version,
86
+ availableCount,
87
+ busyCount,
52
88
} ) ;
53
89
}
54
90
}
55
91
56
92
// Create new process
93
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
94
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
95
+
57
96
logger . debug ( "[TaskRunProcessPool] Creating new process" , {
58
- availableCount : this . availableProcesses . length ,
59
- busyCount : this . busyProcesses . size ,
97
+ version,
98
+ availableCount,
99
+ busyCount,
60
100
} ) ;
61
101
62
102
const newProcess = new TaskRunProcess ( {
@@ -70,60 +110,99 @@ export class TaskRunProcessPool {
70
110
cwd : this . options . cwd ,
71
111
} ) . initialize ( ) ;
72
112
73
- this . busyProcesses . add ( newProcess ) ;
113
+ // Add to busy processes for this version
114
+ if ( ! this . busyProcessesByVersion . has ( version ) ) {
115
+ this . busyProcessesByVersion . set ( version , new Set ( ) ) ;
116
+ }
117
+ this . busyProcessesByVersion . get ( version ) ! . add ( newProcess ) ;
118
+
74
119
return { taskRunProcess : newProcess , isReused : false } ;
75
120
}
76
121
77
- async returnProcess ( process : TaskRunProcess ) : Promise < void > {
78
- this . busyProcesses . delete ( process ) ;
122
+ async returnProcess ( process : TaskRunProcess , version : string ) : Promise < void > {
123
+ // Remove from busy processes for this version
124
+ const busyProcesses = this . busyProcessesByVersion . get ( version ) ;
125
+ if ( busyProcesses ) {
126
+ busyProcesses . delete ( process ) ;
127
+ }
128
+
129
+ if ( process . pid ) {
130
+ this . executionCountsPerProcess . set (
131
+ process . pid ,
132
+ ( this . executionCountsPerProcess . get ( process . pid ) ?? 0 ) + 1
133
+ ) ;
134
+ }
135
+
136
+ if ( this . shouldReuseProcess ( process , version ) ) {
137
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
138
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
79
139
80
- if ( this . shouldReuseProcess ( process ) ) {
81
140
logger . debug ( "[TaskRunProcessPool] Returning process to pool" , {
82
- availableCount : this . availableProcesses . length ,
83
- busyCount : this . busyProcesses . size ,
141
+ version,
142
+ availableCount,
143
+ busyCount,
84
144
} ) ;
85
145
86
146
// Clean up but don't kill the process
87
147
try {
88
148
await process . cleanup ( false ) ;
89
- this . availableProcesses . push ( process ) ;
149
+
150
+ // Add to available processes for this version
151
+ if ( ! this . availableProcessesByVersion . has ( version ) ) {
152
+ this . availableProcessesByVersion . set ( version , [ ] ) ;
153
+ }
154
+ this . availableProcessesByVersion . get ( version ) ! . push ( process ) ;
90
155
} catch ( error ) {
91
156
logger . debug ( "[TaskRunProcessPool] Failed to cleanup process for reuse, killing it" , {
92
157
error,
93
158
} ) ;
94
159
await this . killProcess ( process ) ;
95
160
}
96
161
} else {
162
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
163
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
164
+
97
165
logger . debug ( "[TaskRunProcessPool] Killing process" , {
98
- availableCount : this . availableProcesses . length ,
99
- busyCount : this . busyProcesses . size ,
166
+ version,
167
+ availableCount,
168
+ busyCount,
100
169
} ) ;
101
170
await this . killProcess ( process ) ;
102
171
}
103
172
}
104
173
105
- private findReusableProcess ( ) : TaskRunProcess | undefined {
106
- return this . availableProcesses . find ( ( process ) => this . isProcessHealthy ( process ) ) ;
174
+ private findReusableProcess ( version : string ) : TaskRunProcess | undefined {
175
+ const availableProcesses = this . availableProcessesByVersion . get ( version ) || [ ] ;
176
+ return availableProcesses . find ( ( process ) => this . isProcessHealthy ( process ) ) ;
107
177
}
108
178
109
- private shouldReuseProcess ( process : TaskRunProcess ) : boolean {
179
+ private shouldReuseProcess ( process : TaskRunProcess , version : string ) : boolean {
110
180
const isHealthy = this . isProcessHealthy ( process ) ;
111
181
const isBeingKilled = process . isBeingKilled ;
112
182
const pid = process . pid ;
183
+ const executionCount = this . executionCountsPerProcess . get ( pid ?? 0 ) ?? 0 ;
184
+ const availableCount = this . availableProcessesByVersion . get ( version ) ?. length || 0 ;
185
+ const busyCount = this . busyProcessesByVersion . get ( version ) ?. size || 0 ;
186
+ const isDeprecated = this . deprecatedVersions . has ( version ) ;
113
187
114
188
logger . debug ( "[TaskRunProcessPool] Checking if process should be reused" , {
189
+ version,
115
190
isHealthy,
116
191
isBeingKilled,
117
192
pid,
118
- availableCount : this . availableProcesses . length ,
119
- busyCount : this . busyProcesses . size ,
193
+ availableCount,
194
+ busyCount,
120
195
maxPoolSize : this . maxPoolSize ,
196
+ executionCount,
197
+ isDeprecated,
121
198
} ) ;
122
199
123
200
return (
124
201
this . options . enableProcessReuse &&
125
202
this . isProcessHealthy ( process ) &&
126
- this . availableProcesses . length < this . maxPoolSize
203
+ availableCount < this . maxPoolSize &&
204
+ executionCount < this . maxExecutionsPerProcess &&
205
+ ! isDeprecated
127
206
) ;
128
207
}
129
208
@@ -141,25 +220,65 @@ export class TaskRunProcessPool {
141
220
}
142
221
143
222
async shutdown ( ) : Promise < void > {
223
+ const totalAvailable = Array . from ( this . availableProcessesByVersion . values ( ) ) . reduce (
224
+ ( sum , processes ) => sum + processes . length ,
225
+ 0
226
+ ) ;
227
+ const totalBusy = Array . from ( this . busyProcessesByVersion . values ( ) ) . reduce (
228
+ ( sum , processes ) => sum + processes . size ,
229
+ 0
230
+ ) ;
231
+
144
232
logger . debug ( "[TaskRunProcessPool] Shutting down pool" , {
145
- availableCount : this . availableProcesses . length ,
146
- busyCount : this . busyProcesses . size ,
233
+ availableCount : totalAvailable ,
234
+ busyCount : totalBusy ,
235
+ versions : Array . from ( this . availableProcessesByVersion . keys ( ) ) ,
147
236
} ) ;
148
237
149
- // Kill all available processes
150
- await Promise . all ( this . availableProcesses . map ( ( process ) => this . killProcess ( process ) ) ) ;
151
- this . availableProcesses = [ ] ;
238
+ // Kill all available processes across all versions
239
+ const allAvailableProcesses = Array . from ( this . availableProcessesByVersion . values ( ) ) . flat ( ) ;
240
+ await Promise . all ( allAvailableProcesses . map ( ( process ) => this . killProcess ( process ) ) ) ;
241
+ this . availableProcessesByVersion . clear ( ) ;
152
242
153
- // Kill all busy processes
154
- await Promise . all ( Array . from ( this . busyProcesses ) . map ( ( process ) => this . killProcess ( process ) ) ) ;
155
- this . busyProcesses . clear ( ) ;
243
+ // Kill all busy processes across all versions
244
+ const allBusyProcesses = Array . from ( this . busyProcessesByVersion . values ( ) )
245
+ . map ( ( processSet ) => Array . from ( processSet ) )
246
+ . flat ( ) ;
247
+ await Promise . all ( allBusyProcesses . map ( ( process ) => this . killProcess ( process ) ) ) ;
248
+ this . busyProcessesByVersion . clear ( ) ;
156
249
}
157
250
158
251
getStats ( ) {
252
+ const totalAvailable = Array . from ( this . availableProcessesByVersion . values ( ) ) . reduce (
253
+ ( sum , processes ) => sum + processes . length ,
254
+ 0
255
+ ) ;
256
+ const totalBusy = Array . from ( this . busyProcessesByVersion . values ( ) ) . reduce (
257
+ ( sum , processes ) => sum + processes . size ,
258
+ 0
259
+ ) ;
260
+
261
+ const statsByVersion : Record < string , { available : number ; busy : number } > = { } ;
262
+ for ( const [ version , processes ] of this . availableProcessesByVersion . entries ( ) ) {
263
+ statsByVersion [ version ] = {
264
+ available : processes . length ,
265
+ busy : this . busyProcessesByVersion . get ( version ) ?. size || 0 ,
266
+ } ;
267
+ }
268
+ for ( const [ version , processes ] of this . busyProcessesByVersion . entries ( ) ) {
269
+ if ( ! statsByVersion [ version ] ) {
270
+ statsByVersion [ version ] = {
271
+ available : 0 ,
272
+ busy : processes . size ,
273
+ } ;
274
+ }
275
+ }
276
+
159
277
return {
160
- availableCount : this . availableProcesses . length ,
161
- busyCount : this . busyProcesses . size ,
162
- totalCount : this . availableProcesses . length + this . busyProcesses . size ,
278
+ availableCount : totalAvailable ,
279
+ busyCount : totalBusy ,
280
+ totalCount : totalAvailable + totalBusy ,
281
+ byVersion : statsByVersion ,
163
282
} ;
164
283
}
165
284
}
0 commit comments