17 Commits

Author SHA1 Message Date
prixod
f009c95645 fix memory limiting java & kotlin
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 43s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m13s
2025-12-01 22:52:18 +04:00
prixod
7a1f22eb8e fix kotlin
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 47s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m4s
2025-12-01 22:47:44 +04:00
prixod
f5cb2c8e2e fix java 2025-12-01 22:30:08 +04:00
prixod
5ed5925a38 fix local testing & isolate & C# processing
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 48s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 57s
2025-12-01 02:26:57 +04:00
prixod
bd2ed7716c fix privileges 2025-12-01 02:26:17 +04:00
a6c56ecb22 Merge remote-tracking branch 'origin/master' into roman
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 55s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 4m47s
2025-11-30 23:31:35 +03:00
bc9c162de5 Adds callback failure logging
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 46s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m2s
Adds logging for non-success status codes returned by the callback URL.

This improves debugging by providing visibility into callback failures, including the status code and a truncated response body.
2025-11-06 23:02:08 +03:00
f4d855c958 Попытка добавить ld
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 47s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m2s
2025-11-06 12:21:27 +03:00
27581c4385 /usr/bin
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 53s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m3s
2025-11-05 22:47:00 +03:00
0b29ce168e Ещё штуки
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 51s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m4s
2025-11-05 22:39:32 +03:00
7f8eb875f9 мпаиуцагцкмпцкмпгшк
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 52s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m5s
2025-11-05 22:32:22 +03:00
6ed26ae29b Ещё фиксы от ИИ
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 57s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m17s
2025-11-05 22:15:50 +03:00
a8c0ec9ed3 Enables cgroup support for isolate
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 56s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m7s
Configures isolate to use cgroups for improved resource management.

Adds the `--cg` flag to the isolate init and cleanup commands,
leveraging cgroups for better resource isolation during code execution testing.

Makes isolate configuration file explicit.
2025-11-05 22:01:57 +03:00
24943a7c86 fix ro for isolate
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 55s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 1m7s
2025-11-05 21:47:49 +03:00
619b93b042 Дроп конфига
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 33s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 57s
2025-11-05 21:41:38 +03:00
ca4b6925ac правильное название ветки и образов
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 54s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 4m49s
2025-11-05 21:24:04 +03:00
6a23cc4c72 фикс конфига isolate 2025-11-05 21:23:31 +03:00
16 changed files with 6711 additions and 79 deletions

View File

@@ -2,7 +2,7 @@ name: Build and Push Docker Images
on:
push:
branches: [ master ]
branches: [ roman ]
env:
REGISTRY: git.nullptr.top
@@ -17,10 +17,10 @@ jobs:
include:
- service: gateway
dockerfile: src/LiquidCode.Tester.Gateway/Dockerfile
image: git.nullptr.top/liquidcode/liquidcode-tester-gateway
image: git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman
- service: worker
dockerfile: src/LiquidCode.Tester.Worker/Dockerfile
image: git.nullptr.top/liquidcode/liquidcode-tester-worker
image: git.nullptr.top/liquidcode/liquidcode-tester-worker-roman
steps:
- name: Checkout repository
uses: actions/checkout@v4

View File

@@ -26,6 +26,7 @@
worker:
image: liquidcode-tester-worker:latest
privileged: true
container_name: liquidcode-tester-worker
build:
context: .
@@ -36,16 +37,9 @@
- ASPNETCORE_ENVIRONMENT=Development
networks:
- liquidcode-network
# Security hardening for Worker
security_opt:
- no-new-privileges:true
- apparmor=docker-default
cap_drop:
- ALL
cap_add:
- SYS_ADMIN # Required for Isolate namespaces
- SETUID # Required for Isolate to change user context
- SETGID # Required for Isolate to change group context
# Mount cgroup for Isolate sandbox
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
# Temporary filesystem for compilation and testing
tmpfs:
- /tmp:exec,size=4G

View File

@@ -35,7 +35,7 @@ public class WorkerClientService : IWorkerClientService
form.Add(new StringContent(submit.MissionId.ToString()), "MissionId");
form.Add(new StringContent(submit.Language), "Language");
form.Add(new StringContent(submit.LanguageVersion), "LanguageVersion");
form.Add(new StringContent(submit.SourceCode), "SourceCode");
form.Add(new StringContent(submit.SourceCode, System.Text.Encoding.UTF8, "text/plain"), "SourceCode");
form.Add(new StringContent(submit.CallbackUrl), "CallbackUrl");
// Add package file

