update isolate integrity

This commit is contained in:
prixod
2025-11-04 23:16:13 +04:00
parent b61eac05a2
commit 44cb179cf5
9 changed files with 1446 additions and 14 deletions

View File

@@ -23,16 +23,28 @@ builder.Services.AddSingleton(sp =>
builder.Services.AddSingleton<PolygonProblemXmlParser>();
builder.Services.AddSingleton<AnswerGenerationService>();
builder.Services.AddSingleton<CheckerService>();
builder.Services.AddSingleton<CheckerServiceIsolate>();
builder.Services.AddSingleton<IPackageParserService, PackageParserService>();
builder.Services.AddSingleton<IOutputCheckerService, OutputCheckerService>();
builder.Services.AddSingleton<ICallbackService, CallbackService>();
// Register compilation services
// Always register both standard and isolate versions
builder.Services.AddSingleton<CppCompilationService>();
builder.Services.AddSingleton<CppCompilationServiceIsolate>();
builder.Services.AddSingleton<JavaCompilationService>();
builder.Services.AddSingleton<JavaCompilationServiceIsolate>();
builder.Services.AddSingleton<KotlinCompilationService>();
builder.Services.AddSingleton<KotlinCompilationServiceIsolate>();
builder.Services.AddSingleton<CSharpCompilationService>();
builder.Services.AddSingleton<CSharpCompilationServiceIsolate>();
builder.Services.AddSingleton<PythonCompilationService>();
builder.Services.AddSingleton<PythonCompilationServiceIsolate>();
builder.Services.AddSingleton<ICompilationServiceFactory, CompilationServiceFactory>();
// Register execution services

View File

@@ -0,0 +1,218 @@
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// C# compilation service using Isolate sandbox for security
/// </summary>
public class CSharpCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<CSharpCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
public CSharpCompilationServiceIsolate(
ILogger<CSharpCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "Solution.cs");
var executablePath = Path.Combine(workingDirectory, "solution.exe");
_logger.LogInformation("Compiling C# code with Isolate in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, executablePath, version);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate C# compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
}
private async Task<CompilationResult> CompileFileInIsolateAsync(
string sourceFilePath,
string executablePath,
string? version = null)
{
int boxId = -1;
try
{
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for C# compilation", boxId);
await _isolateService.InitBoxAsync(boxId);
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
var exeFileName = "solution.exe";
var boxExePath = Path.Combine(boxDir, exeFileName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
var (compiler, compilerFlags) = ResolveVersion(version);
_logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, compilerFlags);
var arguments = new List<string>();
if (!string.IsNullOrWhiteSpace(compilerFlags))
{
arguments.AddRange(compilerFlags.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
arguments.Add($"/out:/box/{exeFileName}");
arguments.Add($"/box/{sourceFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/bin/{compiler}",
Arguments = arguments.ToArray(),
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10,
EnableNetwork = false,
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 }
}
});
var compilerOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
}
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("C# compilation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
CompilerOutput = compilerOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("C# compilation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
CompilerOutput = compilerOutput
};
}
if (isolateResult.ExitCode == 0 && File.Exists(boxExePath))
{
Directory.CreateDirectory(Path.GetDirectoryName(executablePath)!);
File.Copy(boxExePath, executablePath, overwrite: true);
_logger.LogInformation("C# compilation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
ExecutablePath = executablePath,
CompilerOutput = compilerOutput
};
}
_logger.LogWarning("C# compilation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
CompilerOutput = compilerOutput
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate C# compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
finally
{
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 compilation box {BoxId}", boxId);
}
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released compilation box {BoxId} back to pool", boxId);
}
}
}
private (string compiler, string compilerFlags) ResolveVersion(string? version)
{
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
var compiler = _configuration["CSharp:Compiler"] ?? "csc";
var compilerFlags = _configuration["CSharp:CompilerFlags"] ?? "/optimize+";
return (compiler, compilerFlags);
}
var versionKey = $"CSharp:Versions:{version}";
var versionCompiler = _configuration[$"{versionKey}:Compiler"];
var versionFlags = _configuration[$"{versionKey}:CompilerFlags"];
if (!string.IsNullOrEmpty(versionCompiler))
{
_logger.LogInformation("Using C# version {Version} configuration", version);
return (versionCompiler, versionFlags ?? "/optimize+");
}
_logger.LogWarning("C# version {Version} not found in configuration, using default", version);
var defaultCompiler = _configuration["CSharp:Compiler"] ?? "csc";
var defaultFlags = _configuration["CSharp:CompilerFlags"] ?? "/optimize+";
return (defaultCompiler, defaultFlags);
}
}

