docker isolation update

This commit is contained in:
prixod
2025-11-04 20:22:51 +04:00
parent cb346db783
commit 48c2b4dafd
11 changed files with 1215 additions and 13 deletions

View File

@@ -64,12 +64,45 @@ RUN apt-get update && \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Isolate sandbox for secure code execution
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
libcap-dev \
libsystemd-dev \
pkg-config \
&& git clone https://github.com/ioi/isolate.git /tmp/isolate \
&& cd /tmp/isolate \
&& make isolate \
&& make install \
&& rm -rf /tmp/isolate \
&& apt-get remove -y git \
&& apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Create unprivileged user for running the worker service
RUN useradd -m -u 1001 -s /bin/bash workeruser && \
mkdir -p /var/local/lib/isolate && \
chmod 755 /var/local/lib/isolate && \
chown -R workeruser:workeruser /var/local/lib/isolate
# Configure isolate
RUN echo "cg_root = /sys/fs/cgroup" > /usr/local/etc/isolate && \
echo "cg_enable = 1" >> /usr/local/etc/isolate && \
echo "box_root = /var/local/lib/isolate" >> /usr/local/etc/isolate
# Copy published app
COPY --from=publish /app/publish .
# Create temp directory for compilation and testing
RUN mkdir -p /tmp/testing
# Create temp directory for compilation and testing with proper permissions
RUN mkdir -p /tmp/testing && \
chown -R workeruser:workeruser /tmp/testing && \
chown -R workeruser:workeruser /app
ENV ASPNETCORE_URLS=http://+:8080
# Switch to unprivileged user
USER workeruser
ENTRYPOINT ["dotnet", "LiquidCode.Tester.Worker.dll"]

View File

@@ -1,4 +1,5 @@
using LiquidCode.Tester.Worker.Services;
using LiquidCode.Tester.Worker.Services.Isolate;
var builder = WebApplication.CreateBuilder(args);
@@ -9,6 +10,15 @@ builder.Services.AddOpenApi();
// Add HttpClient
builder.Services.AddHttpClient();
// Register Isolate services
builder.Services.AddSingleton<IsolateService>();
builder.Services.AddSingleton(sp =>
{
var logger = sp.GetRequiredService<ILogger<IsolateBoxPool>>();
var maxBoxes = builder.Configuration.GetValue<int>("Isolate:MaxBoxes", 100);
return new IsolateBoxPool(maxBoxes, logger);
});
// Register application services
builder.Services.AddSingleton<PolygonProblemXmlParser>();
builder.Services.AddSingleton<AnswerGenerationService>();
@@ -26,7 +36,10 @@ builder.Services.AddSingleton<PythonCompilationService>();
builder.Services.AddSingleton<ICompilationServiceFactory, CompilationServiceFactory>();
// Register execution services
// Always register both standard and isolate versions
builder.Services.AddSingleton<CppExecutionService>();
builder.Services.AddSingleton<CppExecutionServiceIsolate>();
builder.Services.AddSingleton<JavaExecutionService>();
builder.Services.AddSingleton<KotlinExecutionService>();
builder.Services.AddSingleton<CSharpExecutionService>();

View File

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

View File

