add isolate integrity
This commit is contained in:
@@ -41,9 +41,17 @@ builder.Services.AddSingleton<CppExecutionService>();
|
||||
builder.Services.AddSingleton<CppExecutionServiceIsolate>();
|
||||
|
||||
builder.Services.AddSingleton<JavaExecutionService>();
|
||||
builder.Services.AddSingleton<JavaExecutionServiceIsolate>();
|
||||
|
||||
builder.Services.AddSingleton<KotlinExecutionService>();
|
||||
builder.Services.AddSingleton<KotlinExecutionServiceIsolate>();
|
||||
|
||||
builder.Services.AddSingleton<CSharpExecutionService>();
|
||||
builder.Services.AddSingleton<CSharpExecutionServiceIsolate>();
|
||||
|
||||
builder.Services.AddSingleton<PythonExecutionService>();
|
||||
builder.Services.AddSingleton<PythonExecutionServiceIsolate>();
|
||||
|
||||
builder.Services.AddSingleton<IExecutionServiceFactory, ExecutionServiceFactory>();
|
||||
|
||||
// Register testing service
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
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 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 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,
|
||||
ProcessLimit = 1, // Single process for C#
|
||||
EnableNetwork = false,
|
||||
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);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,18 @@ public class ExecutionServiceFactory : IExecutionServiceFactory
|
||||
"c++" or "cpp" => _useIsolate
|
||||
? _serviceProvider.GetRequiredService<CppExecutionServiceIsolate>()
|
||||
: _serviceProvider.GetRequiredService<CppExecutionService>(),
|
||||
"java" => _serviceProvider.GetRequiredService<JavaExecutionService>(),
|
||||
"kotlin" => _serviceProvider.GetRequiredService<KotlinExecutionService>(),
|
||||
"c#" or "csharp" => _serviceProvider.GetRequiredService<CSharpExecutionService>(),
|
||||
"python" => _serviceProvider.GetRequiredService<PythonExecutionService>(),
|
||||
"java" => _useIsolate
|
||||
? _serviceProvider.GetRequiredService<JavaExecutionServiceIsolate>()
|
||||
: _serviceProvider.GetRequiredService<JavaExecutionService>(),
|
||||
"kotlin" => _useIsolate
|
||||
? _serviceProvider.GetRequiredService<KotlinExecutionServiceIsolate>()
|
||||
: _serviceProvider.GetRequiredService<KotlinExecutionService>(),
|
||||
"c#" or "csharp" => _useIsolate
|
||||
? _serviceProvider.GetRequiredService<CSharpExecutionServiceIsolate>()
|
||||
: _serviceProvider.GetRequiredService<CSharpExecutionService>(),
|
||||
"python" => _useIsolate
|
||||
? _serviceProvider.GetRequiredService<PythonExecutionServiceIsolate>()
|
||||
: _serviceProvider.GetRequiredService<PythonExecutionService>(),
|
||||
_ => throw new NotSupportedException($"Language '{language}' is not supported")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
using System.Diagnostics;
|
||||
using LiquidCode.Tester.Worker.Services.Isolate;
|
||||
|
||||
namespace LiquidCode.Tester.Worker.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Java program execution service using Isolate sandbox
|
||||
/// </summary>
|
||||
public class JavaExecutionServiceIsolate : IExecutionService
|
||||
{
|
||||
private readonly ILogger<JavaExecutionServiceIsolate> _logger;
|
||||
private readonly IsolateService _isolateService;
|
||||
private readonly IsolateBoxPool _boxPool;
|
||||
|
||||
public JavaExecutionServiceIsolate(
|
||||
ILogger<JavaExecutionServiceIsolate> logger,
|
||||
IsolateService isolateService,
|
||||
IsolateBoxPool boxPool)
|
||||
{
|
||||
_logger = logger;
|
||||
_isolateService = isolateService;
|
||||
_boxPool = boxPool;
|
||||
}
|
||||
|
||||
public async Task<ExecutionResult> ExecuteAsync(
|
||||
string executablePath,
|
||||
string inputFilePath,
|
||||
int timeLimitMs,
|
||||
int memoryLimitMb)
|
||||
{
|
||||
var workingDirectory = Path.GetDirectoryName(executablePath)!;
|
||||
_logger.LogInformation(
|
||||
"Executing Java with Isolate from {WorkingDirectory}: time={TimeLimit}ms, memory={MemoryLimit}MB",
|
||||
workingDirectory, 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 all .class files to box (Java needs classpath)
|
||||
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
|
||||
|
||||
// Copy all files from working directory (includes Solution.class and dependencies)
|
||||
foreach (var file in Directory.GetFiles(workingDirectory, "*.*"))
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
var destPath = Path.Combine(boxDir, fileName);
|
||||
File.Copy(file, destPath, overwrite: true);
|
||||
}
|
||||
|
||||
// Prepare output file in box
|
||||
var outputFilePath = Path.Combine(boxDir, "output.txt");
|
||||
|
||||
// Run in Isolate
|
||||
// Note: Java needs more memory for JVM overhead
|
||||
var javaMemoryMb = Math.Max(memoryLimitMb, 128); // Minimum 128MB for JVM
|
||||
|
||||
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
|
||||
{
|
||||
BoxId = boxId,
|
||||
Executable = "/usr/bin/java",
|
||||
Arguments = new[] { "-cp", "/box", "Solution" },
|
||||
TimeLimitSeconds = timeLimitMs / 1000.0,
|
||||
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
|
||||
MemoryLimitKb = javaMemoryMb * 1024,
|
||||
StackLimitKb = 256 * 1024, // 256 MB stack
|
||||
ProcessLimit = 64, // Java creates multiple threads
|
||||
EnableNetwork = false,
|
||||
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);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using System.Diagnostics;
|
||||
using LiquidCode.Tester.Worker.Services.Isolate;
|
||||
|
||||
namespace LiquidCode.Tester.Worker.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Kotlin program execution service using Isolate sandbox
|
||||
/// </summary>
|
||||
public class KotlinExecutionServiceIsolate : IExecutionService
|
||||
{
|
||||
private readonly ILogger<KotlinExecutionServiceIsolate> _logger;
|
||||
private readonly IsolateService _isolateService;
|
||||
private readonly IsolateBoxPool _boxPool;
|
||||
|
||||
public KotlinExecutionServiceIsolate(
|
||||
ILogger<KotlinExecutionServiceIsolate> 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 Kotlin JAR 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 JAR file to box
|
||||
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
|
||||
var jarName = Path.GetFileName(executablePath);
|
||||
var boxJarPath = Path.Combine(boxDir, jarName);
|
||||
|
||||
File.Copy(executablePath, boxJarPath, overwrite: true);
|
||||
|
||||
// Prepare output file in box
|
||||
var outputFilePath = Path.Combine(boxDir, "output.txt");
|
||||
|
||||
// Run in Isolate (Kotlin runs via Java)
|
||||
var kotlinMemoryMb = Math.Max(memoryLimitMb, 128); // Minimum 128MB for JVM
|
||||
|
||||
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
|
||||
{
|
||||
BoxId = boxId,
|
||||
Executable = "/usr/bin/java",
|
||||
Arguments = new[] { "-jar", $"/box/{jarName}" },
|
||||
TimeLimitSeconds = timeLimitMs / 1000.0,
|
||||
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
|
||||
MemoryLimitKb = kotlinMemoryMb * 1024,
|
||||
StackLimitKb = 256 * 1024,
|
||||
ProcessLimit = 64, // JVM creates multiple threads
|
||||
EnableNetwork = false,
|
||||
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);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Diagnostics;
|
||||
using LiquidCode.Tester.Worker.Services.Isolate;
|
||||
|
||||
namespace LiquidCode.Tester.Worker.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Python program execution service using Isolate sandbox
|
||||
/// </summary>
|
||||
public class PythonExecutionServiceIsolate : IExecutionService
|
||||
{
|
||||
private readonly ILogger<PythonExecutionServiceIsolate> _logger;
|
||||
private readonly IsolateService _isolateService;
|
||||
private readonly IsolateBoxPool _boxPool;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public PythonExecutionServiceIsolate(
|
||||
ILogger<PythonExecutionServiceIsolate> logger,
|
||||
IsolateService isolateService,
|
||||
IsolateBoxPool boxPool,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_isolateService = isolateService;
|
||||
_boxPool = boxPool;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<ExecutionResult> ExecuteAsync(
|
||||
string executablePath,
|
||||
string inputFilePath,
|
||||
int timeLimitMs,
|
||||
int memoryLimitMb)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Executing Python 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 Python script to box
|
||||
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
|
||||
var scriptName = Path.GetFileName(executablePath);
|
||||
var boxScriptPath = Path.Combine(boxDir, scriptName);
|
||||
|
||||
File.Copy(executablePath, boxScriptPath, overwrite: true);
|
||||
|
||||
// Prepare output file in box
|
||||
var outputFilePath = Path.Combine(boxDir, "output.txt");
|
||||
|
||||
// Get Python executable from configuration
|
||||
var pythonExecutable = _configuration["Python:Executable"] ?? "python3";
|
||||
|
||||
// Run in Isolate
|
||||
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
|
||||
{
|
||||
BoxId = boxId,
|
||||
Executable = $"/usr/bin/{pythonExecutable}",
|
||||
Arguments = new[] { $"/box/{scriptName}" },
|
||||
TimeLimitSeconds = timeLimitMs / 1000.0,
|
||||
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
|
||||
MemoryLimitKb = memoryLimitMb * 1024,
|
||||
StackLimitKb = 256 * 1024,
|
||||
ProcessLimit = 1, // Single process for Python
|
||||
EnableNetwork = false,
|
||||
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);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user