View File

@@ -0,0 +1,222 @@
using LiquidCode.Tester.Worker.Services.Isolate;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// Service for running custom checkers in Isolate sandbox
/// </summary>
public class CheckerServiceIsolate
{
private readonly ILogger<CheckerServiceIsolate> _logger;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
private const int CheckerTimeLimitSeconds = 5;
private const int CheckerMemoryLimitMb = 256;
public CheckerServiceIsolate(
ILogger<CheckerServiceIsolate> logger,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_isolateService = isolateService;
_boxPool = boxPool;
}
/// <summary>
/// Check user output using custom checker in Isolate sandbox
/// </summary>
/// <param name="checkerPath">Path to checker executable</param>
/// <param name="inputPath">Path to input file</param>
/// <param name="userOutput">User program output</param>
/// <param name="answerPath">Path to answer file</param>
/// <returns>Checker result</returns>
public async Task<CheckerResult> CheckAsync(
string checkerPath,
string inputPath,
string userOutput,
string answerPath)
{
if (!File.Exists(checkerPath))
{
_logger.LogError("Checker not found: {CheckerPath}", checkerPath);
return new CheckerResult
{
Accepted = false,
ExitCode = -1,
Message = "Checker executable not found"
};
}
int boxId = -1;
try
{
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for checker execution", boxId);
await _isolateService.InitBoxAsync(boxId);
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
// Copy checker executable to box
var checkerName = Path.GetFileName(checkerPath);
var boxCheckerPath = Path.Combine(boxDir, checkerName);
File.Copy(checkerPath, boxCheckerPath, overwrite: true);
// Make checker executable
var chmodProcess = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "chmod",
Arguments = $"+x {boxCheckerPath}",
UseShellExecute = false
});
chmodProcess?.WaitForExit();
// Copy input file
var inputName = "input.txt";
var boxInputPath = Path.Combine(boxDir, inputName);
File.Copy(inputPath, boxInputPath, overwrite: true);
// Save user output to file in box
var outputName = "output.txt";
var boxOutputPath = Path.Combine(boxDir, outputName);
await File.WriteAllTextAsync(boxOutputPath, userOutput);
// Copy answer file
var answerName = "answer.txt";
var boxAnswerPath = Path.Combine(boxDir, answerName);
File.Copy(answerPath, boxAnswerPath, overwrite: true);
// Prepare files for checker stdout/stderr
var stdoutPath = Path.Combine(boxDir, "checker_stdout.txt");
var stderrPath = Path.Combine(boxDir, "checker_stderr.txt");
_logger.LogDebug("Running checker in Isolate: {Checker} {Input} {Output} {Answer}",
checkerName, inputName, outputName, answerName);
// Run checker in Isolate
// Checker arguments: <input> <output> <answer>
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/box/{checkerName}",
Arguments = new[] { $"/box/{inputName}", $"/box/{outputName}", $"/box/{answerName}" },
TimeLimitSeconds = CheckerTimeLimitSeconds,
WallTimeLimitSeconds = CheckerTimeLimitSeconds * 2,
MemoryLimitKb = CheckerMemoryLimitMb * 1024,
StackLimitKb = 64 * 1024,
ProcessLimit = 1, // Single process for checker
EnableNetwork = false,
StdoutFile = stdoutPath,
StderrFile = stderrPath,
WorkingDirectory = "/box"
});
// Read checker output
var stdout = string.Empty;
if (File.Exists(stdoutPath))
{
stdout = await File.ReadAllTextAsync(stdoutPath);
}
var stderr = string.Empty;
if (File.Exists(stderrPath))
{
stderr = await File.ReadAllTextAsync(stderrPath);
}
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Checker timeout in box {BoxId}", boxId);
return new CheckerResult
{
Accepted = false,
ExitCode = -1,
Message = "Checker timeout",
Verdict = CheckerVerdict.CheckerFailed
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Checker memory limit exceeded in box {BoxId}", boxId);
return new CheckerResult
{
Accepted = false,
ExitCode = -1,
Message = "Checker memory limit exceeded",
Verdict = CheckerVerdict.CheckerFailed
};
}
if (isolateResult.RuntimeError)
{
_logger.LogWarning("Checker runtime error in box {BoxId}: {Message}", boxId, isolateResult.Message);
return new CheckerResult
{
Accepted = false,
ExitCode = isolateResult.ExitCode,
Message = $"Checker runtime error: {isolateResult.Message}",
Verdict = CheckerVerdict.CheckerFailed
};
}
var exitCode = isolateResult.ExitCode;
var message = string.IsNullOrWhiteSpace(stderr) ? stdout : stderr;
_logger.LogDebug("Checker exit code: {ExitCode}, message: {Message}", exitCode, message);
return new CheckerResult
{
Accepted = exitCode == 0,
ExitCode = exitCode,
Message = message,
Verdict = GetVerdictFromExitCode(exitCode)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error running checker in Isolate");
return new CheckerResult
{
Accepted = false,
ExitCode = -1,
Message = $"Checker error: {ex.Message}",
Verdict = CheckerVerdict.CheckerFailed
};
}
finally
{
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 checker box {BoxId}", boxId);
}
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released checker box {BoxId} back to pool", boxId);
}
}
}
private CheckerVerdict GetVerdictFromExitCode(int exitCode)
{
return exitCode switch
{
0 => CheckerVerdict.OK,
1 => CheckerVerdict.WrongAnswer,
2 => CheckerVerdict.PresentationError,
3 => CheckerVerdict.CheckerFailed,
7 => CheckerVerdict.PartialScore,
_ => CheckerVerdict.Unknown
};
}
}