@@ -3,23 +3,42 @@ namespace LiquidCode.Tester.Worker.Services;
public class ExecutionServiceFactory : IExecutionServiceFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly IConfiguration _configuration;
private readonly ILogger<ExecutionServiceFactory> _logger;
private readonly bool _useIsolate;
public ExecutionServiceFactory(IServiceProvider serviceProvider, ILogger<ExecutionServiceFactory> logger)
public ExecutionServiceFactory(
IServiceProvider serviceProvider,
IConfiguration configuration,
ILogger<ExecutionServiceFactory> logger)
{
_serviceProvider = serviceProvider;
_configuration = configuration;
_logger = logger;
_useIsolate = configuration.GetValue<bool>("Isolate:Enabled", false);
if (_useIsolate)
{
_logger.LogInformation("Isolate sandbox is ENABLED for code execution");
}
else
{
_logger.LogWarning("Isolate sandbox is DISABLED - using standard execution (NOT SECURE for production!)");
}
}
public IExecutionService GetExecutionService(string language)
{
var normalizedLanguage = language.ToLowerInvariant().Replace(" ", "");
_logger.LogInformation("Getting execution service for language: {Language}", normalizedLanguage);
_logger.LogInformation("Getting execution service for language: {Language} (Isolate: {UseIsolate})",
normalizedLanguage, _useIsolate);
return normalizedLanguage switch
{
"c++" or "cpp" => _serviceProvider.GetRequiredService<CppExecutionService>(),
"c++" or "cpp" => _useIsolate
? _serviceProvider.GetRequiredService<CppExecutionServiceIsolate>()
: _serviceProvider.GetRequiredService<CppExecutionService>(),
"java" => _serviceProvider.GetRequiredService<JavaExecutionService>(),
"kotlin" => _serviceProvider.GetRequiredService<KotlinExecutionService>(),
"c#" or "csharp" => _serviceProvider.GetRequiredService<CSharpExecutionService>(),

View File

@@ -0,0 +1,80 @@
using System.Collections.Concurrent;
namespace LiquidCode.Tester.Worker.Services.Isolate;
/// <summary>
/// Pool manager for Isolate sandbox boxes
/// Manages box IDs for parallel execution
/// </summary>
public class IsolateBoxPool
{
private readonly ConcurrentBag<int> _availableBoxes;
private readonly SemaphoreSlim _semaphore;
private readonly int _maxBoxes;
private readonly ILogger<IsolateBoxPool> _logger;
public IsolateBoxPool(int maxBoxes, ILogger<IsolateBoxPool> logger)
{
if (maxBoxes <= 0)
throw new ArgumentException("Max boxes must be greater than 0", nameof(maxBoxes));
_maxBoxes = maxBoxes;
_logger = logger;
_availableBoxes = new ConcurrentBag<int>();
_semaphore = new SemaphoreSlim(maxBoxes, maxBoxes);
// Initialize pool with box IDs
for (int i = 0; i < maxBoxes; i++)
{
_availableBoxes.Add(i);
}
_logger.LogInformation("Initialized Isolate box pool with {MaxBoxes} boxes", maxBoxes);
}
/// <summary>
/// Acquire a box ID from the pool (async, waits if all boxes are in use)
/// </summary>
public async Task<int> AcquireBoxAsync(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
if (_availableBoxes.TryTake(out var boxId))
{
_logger.LogDebug("Acquired box {BoxId}, remaining: {Remaining}",
boxId, _availableBoxes.Count);
return boxId;
}
// Should not happen due to semaphore, but handle anyway
_semaphore.Release();
throw new InvalidOperationException("Failed to acquire box from pool");
}
/// <summary>
/// Release a box ID back to the pool
/// </summary>
public void ReleaseBox(int boxId)
{
if (boxId < 0 || boxId >= _maxBoxes)
{
_logger.LogWarning("Attempted to release invalid box ID: {BoxId}", boxId);
return;
}
_availableBoxes.Add(boxId);
_semaphore.Release();
_logger.LogDebug("Released box {BoxId}, available: {Available}",
boxId, _availableBoxes.Count);
}
/// <summary>
/// Get current pool statistics
/// </summary>
public (int Total, int Available, int InUse) GetStatistics()
{
var available = _availableBoxes.Count;
return (_maxBoxes, available, _maxBoxes - available);
}
}

View File

@@ -0,0 +1,97 @@
namespace LiquidCode.Tester.Worker.Services.Isolate;
/// <summary>
/// Result of program execution in Isolate sandbox
/// </summary>
public class IsolateExecutionResult
{
/// <summary>
/// Standard output from the program
/// </summary>
public string Output { get; set; } = string.Empty;
/// <summary>
/// Standard error output from the program
/// </summary>
public string ErrorOutput { get; set; } = string.Empty;
/// <summary>
/// CPU time used in seconds
/// </summary>
public double CpuTimeSeconds { get; set; }
/// <summary>
/// Wall (real) time used in seconds
/// </summary>
public double WallTimeSeconds { get; set; }
/// <summary>
/// Memory used in kilobytes (from cgroups)
/// </summary>
public long MemoryUsedKb { get; set; }
/// <summary>
/// Maximum resident set size in kilobytes
/// </summary>
public long MaxRssKb { get; set; }
/// <summary>
/// Exit code of the program
/// </summary>
public int ExitCode { get; set; }
/// <summary>
/// Exit signal if program was killed by signal
/// </summary>
public int? ExitSignal { get; set; }
/// <summary>
/// Status code from isolate (RE/SG/TO/XX/OK)
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// Additional message from isolate
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// Whether the program was killed
/// </summary>
public bool WasKilled { get; set; }
/// <summary>
/// Whether time limit was exceeded
/// </summary>
public bool TimeLimitExceeded { get; set; }
/// <summary>
/// Whether memory limit was exceeded
/// </summary>
public bool MemoryLimitExceeded { get; set; }
/// <summary>
/// Whether program had runtime error
/// </summary>
public bool RuntimeError { get; set; }
/// <summary>
/// Whether process was killed by cgroup OOM killer
/// </summary>
public bool CgroupOomKilled { get; set; }
/// <summary>
/// Number of voluntary context switches
/// </summary>
public int VoluntaryContextSwitches { get; set; }
/// <summary>
/// Number of forced context switches
/// </summary>
public int ForcedContextSwitches { get; set; }
/// <summary>
/// Whether execution was successful (no errors, no limits exceeded)
/// </summary>
public bool Success => ExitCode == 0 && !TimeLimitExceeded && !MemoryLimitExceeded && !RuntimeError;
}

View File

@@ -0,0 +1,103 @@
namespace LiquidCode.Tester.Worker.Services.Isolate;
/// <summary>
/// Options for running a program in Isolate sandbox
/// </summary>
public class IsolateRunOptions
{
/// <summary>
/// Box ID for parallel execution (0-999)
/// </summary>
public int BoxId { get; set; }
/// <summary>
/// Path to executable to run
/// </summary>
public string Executable { get; set; } = string.Empty;
/// <summary>
/// Command-line arguments for the executable
/// </summary>
public string[]? Arguments { get; set; }
/// <summary>
/// CPU time limit in seconds (0 = no limit)
/// </summary>
public double TimeLimitSeconds { get; set; }
/// <summary>
/// Wall time limit in seconds (0 = no limit)
/// </summary>
public double WallTimeLimitSeconds { get; set; }
/// <summary>
/// Memory limit in kilobytes (0 = no limit)
/// </summary>
public long MemoryLimitKb { get; set; }
/// <summary>
/// Stack size limit in kilobytes (0 = default)
/// </summary>
public long StackLimitKb { get; set; }
/// <summary>
/// Maximum number of processes (0 = no limit, 1 = single process)
/// </summary>
public int ProcessLimit { get; set; } = 1;
/// <summary>
/// Enable network access (default: false)
/// </summary>
public bool EnableNetwork { get; set; } = false;
/// <summary>
/// Path to file for stdin redirection
/// </summary>
public string? StdinFile { get; set; }
/// <summary>
/// Path to file for stdout redirection
/// </summary>
public string? StdoutFile { get; set; }
/// <summary>
/// Path to file for stderr redirection
/// </summary>
public string? StderrFile { get; set; }
/// <summary>
/// Working directory inside sandbox
/// </summary>
public string? WorkingDirectory { get; set; }
/// <summary>
/// Directory bindings (host path -> sandbox path)
/// </summary>
public List<DirectoryBinding>? DirectoryBindings { get; set; }
/// <summary>
/// Environment variables to set
/// </summary>
public Dictionary<string, string>? EnvironmentVariables { get; set; }
}
/// <summary>
/// Directory binding for isolate sandbox
/// </summary>
public class DirectoryBinding
{
/// <summary>
/// Path on host system
/// </summary>
public string HostPath { get; set; } = string.Empty;
/// <summary>
/// Path inside sandbox
/// </summary>
public string SandboxPath { get; set; } = string.Empty;
/// <summary>
/// Read-only binding (default: true)
/// </summary>
public bool ReadOnly { get; set; } = true;
}

View File

@@ -0,0 +1,349 @@
using System.Diagnostics;
using System.Text;
namespace LiquidCode.Tester.Worker.Services.Isolate;
/// <summary>
/// Service for running programs in Isolate sandbox
/// </summary>
public class IsolateService
{
private readonly ILogger<IsolateService> _logger;
private readonly string _isolatePath;
public IsolateService(ILogger<IsolateService> logger)
{
_logger = logger;
_isolatePath = FindIsolatePath();
}
/// <summary>
/// Initialize a sandbox box
/// </summary>
public async Task InitBoxAsync(int boxId)
{
_logger.LogDebug("Initializing isolate box {BoxId}", boxId);
var result = await RunIsolateCommandAsync($"--box-id={boxId} --init");
if (result.ExitCode != 0)
{
throw new InvalidOperationException(
$"Failed to initialize isolate box {boxId}: {result.Error}");
}
_logger.LogDebug("Box {BoxId} initialized at {Path}", boxId, result.Output.Trim());
}
/// <summary>
/// Execute program in sandbox
/// </summary>
public async Task<IsolateExecutionResult> RunAsync(IsolateRunOptions options)
{
_logger.LogInformation("Running in isolate box {BoxId}: {Executable}",
options.BoxId, options.Executable);
// Create metadata file path
var metaFile = Path.Combine(Path.GetTempPath(), $"isolate_meta_{options.BoxId}_{Guid.NewGuid()}.txt");
try
{
// Build isolate command
var command = BuildRunCommand(options, metaFile);
_logger.LogDebug("Isolate command: {Command}", command);
// Execute
var cmdResult = await RunIsolateCommandAsync(command);
// Parse metadata
var result = await ParseMetadataAsync(metaFile);
result.Output = cmdResult.Output;
result.ErrorOutput = cmdResult.Error;
_logger.LogInformation("Execution completed: time={Time}s, memory={Memory}KB, exitcode={ExitCode}",
result.CpuTimeSeconds, result.MemoryUsedKb, result.ExitCode);
return result;
}
finally
{
// Cleanup metadata file
try
{
if (File.Exists(metaFile))
{
File.Delete(metaFile);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete metadata file {MetaFile}", metaFile);
}
}
}
/// <summary>
/// Cleanup sandbox box
/// </summary>
public async Task CleanupBoxAsync(int boxId)
{
_logger.LogDebug("Cleaning up isolate box {BoxId}", boxId);
var result = await RunIsolateCommandAsync($"--box-id={boxId} --cleanup");
if (result.ExitCode != 0)
{
_logger.LogWarning("Failed to cleanup isolate box {BoxId}: {Error}", boxId, result.Error);
}
}
/// <summary>
/// Build isolate run command from options
/// </summary>
private string BuildRunCommand(IsolateRunOptions options, string metaFile)
{
var args = new List<string>
{
$"--box-id={options.BoxId}",
$"--meta={metaFile}",
"--cg", // Enable cgroups
"--silent" // Suppress status messages
};
// Time limits
if (options.TimeLimitSeconds > 0)
{
args.Add($"--time={options.TimeLimitSeconds:F3}");
}
if (options.WallTimeLimitSeconds > 0)
{
args.Add($"--wall-time={options.WallTimeLimitSeconds:F3}");
}
// Memory limit
if (options.MemoryLimitKb > 0)
{
args.Add($"--cg-mem={options.MemoryLimitKb}");
}
// Process limit
if (options.ProcessLimit > 0)
{
args.Add($"--processes={options.ProcessLimit}");
}
else
{
args.Add("--processes=1"); // Default: single process
}
// Stack size
if (options.StackLimitKb > 0)
{
args.Add($"--stack={options.StackLimitKb}");
}
// Network isolation (default: disabled)
if (!options.EnableNetwork)
{
// Network is isolated by default in isolate
}
else
{
args.Add("--share-net");
}
// I/O redirection
if (!string.IsNullOrEmpty(options.StdinFile))
{
args.Add($"--stdin={options.StdinFile}");
}
if (!string.IsNullOrEmpty(options.StdoutFile))
{
args.Add($"--stdout={options.StdoutFile}");
}
if (!string.IsNullOrEmpty(options.StderrFile))
{
args.Add($"--stderr={options.StderrFile}");
}
// Working directory
if (!string.IsNullOrEmpty(options.WorkingDirectory))
{
args.Add($"--chdir={options.WorkingDirectory}");
}
// Directory bindings
if (options.DirectoryBindings != null)
{
foreach (var binding in options.DirectoryBindings)
{
var dirSpec = binding.ReadOnly
? $"--dir={binding.HostPath}={binding.SandboxPath}:ro"
: $"--dir={binding.HostPath}={binding.SandboxPath}:rw";
args.Add(dirSpec);
}
}
// Environment variables
if (options.EnvironmentVariables != null)
{
foreach (var env in options.EnvironmentVariables)
{
args.Add($"--env={env.Key}={env.Value}");
}
}
// Run command
args.Add("--run");
args.Add("--");
args.Add(options.Executable);
if (options.Arguments != null)
{
args.AddRange(options.Arguments);
}
return string.Join(" ", args);
}
/// <summary>
/// Parse isolate metadata file
/// </summary>
private async Task<IsolateExecutionResult> ParseMetadataAsync(string metaFile)
{
var result = new IsolateExecutionResult();
if (!File.Exists(metaFile))
{
_logger.LogWarning("Metadata file not found: {MetaFile}", metaFile);
return result;
}
var lines = await File.ReadAllLinesAsync(metaFile);
foreach (var line in lines)
{
var parts = line.Split(':', 2);
if (parts.Length != 2) continue;
var key = parts[0].Trim();
var value = parts[1].Trim();
switch (key)
{
case "time":
if (double.TryParse(value, out var time))
result.CpuTimeSeconds = time;
break;
case "time-wall":
if (double.TryParse(value, out var wallTime))
result.WallTimeSeconds = wallTime;
break;
case "max-rss":
if (long.TryParse(value, out var rss))
result.MaxRssKb = rss;
break;
case "cg-mem":
if (long.TryParse(value, out var cgMem))
result.MemoryUsedKb = cgMem;
break;
case "exitcode":
if (int.TryParse(value, out var exitCode))
result.ExitCode = exitCode;
break;
case "exitsig":
if (int.TryParse(value, out var exitSig))
result.ExitSignal = exitSig;
break;
case "status":
result.Status = value;
result.TimeLimitExceeded = value == "TO";
result.MemoryLimitExceeded = value == "XX" || value == "MLE";
result.RuntimeError = value == "RE" || value == "SG";
break;
case "message":
result.Message = value;
break;
case "killed":
result.WasKilled = value == "1";
break;
case "cg-oom-killed":
result.CgroupOomKilled = value == "1";
result.MemoryLimitExceeded = true;
break;
case "csw-voluntary":
if (int.TryParse(value, out var csvVoluntary))
result.VoluntaryContextSwitches = csvVoluntary;
break;
case "csw-forced":
if (int.TryParse(value, out var csvForced))
result.ForcedContextSwitches = csvForced;
break;
}
}
return result;
}
/// <summary>
/// Execute isolate command
/// </summary>
private async Task<(int ExitCode, string Output, string Error)> RunIsolateCommandAsync(string arguments)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _isolatePath,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return (process.ExitCode, await outputTask, await errorTask);
}
/// <summary>
/// Find isolate binary path
/// </summary>
private string FindIsolatePath()
{
var paths = new[] { "/usr/local/bin/isolate", "/usr/bin/isolate" };
foreach (var path in paths)
{
if (File.Exists(path))
{
_logger.LogInformation("Found isolate at {Path}", path);
return path;
}
}
throw new FileNotFoundException("Isolate binary not found. Make sure isolate is installed.");
}
}

View File

@@ -6,6 +6,10 @@
}
},
"AllowedHosts": "*",
"Isolate": {
"Enabled": true,
"MaxBoxes": 100
},
"Cpp": {
"Compiler": "g++",
"CompilerFlags": "-O2 -std=c++17 -Wall",