diff --git a/src/LiquidCode.Tester.Worker/Program.cs b/src/LiquidCode.Tester.Worker/Program.cs index 2274ee3..1a65b7d 100644 --- a/src/LiquidCode.Tester.Worker/Program.cs +++ b/src/LiquidCode.Tester.Worker/Program.cs @@ -41,9 +41,17 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Register testing service diff --git a/src/LiquidCode.Tester.Worker/Services/CSharpExecutionServiceIsolate.cs b/src/LiquidCode.Tester.Worker/Services/CSharpExecutionServiceIsolate.cs new file mode 100644 index 0000000..709d7be --- /dev/null +++ b/src/LiquidCode.Tester.Worker/Services/CSharpExecutionServiceIsolate.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using LiquidCode.Tester.Worker.Services.Isolate; + +namespace LiquidCode.Tester.Worker.Services; + +/// +/// C# program execution service using Isolate sandbox +/// +public class CSharpExecutionServiceIsolate : IExecutionService +{ + private readonly ILogger _logger; + private readonly IsolateService _isolateService; + private readonly IsolateBoxPool _boxPool; + + public CSharpExecutionServiceIsolate( + ILogger logger, + IsolateService isolateService, + IsolateBoxPool boxPool) + { + _logger = logger; + _isolateService = isolateService; + _boxPool = boxPool; + } + + public async Task 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; + } +} diff --git a/src/LiquidCode.Tester.Worker/Services/ExecutionServiceFactory.cs b/src/LiquidCode.Tester.Worker/Services/ExecutionServiceFactory.cs index 0cac692..68a639c 100644 --- a/src/LiquidCode.Tester.Worker/Services/ExecutionServiceFactory.cs +++ b/src/LiquidCode.Tester.Worker/Services/ExecutionServiceFactory.cs @@ -39,10 +39,18 @@ public class ExecutionServiceFactory : IExecutionServiceFactory "c++" or "cpp" => _useIsolate ? _serviceProvider.GetRequiredService() : _serviceProvider.GetRequiredService(), - "java" => _serviceProvider.GetRequiredService(), - "kotlin" => _serviceProvider.GetRequiredService(), - "c#" or "csharp" => _serviceProvider.GetRequiredService(), - "python" => _serviceProvider.GetRequiredService(), + "java" => _useIsolate + ? _serviceProvider.GetRequiredService() + : _serviceProvider.GetRequiredService(), + "kotlin" => _useIsolate + ? _serviceProvider.GetRequiredService() + : _serviceProvider.GetRequiredService(), + "c#" or "csharp" => _useIsolate + ? _serviceProvider.GetRequiredService() + : _serviceProvider.GetRequiredService(), + "python" => _useIsolate + ? _serviceProvider.GetRequiredService() + : _serviceProvider.GetRequiredService(), _ => throw new NotSupportedException($"Language '{language}' is not supported") }; } diff --git a/src/LiquidCode.Tester.Worker/Services/JavaExecutionServiceIsolate.cs b/src/LiquidCode.Tester.Worker/Services/JavaExecutionServiceIsolate.cs new file mode 100644 index 0000000..27fc0b9 --- /dev/null +++ b/src/LiquidCode.Tester.Worker/Services/JavaExecutionServiceIsolate.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using LiquidCode.Tester.Worker.Services.Isolate; + +namespace LiquidCode.Tester.Worker.Services; + +/// +/// Java program execution service using Isolate sandbox +/// +public class JavaExecutionServiceIsolate : IExecutionService +{ + private readonly ILogger _logger; + private readonly IsolateService _isolateService; + private readonly IsolateBoxPool _boxPool; + + public JavaExecutionServiceIsolate( + ILogger logger, + IsolateService isolateService, + IsolateBoxPool boxPool) + { + _logger = logger; + _isolateService = isolateService; + _boxPool = boxPool; + } + + public async Task 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; + } +} diff --git a/src/LiquidCode.Tester.Worker/Services/KotlinExecutionServiceIsolate.cs b/src/LiquidCode.Tester.Worker/Services/KotlinExecutionServiceIsolate.cs new file mode 100644 index 0000000..a95c686 --- /dev/null +++ b/src/LiquidCode.Tester.Worker/Services/KotlinExecutionServiceIsolate.cs @@ -0,0 +1,169 @@ +using System.Diagnostics; +using LiquidCode.Tester.Worker.Services.Isolate; + +namespace LiquidCode.Tester.Worker.Services; + +/// +/// Kotlin program execution service using Isolate sandbox +/// +public class KotlinExecutionServiceIsolate : IExecutionService +{ + private readonly ILogger _logger; + private readonly IsolateService _isolateService; + private readonly IsolateBoxPool _boxPool; + + public KotlinExecutionServiceIsolate( + ILogger logger, + IsolateService isolateService, + IsolateBoxPool boxPool) + { + _logger = logger; + _isolateService = isolateService; + _boxPool = boxPool; + } + + public async Task 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; + } +} diff --git a/src/LiquidCode.Tester.Worker/Services/PythonExecutionServiceIsolate.cs b/src/LiquidCode.Tester.Worker/Services/PythonExecutionServiceIsolate.cs new file mode 100644 index 0000000..78f3a01 --- /dev/null +++ b/src/LiquidCode.Tester.Worker/Services/PythonExecutionServiceIsolate.cs @@ -0,0 +1,173 @@ +using System.Diagnostics; +using LiquidCode.Tester.Worker.Services.Isolate; + +namespace LiquidCode.Tester.Worker.Services; + +/// +/// Python program execution service using Isolate sandbox +/// +public class PythonExecutionServiceIsolate : IExecutionService +{ + private readonly ILogger _logger; + private readonly IsolateService _isolateService; + private readonly IsolateBoxPool _boxPool; + private readonly IConfiguration _configuration; + + public PythonExecutionServiceIsolate( + ILogger logger, + IsolateService isolateService, + IsolateBoxPool boxPool, + IConfiguration configuration) + { + _logger = logger; + _isolateService = isolateService; + _boxPool = boxPool; + _configuration = configuration; + } + + public async Task 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; + } +}