Files
LiquidCode.Tester/src/LiquidCode.Tester.Worker/Services/CSharpExecutionServiceIsolate.cs
prixod 5ed5925a38
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 48s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 57s
fix local testing & isolate & C# processing
2025-12-01 02:26:57 +04:00

204 lines
8.0 KiB
C#

using System.Diagnostics;
using LiquidCode.Tester.Worker.Services.Isolate;
using System.Collections.Generic;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// C# program execution service using Isolate sandbox
/// </summary>
public class CSharpExecutionServiceIsolate : IExecutionService
{
private readonly ILogger<CSharpExecutionServiceIsolate> _logger;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
public CSharpExecutionServiceIsolate(
ILogger<CSharpExecutionServiceIsolate> 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 C# with Isolate: {Executable}, 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 input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
var stderrFilePath = Path.Combine(boxDir, "stderr.txt");
string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Run in Isolate using mono runtime
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = "/usr/bin/mono",
Arguments = new[] { $"/box/{executableName}" },
TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = memoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 64, // Mono may create multiple threads/processes
EnableNetwork = false,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/share", SandboxPath = "/usr/share", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc/mono", SandboxPath = "/etc/mono", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true }
}
});
stopwatch.Stop();
// Read output
if (File.Exists(outputFilePath))
{
result.Output = await File.ReadAllTextAsync(outputFilePath);
}
// Read stderr
var stderrContent = string.Empty;
if (File.Exists(stderrFilePath))
{
stderrContent = await File.ReadAllTextAsync(stderrFilePath);
}
// Map Isolate result to ExecutionResult
result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000);
result.MemoryUsedMb = isolateResult.MemoryUsedKb / 1024;
result.ErrorOutput = string.IsNullOrEmpty(stderrContent)
? isolateResult.ErrorOutput
: $"{stderrContent}\n{isolateResult.ErrorOutput}".Trim();
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);
}
_logger.LogDebug(
"Isolate stats: CPU={Cpu}s, Wall={Wall}s, Memory={Mem}KB",
isolateResult.CpuTimeSeconds,
isolateResult.WallTimeSeconds,
isolateResult.MemoryUsedKb);
}
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;
}
}