View File

@@ -3,27 +3,54 @@ namespace LiquidCode.Tester.Worker.Services;
public class CompilationServiceFactory : ICompilationServiceFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly IConfiguration _configuration;
private readonly ILogger<CompilationServiceFactory> _logger;
private readonly bool _useIsolate;
public CompilationServiceFactory(IServiceProvider serviceProvider, ILogger<CompilationServiceFactory> logger)
public CompilationServiceFactory(
IServiceProvider serviceProvider,
IConfiguration configuration,
ILogger<CompilationServiceFactory> logger)
{
_serviceProvider = serviceProvider;
_configuration = configuration;
_logger = logger;
_useIsolate = configuration.GetValue<bool>("Isolate:Enabled", false);
if (_useIsolate)
{
_logger.LogInformation("Isolate sandbox is ENABLED for compilation");
}
else
{
_logger.LogWarning("Isolate sandbox is DISABLED for compilation - using standard compilation (NOT SECURE for production!)");
}
}
public ICompilationService GetCompilationService(string language)
{
var normalizedLanguage = language.ToLowerInvariant().Replace(" ", "");
_logger.LogInformation("Getting compilation service for language: {Language}", normalizedLanguage);
_logger.LogInformation("Getting compilation service for language: {Language} (Isolate: {UseIsolate})",
normalizedLanguage, _useIsolate);
return normalizedLanguage switch
{
"c++" or "cpp" => _serviceProvider.GetRequiredService<CppCompilationService>(),
"java" => _serviceProvider.GetRequiredService<JavaCompilationService>(),
"kotlin" => _serviceProvider.GetRequiredService<KotlinCompilationService>(),
"c#" or "csharp" => _serviceProvider.GetRequiredService<CSharpCompilationService>(),
"python" => _serviceProvider.GetRequiredService<PythonCompilationService>(),
"c++" or "cpp" => _useIsolate
? _serviceProvider.GetRequiredService<CppCompilationServiceIsolate>()
: _serviceProvider.GetRequiredService<CppCompilationService>(),
"java" => _useIsolate
? _serviceProvider.GetRequiredService<JavaCompilationServiceIsolate>()
: _serviceProvider.GetRequiredService<JavaCompilationService>(),
"kotlin" => _useIsolate
? _serviceProvider.GetRequiredService<KotlinCompilationServiceIsolate>()
: _serviceProvider.GetRequiredService<KotlinCompilationService>(),
"c#" or "csharp" => _useIsolate
? _serviceProvider.GetRequiredService<CSharpCompilationServiceIsolate>()
: _serviceProvider.GetRequiredService<CSharpCompilationService>(),
"python" => _useIsolate
? _serviceProvider.GetRequiredService<PythonCompilationServiceIsolate>()
: _serviceProvider.GetRequiredService<PythonCompilationService>(),
_ => throw new NotSupportedException($"Language '{language}' is not supported")
};
}