View File

@@ -82,27 +82,23 @@ RUN apt-get update && \
&& 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
RUN mkdir -p /var/local/lib/isolate && \
chmod 755 /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
# Configure isolate directories and control-group root
RUN printf "box_root = /var/local/lib/isolate\nlock_root = /run/isolate/locks\ncg_root = /sys/fs/cgroup\nfirst_uid = 60000\nfirst_gid = 60000\nnum_boxes = 1000\n" > /usr/local/etc/isolate.conf && \
ln -sf /usr/local/etc/isolate.conf /usr/local/etc/isolate && \
mkdir -p /run/isolate/locks
# Copy published app
COPY --from=publish /app/publish .
# 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
RUN mkdir -p /tmp/testing
ENV ASPNETCORE_URLS=http://+:8080
# Switch to unprivileged user
USER workeruser
#USER workeruser
ENTRYPOINT ["dotnet", "LiquidCode.Tester.Worker.dll"]

View File

@@ -103,7 +103,10 @@ public class CSharpCompilationServiceIsolate : ICompilationService
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 = "/lib", SandboxPath = "/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/share", SandboxPath = "/usr/share", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc/mono", SandboxPath = "/etc/mono", ReadOnly = true }
}
});

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using LiquidCode.Tester.Worker.Services.Isolate;
using System.Collections.Generic;
namespace LiquidCode.Tester.Worker.Services;
@@ -61,23 +62,41 @@ public class CSharpExecutionServiceIsolate : IExecutionService
});
chmodProcess?.WaitForExit();
// Prepare output file in box
// Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
var stderrFilePath = Path.Combine(boxDir, "stderr.txt");
string? sandboxInputPath = null;
// Run in Isolate
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Run in Isolate using mono runtime
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/box/{executableName}",
Executable = "/usr/bin/mono",
Arguments = new[] { $"/box/{executableName}" },
TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = memoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 1, // Single process for C#
ProcessLimit = 64, // Mono may create multiple threads/processes
EnableNetwork = false,
StdinFile = inputFilePath,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
WorkingDirectory = "/box"
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/share", SandboxPath = "/usr/share", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc/mono", SandboxPath = "/etc/mono", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true }
}
});
stopwatch.Stop();
@@ -88,10 +107,19 @@ public class CSharpExecutionServiceIsolate : IExecutionService
result.Output = await File.ReadAllTextAsync(outputFilePath);
}
// Read stderr
var stderrContent = string.Empty;
if (File.Exists(stderrFilePath))
{
stderrContent = await File.ReadAllTextAsync(stderrFilePath);
}
// Map Isolate result to ExecutionResult
result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000);
result.MemoryUsedMb = isolateResult.MemoryUsedKb / 1024;
result.ErrorOutput = isolateResult.ErrorOutput;
result.ErrorOutput = string.IsNullOrEmpty(stderrContent)
? isolateResult.ErrorOutput
: $"{stderrContent}\n{isolateResult.ErrorOutput}".Trim();
result.ExitCode = isolateResult.ExitCode;
if (isolateResult.TimeLimitExceeded)

View File

