trigger.dev
Version:
A Command-Line Interface for Trigger.dev projects
220 lines • 9.98 kB
JavaScript
import { TaskRunProcess } from "../executions/taskRunProcess.js";
import { logger } from "../utilities/logger.js";
export class TaskRunProcessPool {
// Group processes by worker version
availableProcessesByVersion = new Map();
busyProcessesByVersion = new Map();
options;
maxPoolSize;
maxExecutionsPerProcess;
executionCountsPerProcess = new Map();
deprecatedVersions = new Set();
constructor(options) {
this.options = options;
this.maxPoolSize = options.maxPoolSize ?? 3;
this.maxExecutionsPerProcess = options.maxExecutionsPerProcess ?? 50;
}
deprecateVersion(version) {
this.deprecatedVersions.add(version);
logger.debug("[TaskRunProcessPool] Deprecating version", { version });
const versionProcesses = this.availableProcessesByVersion.get(version) || [];
const processesToKill = versionProcesses.filter((process) => !process.isExecuting());
Promise.all(processesToKill.map((process) => this.killProcess(process))).then(() => {
this.availableProcessesByVersion.delete(version);
});
}
async getProcess(workerManifest, serverWorker, machineResources, env, cwd) {
const version = serverWorker.version || "unknown";
// Try to reuse an existing process if enabled
if (this.options.enableProcessReuse) {
const reusableProcess = this.findReusableProcess(version);
if (reusableProcess) {
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
logger.debug("[TaskRunProcessPool] Reusing existing process", {
version,
availableCount,
busyCount,
});
// Remove from available and add to busy for this version
const availableProcesses = this.availableProcessesByVersion.get(version) || [];
this.availableProcessesByVersion.set(version, availableProcesses.filter((p) => p !== reusableProcess));
if (!this.busyProcessesByVersion.has(version)) {
this.busyProcessesByVersion.set(version, new Set());
}
this.busyProcessesByVersion.get(version).add(reusableProcess);
return { taskRunProcess: reusableProcess, isReused: true };
}
else {
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
logger.debug("[TaskRunProcessPool] No reusable process found", {
version,
availableCount,
busyCount,
});
}
}
// Create new process
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
logger.debug("[TaskRunProcessPool] Creating new process", {
version,
availableCount,
busyCount,
workerManifest,
});
const newProcess = new TaskRunProcess({
workerManifest,
env: {
...this.options.env,
...env,
},
serverWorker,
machineResources,
cwd: cwd ?? this.options.cwd,
}).initialize();
// Add to busy processes for this version
if (!this.busyProcessesByVersion.has(version)) {
this.busyProcessesByVersion.set(version, new Set());
}
this.busyProcessesByVersion.get(version).add(newProcess);
return { taskRunProcess: newProcess, isReused: false };
}
async returnProcess(process, version) {
// Remove from busy processes for this version
const busyProcesses = this.busyProcessesByVersion.get(version);
if (busyProcesses) {
busyProcesses.delete(process);
}
if (process.pid) {
this.executionCountsPerProcess.set(process.pid, (this.executionCountsPerProcess.get(process.pid) ?? 0) + 1);
}
if (this.shouldReuseProcess(process, version)) {
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
logger.debug("[TaskRunProcessPool] Returning process to pool", {
version,
availableCount,
busyCount,
});
// Clean up but don't kill the process
try {
await process.cleanup(false);
// Add to available processes for this version
if (!this.availableProcessesByVersion.has(version)) {
this.availableProcessesByVersion.set(version, []);
}
this.availableProcessesByVersion.get(version).push(process);
}
catch (error) {
logger.debug("[TaskRunProcessPool] Failed to cleanup process for reuse, killing it", {
error,
});
await this.killProcess(process);
}
}
else {
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
logger.debug("[TaskRunProcessPool] Killing process", {
version,
availableCount,
busyCount,
});
await this.killProcess(process);
}
}
findReusableProcess(version) {
const availableProcesses = this.availableProcessesByVersion.get(version) || [];
return availableProcesses.find((process) => this.isProcessHealthy(process));
}
shouldReuseProcess(process, version) {
const isHealthy = this.isProcessHealthy(process);
const isBeingKilled = process.isBeingKilled;
const pid = process.pid;
const executionCount = this.executionCountsPerProcess.get(pid ?? 0) ?? 0;
const availableCount = this.availableProcessesByVersion.get(version)?.length || 0;
const busyCount = this.busyProcessesByVersion.get(version)?.size || 0;
const isDeprecated = this.deprecatedVersions.has(version);
logger.debug("[TaskRunProcessPool] Checking if process should be reused", {
version,
isHealthy,
isBeingKilled,
pid,
availableCount,
busyCount,
maxPoolSize: this.maxPoolSize,
executionCount,
isDeprecated,
});
return (this.options.enableProcessReuse &&
this.isProcessHealthy(process) &&
availableCount < this.maxPoolSize &&
executionCount < this.maxExecutionsPerProcess &&
!isDeprecated);
}
isProcessHealthy(process) {
// Basic health checks - we can expand this later
return process.isHealthy;
}
async killProcess(process) {
if (!process.isHealthy) {
logger.debug("[TaskRunProcessPool] Process is not healthy, skipping cleanup", {
processId: process.pid,
});
return;
}
try {
await process.cleanup(true);
}
catch (error) {
logger.debug("[TaskRunProcessPool] Error killing process", { error });
}
}
async shutdown() {
const totalAvailable = Array.from(this.availableProcessesByVersion.values()).reduce((sum, processes) => sum + processes.length, 0);
const totalBusy = Array.from(this.busyProcessesByVersion.values()).reduce((sum, processes) => sum + processes.size, 0);
logger.debug("[TaskRunProcessPool] Shutting down pool", {
availableCount: totalAvailable,
busyCount: totalBusy,
versions: Array.from(this.availableProcessesByVersion.keys()),
});
// Kill all available processes across all versions
const allAvailableProcesses = Array.from(this.availableProcessesByVersion.values()).flat();
await Promise.all(allAvailableProcesses.map((process) => this.killProcess(process)));
this.availableProcessesByVersion.clear();
// Kill all busy processes across all versions
const allBusyProcesses = Array.from(this.busyProcessesByVersion.values())
.map((processSet) => Array.from(processSet))
.flat();
await Promise.all(allBusyProcesses.map((process) => this.killProcess(process)));
this.busyProcessesByVersion.clear();
}
getStats() {
const totalAvailable = Array.from(this.availableProcessesByVersion.values()).reduce((sum, processes) => sum + processes.length, 0);
const totalBusy = Array.from(this.busyProcessesByVersion.values()).reduce((sum, processes) => sum + processes.size, 0);
const statsByVersion = {};
for (const [version, processes] of this.availableProcessesByVersion.entries()) {
statsByVersion[version] = {
available: processes.length,
busy: this.busyProcessesByVersion.get(version)?.size || 0,
};
}
for (const [version, processes] of this.busyProcessesByVersion.entries()) {
if (!statsByVersion[version]) {
statsByVersion[version] = {
available: 0,
busy: processes.size,
};
}
}
return {
availableCount: totalAvailable,
busyCount: totalBusy,
totalCount: totalAvailable + totalBusy,
byVersion: statsByVersion,
};
}
}
//# sourceMappingURL=taskRunProcessPool.js.map