View File

@@ -0,0 +1,280 @@
using System.Collections.Generic;
using System.Linq;
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// C++ compilation service using Isolate sandbox for security
/// </summary>
public class CppCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<CppCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
// Compilation limits (more generous than execution limits)
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
public CppCompilationServiceIsolate(
ILogger<CppCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "solution.cpp");
var executablePath = Path.Combine(workingDirectory, "solution");
_logger.LogInformation("Compiling C++ code with Isolate in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, executablePath, version);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
}
private async Task<CompilationResult> CompileFileInIsolateAsync(
string sourceFilePath,
string outputExecutablePath,
string? version = null)
{
int boxId = -1;
try
{
// Acquire box from pool
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for compilation", boxId);
// Initialize box
await _isolateService.InitBoxAsync(boxId);
// Copy source file to box
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
var boxOutputName = "solution";
var boxOutputPath = Path.Combine(boxDir, boxOutputName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
// Resolve compiler and flags
var (compiler, compilerFlags) = ResolveVersion(version);
_logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, string.Join(' ', compilerFlags));
// Build compiler arguments
var arguments = new List<string>(compilerFlags);
arguments.Add($"/box/{sourceFileName}");
arguments.Add("-o");
arguments.Add($"/box/{boxOutputName}");
// Prepare stderr output file for compiler messages
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
// Run compiler in Isolate
// Note: Isolate by default provides access to /usr, /lib, etc. via --share-net=no
// For compilation, we need access to system headers and libraries
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/bin/{compiler}",
Arguments = arguments.ToArray(),
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10, // g++ spawns multiple processes
EnableNetwork = false,
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/include", SandboxPath = "/usr/include", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true }
}
});
// Read compiler output
var compilerOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
}
// Check for time/memory limits during compilation
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Compilation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
CompilerOutput = compilerOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Compilation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
CompilerOutput = compilerOutput
};
}
// Copy compiled executable back if successful
if (isolateResult.ExitCode == 0 && File.Exists(boxOutputPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(outputExecutablePath)!);
File.Copy(boxOutputPath, outputExecutablePath, overwrite: true);
_logger.LogInformation("Compilation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
ExecutablePath = outputExecutablePath,
CompilerOutput = compilerOutput
};
}
// Compilation failed
_logger.LogWarning("Compilation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
CompilerOutput = compilerOutput
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
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 compilation box {BoxId}", boxId);
}
// Release box back to pool
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released compilation box {BoxId} back to pool", boxId);
}
}
}
private (string compiler, List<string> compilerFlags) ResolveVersion(string? version)
{
var defaultCompiler = _configuration["Cpp:Compiler"] ?? "g++";
var defaultFlags = SplitFlags(_configuration["Cpp:CompilerFlags"] ?? "-O2 -std=c++17 -Wall");
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
return (defaultCompiler, defaultFlags);
}
var versionKey = $"Cpp:Versions:{version}";
var versionCompiler = _configuration[$"{versionKey}:Compiler"];
var versionFlagsValue = _configuration[$"{versionKey}:CompilerFlags"];
if (!string.IsNullOrEmpty(versionCompiler) || !string.IsNullOrEmpty(versionFlagsValue))
{
var resolvedFlags = !string.IsNullOrEmpty(versionFlagsValue)
? SplitFlags(versionFlagsValue)
: defaultFlags;
_logger.LogInformation("Using C++ version {Version} configuration", version);
return (versionCompiler ?? defaultCompiler, resolvedFlags);
}
var normalized = NormalizeCppVersion(version);
if (normalized != null)
{
var flagsWithoutStd = defaultFlags
.Where(flag => !flag.StartsWith("-std=", StringComparison.OrdinalIgnoreCase))
.ToList();
flagsWithoutStd.Add($"-std=c++{normalized}");
_logger.LogInformation("Using inferred C++ standard c++{Standard}", normalized);
return (defaultCompiler, flagsWithoutStd);
}
_logger.LogWarning("C++ version {Version} not found in configuration, using default", version);
return (defaultCompiler, defaultFlags);
}
private static List<string> SplitFlags(string flags) =>
flags.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.ToList();
private static string? NormalizeCppVersion(string version)
{
var cleaned = version.Trim().ToLowerInvariant();
cleaned = cleaned.Replace("c++", string.Empty, StringComparison.OrdinalIgnoreCase)
.Replace("gnu++", string.Empty, StringComparison.OrdinalIgnoreCase)
.Trim('+', ' ');
cleaned = cleaned switch
{
"2b" => "23",
"2a" => "20",
"1z" => "17",
"0x" => "11",
_ => cleaned
};
return cleaned switch
{
"26" => "26",
"23" => "23",
"20" => "20",
"17" => "17",
"14" => "14",
"11" => "11",
_ => null
};
}
}

