/**
* Execute the TexasSolver binary
*/
import { spawn } from 'child_process';
import fs from 'fs/promises';
import path from 'path';
export class SolverExecutor {
constructor(solverPath, options = {}) {
this.solverPath = solverPath;
this.timeout = options.timeout || 10 * 60 * 1000; // 10 minutes default
this.solverDir = path.dirname(solverPath);
}
/**
* Execute the solver with a command file
* @param {string} commandFilePath - Path to command file
* @param {string} outputFilePath - Path where output should be written
* @returns {Promise<Object>} Execution result
*/
async execute(commandFilePath, outputFilePath) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
let stdout = '';
let stderr = '';
let timedOut = false;
// Spawn solver process
const solverProcess = spawn(this.solverPath, [], {
cwd: this.solverDir,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: this.timeout
});
// Read and pipe command file to stdin
fs.readFile(commandFilePath, 'utf8')
.then(commandText => {
solverProcess.stdin.write(commandText);
solverProcess.stdin.end();
})
.catch(error => {
solverProcess.kill();
reject(new Error(`Failed to read command file: ${error.message}`));
});
// Capture stdout
solverProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
// Capture stderr
solverProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
// Handle process completion
solverProcess.on('close', async (code) => {
const executionTime = Date.now() - startTime;
// Verify output file exists
const outputExists = await this._outputFileExists(outputFilePath);
if (code === 0 && outputExists) {
resolve({
success: true,
output_file: outputFilePath,
command_file: commandFilePath,
execution_time_ms: executionTime,
solver_log: stdout,
stderr: stderr
});
} else if (timedOut) {
reject(new Error(`Solver execution timed out after ${this.timeout}ms`));
} else if (!outputExists) {
reject(new Error(
`Solver did not produce output file. Exit code: ${code}\n` +
`Stderr: ${stderr}\n` +
`Stdout: ${stdout}`
));
} else {
reject(new Error(
`Solver exited with code ${code}\n` +
`Stderr: ${stderr}\n` +
`Stdout: ${stdout}`
));
}
});
// Handle process errors
solverProcess.on('error', (err) => {
reject(new Error(`Failed to start solver process: ${err.message}`));
});
// Set timeout
const timeoutHandle = setTimeout(() => {
timedOut = true;
solverProcess.kill('SIGTERM');
// If SIGTERM doesn't work, try SIGKILL after 5 seconds
const killTimeout = setTimeout(() => {
solverProcess.kill('SIGKILL');
}, 5000);
solverProcess.on('exit', () => clearTimeout(killTimeout));
}, this.timeout);
// Clear timeout when process ends
solverProcess.on('exit', () => clearTimeout(timeoutHandle));
});
}
/**
* Check if solver binary is executable
* @param {string} solverPath - Path to solver
* @returns {Promise<boolean>}
*/
static async validateSolver(solverPath) {
try {
const stats = await fs.stat(solverPath);
if (!stats.isFile()) {
return false;
}
// Check if executable (Unix permission check)
return (stats.mode & parseInt('0111', 8)) !== 0;
} catch {
return false;
}
}
/**
* Check if output file exists and has content
* @private
* @param {string} filepath
* @returns {Promise<boolean>}
*/
async _outputFileExists(filepath) {
try {
const stats = await fs.stat(filepath);
return stats.isFile() && stats.size > 0;
} catch {
return false;
}
}
/**
* Get information about the solver
* @returns {Promise<Object>}
*/
async getSolverInfo() {
try {
const stats = await fs.stat(this.solverPath);
return {
path: this.solverPath,
exists: true,
size_bytes: stats.size,
is_executable: (stats.mode & parseInt('0111', 8)) !== 0,
modified: stats.mtime.toISOString()
};
} catch (error) {
return {
path: this.solverPath,
exists: false,
error: error.message
};
}
}
}
export default SolverExecutor;