@@ -33,6 +33,14 @@ public class CallbackService : ICallbackService
var content = new StringContent(json, Encoding.UTF8, "application/json");
var httpResponse = await httpClient.PostAsync(callbackUrl, content);
if (!httpResponse.IsSuccessStatusCode)
{
var responseBody = await httpResponse.Content.ReadAsStringAsync();
_logger.LogWarning("Callback returned non-success status {StatusCode} with body: {Body}",
(int)httpResponse.StatusCode, Truncate(responseBody, 2048));
}
httpResponse.EnsureSuccessStatusCode();
_logger.LogInformation("Status update sent successfully");
@@ -44,6 +52,16 @@ public class CallbackService : ICallbackService
}
}
private static string Truncate(string value, int maxLength)
{
if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
{
return value;
}
return value.Substring(0, maxLength) + "…";
}
private bool IsLogCallback(string callbackUrl)
{
if (string.IsNullOrWhiteSpace(callbackUrl))

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
/// <summary>
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
@@ -85,21 +88,60 @@ public class CppCompilationServiceIsolate : ICompilationService
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
// Copy common headers from the source directory (e.g., testlib.h)
var sourceDirectory = Path.GetDirectoryName(sourceFilePath);
if (!string.IsNullOrEmpty(sourceDirectory) && Directory.Exists(sourceDirectory))
{
foreach (var header in Directory.EnumerateFiles(sourceDirectory))
{
if (string.Equals(header, sourceFilePath, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var extension = Path.GetExtension(header);
if (extension is ".h" or ".hpp" or ".hh" or ".hxx" or ".h++" or ".inl" or ".tcc" )
{
var destination = Path.Combine(boxDir, Path.GetFileName(header));
File.Copy(header, destination, 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);
var includeCounter = 0;
// Add include directories
if (includeDirectories != null)
{
foreach (var includeDir in includeDirectories.Where(d => !string.IsNullOrWhiteSpace(d)))
{
var resolvedIncludeDir = includeDir;
if (!Path.IsPathRooted(resolvedIncludeDir))
{
var baseDir = sourceDirectory ?? Directory.GetCurrentDirectory();
resolvedIncludeDir = Path.GetFullPath(Path.Combine(baseDir, includeDir));
}
if (Directory.Exists(resolvedIncludeDir))
{
includeCounter++;
var targetIncludeDir = Path.Combine(boxDir, $"include_{includeCounter}");
CopyDirectory(resolvedIncludeDir, targetIncludeDir);
arguments.Add($"-I/box/include_{includeCounter}");
}
else
{
arguments.Add($"-I{includeDir}");
}
}
}
// Add additional flags
if (additionalFlags != null)
@@ -118,8 +160,25 @@ public class CppCompilationServiceIsolate : ICompilationService
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
// Bind the system toolchain directories read-only so the linker and headers remain reachable
var directoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/include", SandboxPath = "/usr/include", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true }
};
if (Directory.Exists("/bin"))
{
directoryBindings.Add(new DirectoryBinding { HostPath = "/bin", SandboxPath = "/bin", ReadOnly = true });
}
if (Directory.Exists("/usr/local/bin"))
{
directoryBindings.Add(new DirectoryBinding { HostPath = "/usr/local/bin", SandboxPath = "/usr/local/bin", ReadOnly = true });
}
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
@@ -133,11 +192,10 @@ public class CppCompilationServiceIsolate : ICompilationService
EnableNetwork = false,
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
DirectoryBindings = directoryBindings,
EnvironmentVariables = new Dictionary<string, string>
{
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 }
["PATH"] = GetSandboxPath()
}
});
@@ -231,6 +289,55 @@ public class CppCompilationServiceIsolate : ICompilationService
}
}
private static void CopyDirectory(string sourceDirectory, string destinationDirectory)
{
var sourceRoot = Path.GetFullPath(sourceDirectory);
var destinationRoot = Path.GetFullPath(destinationDirectory);
Directory.CreateDirectory(destinationRoot);
foreach (var directory in Directory.EnumerateDirectories(sourceRoot, "*", SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(sourceRoot, directory);
var targetDir = Path.Combine(destinationRoot, relativePath);
Directory.CreateDirectory(targetDir);
}
foreach (var file in Directory.EnumerateFiles(sourceRoot, "*", SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(sourceRoot, file);
var targetFile = Path.Combine(destinationRoot, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)!);
File.Copy(file, targetFile, overwrite: true);
}
}
private static string GetSandboxPath()
{
var defaultPaths = new[] { "/usr/local/bin", "/usr/bin", "/bin" };
var hostPath = Environment.GetEnvironmentVariable("PATH");
if (string.IsNullOrWhiteSpace(hostPath))
{
return string.Join(':', defaultPaths);
}
var segments = hostPath
.Split(':', StringSplitOptions.RemoveEmptyEntries)
.Where(path => path.StartsWith("/usr", StringComparison.Ordinal) || path.StartsWith("/bin", StringComparison.Ordinal))
.ToList();
foreach (var defaultPath in defaultPaths)
{
if (!segments.Contains(defaultPath))
{
segments.Add(defaultPath);
}
}
return segments.Count == 0 ? string.Join(':', defaultPaths) : string.Join(':', segments);
}
private (string compiler, List<string> compilerFlags) ResolveVersion(string? version)
{
var defaultCompiler = _configuration["Cpp:Compiler"] ?? "g++";

View File

@@ -61,8 +61,15 @@ public class CppExecutionServiceIsolate : IExecutionService
});
chmodProcess?.WaitForExit();
// Prepare output file in box
// Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Run in Isolate
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
@@ -75,7 +82,7 @@ public class CppExecutionServiceIsolate : IExecutionService
StackLimitKb = 256 * 1024, // 256 MB stack
ProcessLimit = 1, // Single process only
EnableNetwork = false, // No network access
StdinFile = inputFilePath,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
WorkingDirectory = "/box"
});