View File

@@ -0,0 +1,219 @@
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// Java compilation service using Isolate sandbox for security
/// </summary>
public class JavaCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<JavaCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
public JavaCompilationServiceIsolate(
ILogger<JavaCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "Solution.java");
var classFilePath = Path.Combine(workingDirectory, "Solution.class");
_logger.LogInformation("Compiling Java code with Isolate in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, classFilePath, workingDirectory, version);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate Java compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
}
private async Task<CompilationResult> CompileFileInIsolateAsync(
string sourceFilePath,
string classFilePath,
string workingDirectory,
string? version = null)
{
int boxId = -1;
try
{
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for Java compilation", boxId);
await _isolateService.InitBoxAsync(boxId);
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
var (compiler, compilerFlags) = ResolveVersion(version);
_logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, compilerFlags);
// Build arguments
var arguments = new List<string>();
if (!string.IsNullOrWhiteSpace(compilerFlags))
{
arguments.AddRange(compilerFlags.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
arguments.Add($"/box/{sourceFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/bin/{compiler}",
Arguments = arguments.ToArray(),
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10, // javac may spawn multiple processes
EnableNetwork = false,
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 }
}
});
var compilerOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
}
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Java compilation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
CompilerOutput = compilerOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Java compilation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
CompilerOutput = compilerOutput
};
}
// Copy .class file back
var boxClassPath = Path.Combine(boxDir, "Solution.class");
if (isolateResult.ExitCode == 0 && File.Exists(boxClassPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(classFilePath)!);
File.Copy(boxClassPath, classFilePath, overwrite: true);
_logger.LogInformation("Java compilation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
ExecutablePath = classFilePath,
CompilerOutput = compilerOutput
};
}
_logger.LogWarning("Java compilation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
CompilerOutput = compilerOutput
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate Java compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
finally
{
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 compilation box {BoxId}", boxId);
}
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released compilation box {BoxId} back to pool", boxId);
}
}
}
private (string compiler, string compilerFlags) ResolveVersion(string? version)
{
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
var compiler = _configuration["Java:Compiler"] ?? "javac";
var compilerFlags = _configuration["Java:CompilerFlags"] ?? "";
return (compiler, compilerFlags);
}
var versionKey = $"Java:Versions:{version}";
var versionCompiler = _configuration[$"{versionKey}:Compiler"];
var versionFlags = _configuration[$"{versionKey}:CompilerFlags"];
if (!string.IsNullOrEmpty(versionCompiler))
{
_logger.LogInformation("Using Java version {Version} configuration", version);
return (versionCompiler, versionFlags ?? "");
}
_logger.LogWarning("Java version {Version} not found in configuration, using default", version);
var defaultCompiler = _configuration["Java:Compiler"] ?? "javac";
var defaultFlags = _configuration["Java:CompilerFlags"] ?? "";
return (defaultCompiler, defaultFlags);
}
}

View File

