180 lines
6.7 KiB
C#
180 lines
6.7 KiB
C#
using System.Diagnostics;
|
|
using LiquidCode.Tester.Worker.Services.Isolate;
|
|
|
|
namespace LiquidCode.Tester.Worker.Services;
|
|
|
|
/// <summary>
|
|
/// C++ program execution service using Isolate sandbox
|
|
/// </summary>
|
|
public class CppExecutionServiceIsolate : IExecutionService
|
|
{
|
|
private readonly ILogger<CppExecutionServiceIsolate> _logger;
|
|
private readonly IsolateService _isolateService;
|
|
private readonly IsolateBoxPool _boxPool;
|
|
|
|
public CppExecutionServiceIsolate(
|
|
ILogger<CppExecutionServiceIsolate> logger,
|
|
IsolateService isolateService,
|
|
IsolateBoxPool boxPool)
|
|
{
|
|
_logger = logger;
|
|
_isolateService = isolateService;
|
|
_boxPool = boxPool;
|
|
}
|
|
|
|
public async Task<ExecutionResult> ExecuteAsync(
|
|
string executablePath,
|
|
string inputFilePath,
|
|
int timeLimitMs,
|
|
int memoryLimitMb)
|
|
{
|
|
_logger.LogInformation(
|
|
"Executing {Executable} with Isolate: time={TimeLimit}ms, memory={MemoryLimit}MB",
|
|
executablePath, timeLimitMs, memoryLimitMb);
|
|
|
|
var result = new ExecutionResult();
|
|
var stopwatch = Stopwatch.StartNew();
|
|
int boxId = -1;
|
|
|
|
try
|
|
{
|
|
// Acquire a box from pool
|
|
boxId = await _boxPool.AcquireBoxAsync();
|
|
_logger.LogDebug("Acquired isolate box {BoxId}", boxId);
|
|
|
|
// Initialize the box
|
|
await _isolateService.InitBoxAsync(boxId);
|
|
|
|
// Copy executable to box
|
|
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
|
|
var executableName = Path.GetFileName(executablePath);
|
|
var boxExecutablePath = Path.Combine(boxDir, executableName);
|
|
|
|
File.Copy(executablePath, boxExecutablePath, overwrite: true);
|
|
|
|
// Make executable
|
|
var chmodProcess = Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = "chmod",
|
|
Arguments = $"+x {boxExecutablePath}",
|
|
UseShellExecute = false
|
|
});
|
|
chmodProcess?.WaitForExit();
|
|
|
|
// Prepare output file in box
|
|
var outputFilePath = Path.Combine(boxDir, "output.txt");
|
|
|
|
// Run in Isolate
|
|
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
|
|
{
|
|
BoxId = boxId,
|
|
Executable = $"/box/{executableName}",
|
|
TimeLimitSeconds = timeLimitMs / 1000.0,
|
|
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
|
|
MemoryLimitKb = memoryLimitMb * 1024,
|
|
StackLimitKb = 256 * 1024, // 256 MB stack
|
|
ProcessLimit = 1, // Single process only
|
|
EnableNetwork = false, // No network access
|
|
StdinFile = inputFilePath,
|
|
StdoutFile = outputFilePath,
|
|
WorkingDirectory = "/box"
|
|
});
|
|
|
|
stopwatch.Stop();
|
|
|
|
// Read output
|
|
if (File.Exists(outputFilePath))
|
|
{
|
|
result.Output = await File.ReadAllTextAsync(outputFilePath);
|
|
}
|
|
|
|
// Map Isolate result to ExecutionResult
|
|
result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000);
|
|
result.MemoryUsedMb = isolateResult.MemoryUsedKb / 1024;
|
|
result.ErrorOutput = isolateResult.ErrorOutput;
|
|
result.ExitCode = isolateResult.ExitCode;
|
|
|
|
if (isolateResult.TimeLimitExceeded)
|
|
{
|
|
result.TimeLimitExceeded = true;
|
|
result.ErrorMessage = $"Time limit exceeded: {isolateResult.CpuTimeSeconds:F3}s";
|
|
_logger.LogWarning("Time limit exceeded for box {BoxId}", boxId);
|
|
}
|
|
else if (isolateResult.MemoryLimitExceeded)
|
|
{
|
|
result.MemoryLimitExceeded = true;
|
|
result.ErrorMessage = $"Memory limit exceeded: {isolateResult.MemoryUsedKb / 1024}MB";
|
|
if (isolateResult.CgroupOomKilled)
|
|
{
|
|
result.ErrorMessage += " (OOM killed by cgroup)";
|
|
}
|
|
_logger.LogWarning("Memory limit exceeded for box {BoxId}", boxId);
|
|
}
|
|
else if (isolateResult.RuntimeError)
|
|
{
|
|
result.RuntimeError = true;
|
|
result.ErrorMessage = $"Runtime error: {isolateResult.Message}";
|
|
if (isolateResult.ExitSignal.HasValue)
|
|
{
|
|
result.ErrorMessage += $" (signal {isolateResult.ExitSignal})";
|
|
}
|
|
_logger.LogWarning("Runtime error for box {BoxId}: {Message}", boxId, isolateResult.Message);
|
|
}
|
|
else if (isolateResult.ExitCode == 0)
|
|
{
|
|
result.Success = true;
|
|
_logger.LogInformation(
|
|
"Execution successful: time={Time}ms, memory={Memory}MB, box={BoxId}",
|
|
result.ExecutionTimeMs, result.MemoryUsedMb, boxId);
|
|
}
|
|
else
|
|
{
|
|
result.RuntimeError = true;
|
|
result.ErrorMessage = $"Non-zero exit code: {isolateResult.ExitCode}";
|
|
_logger.LogWarning("Non-zero exit code {ExitCode} for box {BoxId}",
|
|
isolateResult.ExitCode, boxId);
|
|
}
|
|
|
|
// Log detailed statistics
|
|
_logger.LogDebug(
|
|
"Isolate stats: CPU={Cpu}s, Wall={Wall}s, Memory={Mem}KB, " +
|
|
"VoluntaryContextSwitches={Vol}, ForcedContextSwitches={Forced}",
|
|
isolateResult.CpuTimeSeconds,
|
|
isolateResult.WallTimeSeconds,
|
|
isolateResult.MemoryUsedKb,
|
|
isolateResult.VoluntaryContextSwitches,
|
|
isolateResult.ForcedContextSwitches);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
stopwatch.Stop();
|
|
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
|
|
result.RuntimeError = true;
|
|
result.ErrorMessage = $"Execution error: {ex.Message}";
|
|
_logger.LogError(ex, "Error during Isolate execution");
|
|
}
|
|
finally
|
|
{
|
|
// Cleanup box
|
|
if (boxId >= 0)
|
|
{
|
|
try
|
|
{
|
|
await _isolateService.CleanupBoxAsync(boxId);
|
|
_logger.LogDebug("Cleaned up isolate box {BoxId}", boxId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to cleanup box {BoxId}", boxId);
|
|
}
|
|
|
|
// Release box back to pool
|
|
_boxPool.ReleaseBox(boxId);
|
|
_logger.LogDebug("Released box {BoxId} back to pool", boxId);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|