View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Text;
@@ -24,7 +25,7 @@ public class IsolateService
{
_logger.LogDebug("Initializing isolate box {BoxId}", boxId);
var result = await RunIsolateCommandAsync($"--box-id={boxId} --init");
var result = await RunIsolateCommandAsync($"--box-id={boxId} --cg --init");
if (result.ExitCode != 0)
{
@@ -90,7 +91,7 @@ public class IsolateService
{
_logger.LogDebug("Cleaning up isolate box {BoxId}", boxId);
var result = await RunIsolateCommandAsync($"--box-id={boxId} --cleanup");
var result = await RunIsolateCommandAsync($"--box-id={boxId} --cg --cleanup");
if (result.ExitCode != 0)
{
@@ -157,17 +158,17 @@ public class IsolateService
// I/O redirection
if (!string.IsNullOrEmpty(options.StdinFile))
{
args.Add($"--stdin={options.StdinFile}");
args.Add($"--stdin={MapSandboxPath(options.StdinFile, options.BoxId)}");
}
if (!string.IsNullOrEmpty(options.StdoutFile))
{
args.Add($"--stdout={options.StdoutFile}");
args.Add($"--stdout={MapSandboxPath(options.StdoutFile, options.BoxId)}");
}
if (!string.IsNullOrEmpty(options.StderrFile))
{
args.Add($"--stderr={options.StderrFile}");
args.Add($"--stderr={MapSandboxPath(options.StderrFile, options.BoxId)}");
}
// Working directory
@@ -182,7 +183,7 @@ public class IsolateService
foreach (var binding in options.DirectoryBindings)
{
var dirSpec = binding.ReadOnly
? $"--dir={binding.HostPath}={binding.SandboxPath}:ro"
? $"--dir={binding.HostPath}={binding.SandboxPath}"
: $"--dir={binding.HostPath}={binding.SandboxPath}:rw";
args.Add(dirSpec);
}
@@ -210,6 +211,25 @@ public class IsolateService
return string.Join(" ", args);
}
private static string MapSandboxPath(string path, int boxId)
{
if (string.IsNullOrEmpty(path))
{
return path;
}
var normalizedPath = Path.GetFullPath(path);
var boxRoot = Path.GetFullPath($"/var/local/lib/isolate/{boxId}/box");
if (normalizedPath.StartsWith(boxRoot, StringComparison.Ordinal))
{
var relative = normalizedPath.Substring(boxRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return relative.Length == 0 ? "/box" : $"/box/{relative.Replace(Path.DirectorySeparatorChar, '/')}";
}
return normalizedPath;
}
/// <summary>
/// Parse isolate metadata file
/// </summary>
@@ -267,9 +287,6 @@ public class IsolateService
case "status":
result.Status = value;
result.TimeLimitExceeded = value == "TO";
result.MemoryLimitExceeded = value == "XX" || value == "MLE";
result.RuntimeError = value == "RE" || value == "SG";
break;
case "message":
@@ -282,7 +299,10 @@ public class IsolateService
case "cg-oom-killed":
result.CgroupOomKilled = value == "1";
if (result.CgroupOomKilled)
{
result.MemoryLimitExceeded = true;
}
break;
case "csw-voluntary":
@@ -297,6 +317,29 @@ public class IsolateService
}
}
// Derive status-related flags after parsing all metadata
switch (result.Status)
{
case "TO":
result.TimeLimitExceeded = true;
break;
case "RE":
case "SG":
result.RuntimeError = true;
break;
case "XX":
// Internal error reported by isolate
result.RuntimeError = true;
break;
}
if (!result.MemoryLimitExceeded &&
!string.IsNullOrEmpty(result.Message) &&
result.Message.Contains("memory limit", StringComparison.OrdinalIgnoreCase))
{
result.MemoryLimitExceeded = true;
}
return result;
}

View File

@@ -14,7 +14,7 @@ public class JavaCompilationServiceIsolate : ICompilationService
private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
private const int CompilationMemoryLimitMb = 1024; // JVM needs more memory
public JavaCompilationServiceIsolate(
ILogger<JavaCompilationServiceIsolate> logger,
@@ -85,6 +85,7 @@ public class JavaCompilationServiceIsolate : ICompilationService
arguments.Add($"/box/{sourceFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var stdoutFilePath = Path.Combine(boxDir, "compile_stdout.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
@@ -94,28 +95,57 @@ public class JavaCompilationServiceIsolate : ICompilationService
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10, // javac may spawn multiple processes
StackLimitKb = 128 * 1024, // 128MB stack per process
ProcessLimit = 64, // JVM needs many threads/processes
EnableNetwork = false,
StdoutFile = stdoutFilePath,
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true }
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true }
}
});
var compilerOutput = string.Empty;
// Read stdout (javac may output errors there)
if (File.Exists(stdoutFilePath))
{
var stdoutContent = await File.ReadAllTextAsync(stdoutFilePath);
if (!string.IsNullOrWhiteSpace(stdoutContent))
{
compilerOutput = stdoutContent;
_logger.LogDebug("Read stdout file: {Length} bytes", stdoutContent.Length);
}
}
// Read stderr
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
var stderrContent = await File.ReadAllTextAsync(stderrFilePath);
if (!string.IsNullOrWhiteSpace(stderrContent))
{
compilerOutput = string.IsNullOrEmpty(compilerOutput)
? stderrContent
: $"{compilerOutput}\n{stderrContent}";
_logger.LogDebug("Read stderr file: {Length} bytes", stderrContent.Length);
}
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
compilerOutput = string.IsNullOrEmpty(compilerOutput)
? isolateResult.ErrorOutput
: $"{compilerOutput}\n{isolateResult.ErrorOutput}";
}
_logger.LogDebug("Final compiler output: {Output}", compilerOutput);
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Java compilation time limit exceeded for box {BoxId}", boxId);