@@ -0,0 +1,221 @@
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// Kotlin compilation service using Isolate sandbox for security
/// </summary>
public class KotlinCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<KotlinCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
public KotlinCompilationServiceIsolate(
ILogger<KotlinCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "Solution.kt");
var jarFilePath = Path.Combine(workingDirectory, "solution.jar");
_logger.LogInformation("Compiling Kotlin code with Isolate in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, jarFilePath, version);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate Kotlin compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
}
private async Task<CompilationResult> CompileFileInIsolateAsync(
string sourceFilePath,
string jarFilePath,
string? version = null)
{
int boxId = -1;
try
{
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for Kotlin compilation", boxId);
await _isolateService.InitBoxAsync(boxId);
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
var jarFileName = "solution.jar";
var boxJarPath = Path.Combine(boxDir, jarFileName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
var (compiler, compilerFlags) = ResolveVersion(version);
_logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, compilerFlags);
var arguments = new List<string>();
if (!string.IsNullOrWhiteSpace(compilerFlags))
{
arguments.AddRange(compilerFlags.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
arguments.Add($"/box/{sourceFileName}");
arguments.Add("-include-runtime");
arguments.Add("-d");
arguments.Add($"/box/{jarFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/local/bin/{compiler}",
Arguments = arguments.ToArray(),
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10,
EnableNetwork = false,
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 = "/opt/kotlinc", SandboxPath = "/opt/kotlinc", ReadOnly = true }
}
});
var compilerOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
}
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Kotlin compilation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
CompilerOutput = compilerOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Kotlin compilation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
CompilerOutput = compilerOutput
};
}
if (isolateResult.ExitCode == 0 && File.Exists(boxJarPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(jarFilePath)!);
File.Copy(boxJarPath, jarFilePath, overwrite: true);
_logger.LogInformation("Kotlin compilation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
ExecutablePath = jarFilePath,
CompilerOutput = compilerOutput
};
}
_logger.LogWarning("Kotlin compilation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
CompilerOutput = compilerOutput
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate Kotlin compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
finally
{
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 compilation box {BoxId}", boxId);
}
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released compilation box {BoxId} back to pool", boxId);
}
}
}
private (string compiler, string compilerFlags) ResolveVersion(string? version)
{
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
var compiler = _configuration["Kotlin:Compiler"] ?? "kotlinc";
var compilerFlags = _configuration["Kotlin:CompilerFlags"] ?? "";
return (compiler, compilerFlags);
}
var versionKey = $"Kotlin:Versions:{version}";
var versionCompiler = _configuration[$"{versionKey}:Compiler"];
var versionFlags = _configuration[$"{versionKey}:CompilerFlags"];
if (!string.IsNullOrEmpty(versionCompiler))
{
_logger.LogInformation("Using Kotlin version {Version} configuration", version);
return (versionCompiler, versionFlags ?? "");
}
_logger.LogWarning("Kotlin version {Version} not found in configuration, using default", version);
var defaultCompiler = _configuration["Kotlin:Compiler"] ?? "kotlinc";
var defaultFlags = _configuration["Kotlin:CompilerFlags"] ?? "";
return (defaultCompiler, defaultFlags);
}
}

View File