View File

@@ -57,27 +57,51 @@ public class JavaExecutionServiceIsolate : IExecutionService
File.Copy(file, destPath, overwrite: true);
}
// Prepare output file in box
// Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Run in Isolate
// Note: Java needs more memory for JVM overhead
var javaMemoryMb = Math.Max(memoryLimitMb, 128); // Minimum 128MB for JVM
// We set JVM heap limit to the requested memory, but give Isolate more for JVM internals
var jvmHeapMb = memoryLimitMb;
var jvmTotalMemoryMb = memoryLimitMb + 64; // Add 64MB for JVM overhead (metaspace, code cache, etc.)
var arguments = new List<string>
{
$"-Xmx{jvmHeapMb}m", // Max heap size = requested memory limit
$"-Xms{Math.Min(jvmHeapMb, 32)}m", // Initial heap size (min 32MB or requested)
"-cp", "/box", "Solution"
};
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = "/usr/bin/java",
Arguments = new[] { "-cp", "/box", "Solution" },
Arguments = arguments.ToArray(),
TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = javaMemoryMb * 1024,
MemoryLimitKb = jvmTotalMemoryMb * 1024,
StackLimitKb = 256 * 1024, // 256 MB stack
ProcessLimit = 64, // Java creates multiple threads
EnableNetwork = false,
StdinFile = inputFilePath,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
WorkingDirectory = "/box"
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true }
}
});
stopwatch.Stop();

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
@@ -14,7 +15,7 @@ public class KotlinCompilationServiceIsolate : ICompilationService
private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
private const int CompilationMemoryLimitMb = 1024; // Kotlin uses JVM, needs more memory
public KotlinCompilationServiceIsolate(
ILogger<KotlinCompilationServiceIsolate> logger,
@@ -38,7 +39,21 @@ public class KotlinCompilationServiceIsolate : ICompilationService
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
// Normalize line endings
var normalizedSourceCode = sourceCode
.Replace("\\r\\n", "\n") // Handle escaped \r\n (literal text "\\r\\n")
.Replace("\\n", "\n") // Handle escaped \n (literal text "\\n")
.Replace("\\r", "\n") // Handle escaped \r (literal text "\\r")
.Replace("\r\n", "\n") // Handle actual Windows line endings
.Replace("\r", "\n"); // Handle actual Mac line endings
_logger.LogDebug("Source code length: {Length}, normalized length: {NormalizedLength}, line count: {LineCount}",
sourceCode.Length, normalizedSourceCode.Length, normalizedSourceCode.Split('\n').Length);
_logger.LogDebug("First 200 chars of source: {Preview}",
normalizedSourceCode.Length > 200 ? normalizedSourceCode.Substring(0, 200) : normalizedSourceCode);
await File.WriteAllTextAsync(sourceFilePath, normalizedSourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, jarFilePath, version);
}
catch (Exception ex)
@@ -88,6 +103,7 @@ public class KotlinCompilationServiceIsolate : ICompilationService
arguments.Add($"/box/{jarFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var stdoutFilePath = Path.Combine(boxDir, "compile_stdout.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
@@ -97,27 +113,57 @@ public class KotlinCompilationServiceIsolate : ICompilationService
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10,
StackLimitKb = 128 * 1024, // 128MB stack per process
ProcessLimit = 64, // Kotlin uses JVM, needs many threads/processes
EnableNetwork = false,
StdoutFile = stdoutFilePath,
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 = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/bin", SandboxPath = "/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true },
new DirectoryBinding { HostPath = "/opt/kotlinc", SandboxPath = "/opt/kotlinc", ReadOnly = true }
},
EnvironmentVariables = new Dictionary<string, string>
{
["PATH"] = "/usr/local/bin:/usr/bin:/bin"
}
});
var compilerOutput = string.Empty;
// Read stdout (kotlinc may output errors there)
if (File.Exists(stdoutFilePath))
{
var stdoutContent = await File.ReadAllTextAsync(stdoutFilePath);
if (!string.IsNullOrWhiteSpace(stdoutContent))
{
compilerOutput = stdoutContent;
}
}
// Read stderr
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
var stderrContent = await File.ReadAllTextAsync(stderrFilePath);
if (!string.IsNullOrWhiteSpace(stderrContent))
{
compilerOutput = string.IsNullOrEmpty(compilerOutput)
? stderrContent
: $"{compilerOutput}\n{stderrContent}";
}
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
compilerOutput = string.IsNullOrEmpty(compilerOutput)
? isolateResult.ErrorOutput
: $"{compilerOutput}\n{isolateResult.ErrorOutput}";
}
if (isolateResult.TimeLimitExceeded)

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Collections.Generic;
using LiquidCode.Tester.Worker.Services.Isolate;
namespace LiquidCode.Tester.Worker.Services;
@@ -52,26 +53,55 @@ public class KotlinExecutionServiceIsolate : IExecutionService
File.Copy(executablePath, boxJarPath, overwrite: true);
// Prepare output file in box
// Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Run in Isolate (Kotlin runs via Java)
var kotlinMemoryMb = Math.Max(memoryLimitMb, 128); // Minimum 128MB for JVM
// We set JVM heap limit to the requested memory, but give Isolate more for JVM internals
var jvmHeapMb = memoryLimitMb;
var jvmTotalMemoryMb = memoryLimitMb + 64; // Add 64MB for JVM overhead
var arguments = new List<string>
{
$"-Xmx{jvmHeapMb}m", // Max heap size = requested memory limit
$"-Xms{Math.Min(jvmHeapMb, 32)}m", // Initial heap size
"-jar", $"/box/{jarName}"
};
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = "/usr/bin/java",
Arguments = new[] { "-jar", $"/box/{jarName}" },
Arguments = arguments.ToArray(),
TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = kotlinMemoryMb * 1024,
MemoryLimitKb = jvmTotalMemoryMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 64, // JVM creates multiple threads
EnableNetwork = false,
StdinFile = inputFilePath,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
WorkingDirectory = "/box"
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 = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/bin", SandboxPath = "/bin", ReadOnly = true },
new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true }
},
EnvironmentVariables = new Dictionary<string, string>
{
["PATH"] = "/usr/local/bin:/usr/bin:/bin"
}
});
stopwatch.Stop();

View File

@@ -55,8 +55,15 @@ public class PythonExecutionServiceIsolate : IExecutionService
File.Copy(executablePath, boxScriptPath, overwrite: true);
// Prepare output file in box
// Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt");
string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
{
sandboxInputPath = Path.Combine(boxDir, "input.txt");
File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
}
// Get Python executable from configuration
var pythonExecutable = _configuration["Python:Executable"] ?? "python3";
@@ -73,7 +80,7 @@ public class PythonExecutionServiceIsolate : IExecutionService
StackLimitKb = 256 * 1024,
ProcessLimit = 1, // Single process for Python
EnableNetwork = false,
StdinFile = inputFilePath,
StdinFile = sandboxInputPath,
StdoutFile = outputFilePath,
WorkingDirectory = "/box"
});

6299
testlib.h Normal file

File diff suppressed because it is too large Load Diff