@@ -4,11 +4,24 @@ public class OutputCheckerService : IOutputCheckerService
{
private readonly ILogger<OutputCheckerService> _logger;
private readonly CheckerService _checkerService;
private readonly CheckerServiceIsolate _checkerServiceIsolate;
private readonly bool _useIsolate;
public OutputCheckerService(ILogger<OutputCheckerService> logger, CheckerService checkerService)
public OutputCheckerService(
ILogger<OutputCheckerService> logger,
CheckerService checkerService,
CheckerServiceIsolate checkerServiceIsolate,
IConfiguration configuration)
{
_logger = logger;
_checkerService = checkerService;
_checkerServiceIsolate = checkerServiceIsolate;
_useIsolate = configuration.GetValue<bool>("Isolate:Enabled", false);
if (_useIsolate)
{
_logger.LogInformation("Using Isolate sandbox for checker execution");
}
}
public async Task<bool> CheckOutputAsync(string actualOutput, string expectedOutputPath)
@@ -69,13 +82,12 @@ public class OutputCheckerService : IOutputCheckerService
// If custom checker is available, use it
if (!string.IsNullOrEmpty(checkerPath) && File.Exists(checkerPath))
{
_logger.LogDebug("Using custom checker: {CheckerPath}", checkerPath);
_logger.LogDebug("Using custom checker: {CheckerPath} (Isolate: {UseIsolate})",
checkerPath, _useIsolate);
var checkerResult = await _checkerService.CheckAsync(
checkerPath,
inputFilePath,
actualOutput,
expectedOutputPath);
var checkerResult = _useIsolate
? await _checkerServiceIsolate.CheckAsync(checkerPath, inputFilePath, actualOutput, expectedOutputPath)
: await _checkerService.CheckAsync(checkerPath, inputFilePath, actualOutput, expectedOutputPath);
if (!checkerResult.Accepted)
{

View File

@@ -0,0 +1,221 @@
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// Python compilation service using Isolate sandbox for security
/// Python doesn't require compilation, but performs syntax validation in sandbox
/// </summary>
public class PythonCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<PythonCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
private const int ValidationTimeLimitSeconds = 10;
private const int ValidationMemoryLimitMb = 256;
public PythonCompilationServiceIsolate(
ILogger<PythonCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "solution.py");
_logger.LogInformation("Preparing Python code with Isolate validation in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
// Optionally validate syntax in Isolate sandbox
var validateSyntax = _configuration.GetValue<bool>("Python:ValidateSyntax", true);
if (validateSyntax)
{
var validationResult = await ValidateSyntaxInIsolateAsync(sourceFilePath, version);
if (!validationResult.Success)
{
return validationResult;
}
}
var executable = ResolveVersion(version);
_logger.LogInformation("Python code prepared and validated successfully");
return new CompilationResult
{
Success = true,
ExecutablePath = sourceFilePath,
CompilerOutput = $"Python code validated successfully (using {executable})"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error preparing Python code");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Error preparing code: {ex.Message}"
};
}
}
private async Task<CompilationResult> ValidateSyntaxInIsolateAsync(
string sourceFilePath,
string? version = null)
{
int boxId = -1;
try
{
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for Python syntax validation", boxId);
await _isolateService.InitBoxAsync(boxId);
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
var executable = ResolveVersion(version);
_logger.LogDebug("Using Python executable: {Executable}", executable);
var stderrFilePath = Path.Combine(boxDir, "validation_stderr.txt");
// Validate syntax using: python3 -m py_compile solution.py
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/bin/{executable}",
Arguments = new[] { "-m", "py_compile", $"/box/{sourceFileName}" },
TimeLimitSeconds = ValidationTimeLimitSeconds,
WallTimeLimitSeconds = ValidationTimeLimitSeconds * 2,
MemoryLimitKb = ValidationMemoryLimitMb * 1024,
StackLimitKb = 64 * 1024,
ProcessLimit = 1,
EnableNetwork = false,
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 }
}
});
var validationOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
validationOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
validationOutput = $"{validationOutput}\n{isolateResult.ErrorOutput}".Trim();
}
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Python validation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Validation timeout: exceeded {ValidationTimeLimitSeconds}s limit",
CompilerOutput = validationOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Python validation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Validation memory limit exceeded: {ValidationMemoryLimitMb}MB",
CompilerOutput = validationOutput
};
}
if (isolateResult.ExitCode != 0)
{
_logger.LogWarning("Python syntax validation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = "Python syntax error",
CompilerOutput = validationOutput
};
}
_logger.LogDebug("Python syntax validation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
CompilerOutput = "Syntax validation passed"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Python syntax validation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Validation error: {ex.Message}"
};
}
finally
{
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 validation box {BoxId}", boxId);
}
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released validation box {BoxId} back to pool", boxId);
}
}
}
private string ResolveVersion(string? version)
{
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
var executable = _configuration["Python:Executable"] ?? "python3";
return executable;
}
var versionKey = $"Python:Versions:{version}";
var versionExecutable = _configuration[$"{versionKey}:Executable"];
if (!string.IsNullOrEmpty(versionExecutable))
{
_logger.LogInformation("Using Python version {Version} configuration", version);
return versionExecutable;
}
_logger.LogWarning("Python version {Version} not found in configuration, using default", version);
var defaultExecutable = _configuration["Python:Executable"] ?? "python3";
return defaultExecutable;
}
}