Compare commits

...

7 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
prixod
6d010ea9ad fix
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway, gateway) (push) Successful in 57s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker, worker) (push) Successful in 5m34s
2025-12-01 00:23:36 +04:00
9 changed files with 177 additions and 47 deletions

View File

@@ -26,6 +26,7 @@
worker: worker:
image: liquidcode-tester-worker:latest image: liquidcode-tester-worker:latest
privileged: true
container_name: liquidcode-tester-worker container_name: liquidcode-tester-worker
build: build:
context: . context: .
@@ -36,16 +37,9 @@
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
networks: networks:
- liquidcode-network - liquidcode-network
# Security hardening for Worker # Mount cgroup for Isolate sandbox
security_opt: volumes:
- no-new-privileges:true - /sys/fs/cgroup:/sys/fs/cgroup:rw
- 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
# Temporary filesystem for compilation and testing # Temporary filesystem for compilation and testing
tmpfs: tmpfs:
- /tmp:exec,size=4G - /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.MissionId.ToString()), "MissionId");
form.Add(new StringContent(submit.Language), "Language"); form.Add(new StringContent(submit.Language), "Language");
form.Add(new StringContent(submit.LanguageVersion), "LanguageVersion"); 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"); form.Add(new StringContent(submit.CallbackUrl), "CallbackUrl");
// Add package file // Add package file
@@ -78,10 +78,10 @@ public class WorkerClientService : IWorkerClientService
{ {
var workerUrl = language.ToLowerInvariant() switch var workerUrl = language.ToLowerInvariant() switch
{ {
"c++" => _configuration["Workers:Cpp"], "c++" or "cpp" => _configuration["Workers:Cpp"],
"java" => _configuration["Workers:Java"], "java" => _configuration["Workers:Java"],
"kotlin" => _configuration["Workers:Kotlin"], "kotlin" => _configuration["Workers:Kotlin"],
"c#" => _configuration["Workers:CSharp"], "c#" or "csharp" => _configuration["Workers:CSharp"],
"python" => _configuration["Workers:Python"], "python" => _configuration["Workers:Python"],
_ => throw new NotSupportedException($"Language {language} is not supported") _ => throw new NotSupportedException($"Language {language} is not supported")
}; };

View File

@@ -82,10 +82,8 @@ RUN apt-get update && \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Create unprivileged user for running the worker service # Create unprivileged user for running the worker service
RUN useradd -m -u 1001 -s /bin/bash workeruser && \ RUN mkdir -p /var/local/lib/isolate && \
mkdir -p /var/local/lib/isolate && \ chmod 755 /var/local/lib/isolate
chmod 755 /var/local/lib/isolate && \
chown -R workeruser:workeruser /var/local/lib/isolate
# Configure isolate directories and control-group root # 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 && \ 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 && \
@@ -96,13 +94,11 @@ RUN printf "box_root = /var/local/lib/isolate\nlock_root = /run/isolate/locks\nc
COPY --from=publish /app/publish . COPY --from=publish /app/publish .
# Create temp directory for compilation and testing with proper permissions # Create temp directory for compilation and testing with proper permissions
RUN mkdir -p /tmp/testing && \ RUN mkdir -p /tmp/testing
chown -R workeruser:workeruser /tmp/testing && \
chown -R workeruser:workeruser /app
ENV ASPNETCORE_URLS=http://+:8080 ENV ASPNETCORE_URLS=http://+:8080
# Switch to unprivileged user # Switch to unprivileged user
USER workeruser #USER workeruser
ENTRYPOINT ["dotnet", "LiquidCode.Tester.Worker.dll"] ENTRYPOINT ["dotnet", "LiquidCode.Tester.Worker.dll"]

View File

@@ -103,7 +103,10 @@ public class CSharpCompilationServiceIsolate : ICompilationService
DirectoryBindings = new List<DirectoryBinding> DirectoryBindings = new List<DirectoryBinding>
{ {
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", 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 = "/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 System.Diagnostics;
using LiquidCode.Tester.Worker.Services.Isolate; using LiquidCode.Tester.Worker.Services.Isolate;
using System.Collections.Generic;
namespace LiquidCode.Tester.Worker.Services; namespace LiquidCode.Tester.Worker.Services;
@@ -63,6 +64,7 @@ public class CSharpExecutionServiceIsolate : IExecutionService
// Prepare input/output files inside the sandbox // Prepare input/output files inside the sandbox
var outputFilePath = Path.Combine(boxDir, "output.txt"); var outputFilePath = Path.Combine(boxDir, "output.txt");
var stderrFilePath = Path.Combine(boxDir, "stderr.txt");
string? sandboxInputPath = null; string? sandboxInputPath = null;
if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath)) if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
@@ -71,20 +73,30 @@ public class CSharpExecutionServiceIsolate : IExecutionService
File.Copy(inputFilePath, sandboxInputPath, overwrite: true); File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
} }
// Run in Isolate // Run in Isolate using mono runtime
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{ {
BoxId = boxId, BoxId = boxId,
Executable = $"/box/{executableName}", Executable = "/usr/bin/mono",
Arguments = new[] { $"/box/{executableName}" },
TimeLimitSeconds = timeLimitMs / 1000.0, TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2, WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = memoryLimitMb * 1024, MemoryLimitKb = memoryLimitMb * 1024,
StackLimitKb = 256 * 1024, StackLimitKb = 256 * 1024,
ProcessLimit = 1, // Single process for C# ProcessLimit = 64, // Mono may create multiple threads/processes
EnableNetwork = false, EnableNetwork = false,
StdinFile = sandboxInputPath, StdinFile = sandboxInputPath,
StdoutFile = outputFilePath, 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(); stopwatch.Stop();
@@ -95,10 +107,19 @@ public class CSharpExecutionServiceIsolate : IExecutionService
result.Output = await File.ReadAllTextAsync(outputFilePath); 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 // Map Isolate result to ExecutionResult
result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000); result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000);
result.MemoryUsedMb = isolateResult.MemoryUsedKb / 1024; 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; result.ExitCode = isolateResult.ExitCode;
if (isolateResult.TimeLimitExceeded) if (isolateResult.TimeLimitExceeded)

View File

@@ -14,7 +14,7 @@ public class JavaCompilationServiceIsolate : ICompilationService
private readonly IsolateBoxPool _boxPool; private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30; private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512; private const int CompilationMemoryLimitMb = 1024; // JVM needs more memory
public JavaCompilationServiceIsolate( public JavaCompilationServiceIsolate(
ILogger<JavaCompilationServiceIsolate> logger, ILogger<JavaCompilationServiceIsolate> logger,
@@ -85,6 +85,7 @@ public class JavaCompilationServiceIsolate : ICompilationService
arguments.Add($"/box/{sourceFileName}"); arguments.Add($"/box/{sourceFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt"); var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var stdoutFilePath = Path.Combine(boxDir, "compile_stdout.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{ {
@@ -94,28 +95,57 @@ public class JavaCompilationServiceIsolate : ICompilationService
TimeLimitSeconds = CompilationTimeLimitSeconds, TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2, WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024, MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024, StackLimitKb = 128 * 1024, // 128MB stack per process
ProcessLimit = 10, // javac may spawn multiple processes ProcessLimit = 64, // JVM needs many threads/processes
EnableNetwork = false, EnableNetwork = false,
StdoutFile = stdoutFilePath,
StderrFile = stderrFilePath, StderrFile = stderrFilePath,
WorkingDirectory = "/box", WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding> 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 = "/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; 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)) 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)) 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) if (isolateResult.TimeLimitExceeded)
{ {
_logger.LogWarning("Java compilation time limit exceeded for box {BoxId}", boxId); _logger.LogWarning("Java compilation time limit exceeded for box {BoxId}", boxId);

View File

@@ -69,22 +69,39 @@ public class JavaExecutionServiceIsolate : IExecutionService
// Run in Isolate // Run in Isolate
// Note: Java needs more memory for JVM overhead // 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 var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{ {
BoxId = boxId, BoxId = boxId,
Executable = "/usr/bin/java", Executable = "/usr/bin/java",
Arguments = new[] { "-cp", "/box", "Solution" }, Arguments = arguments.ToArray(),
TimeLimitSeconds = timeLimitMs / 1000.0, TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2, WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = javaMemoryMb * 1024, MemoryLimitKb = jvmTotalMemoryMb * 1024,
StackLimitKb = 256 * 1024, // 256 MB stack StackLimitKb = 256 * 1024, // 256 MB stack
ProcessLimit = 64, // Java creates multiple threads ProcessLimit = 64, // Java creates multiple threads
EnableNetwork = false, EnableNetwork = false,
StdinFile = sandboxInputPath, StdinFile = sandboxInputPath,
StdoutFile = outputFilePath, 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(); stopwatch.Stop();

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using LiquidCode.Tester.Worker.Services.Isolate; using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models; using LiquidCode.Tester.Worker.Models;
@@ -14,7 +15,7 @@ public class KotlinCompilationServiceIsolate : ICompilationService
private readonly IsolateBoxPool _boxPool; private readonly IsolateBoxPool _boxPool;
private const int CompilationTimeLimitSeconds = 30; private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512; private const int CompilationMemoryLimitMb = 1024; // Kotlin uses JVM, needs more memory
public KotlinCompilationServiceIsolate( public KotlinCompilationServiceIsolate(
ILogger<KotlinCompilationServiceIsolate> logger, ILogger<KotlinCompilationServiceIsolate> logger,
@@ -38,7 +39,21 @@ public class KotlinCompilationServiceIsolate : ICompilationService
try 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); return await CompileFileInIsolateAsync(sourceFilePath, jarFilePath, version);
} }
catch (Exception ex) catch (Exception ex)
@@ -88,6 +103,7 @@ public class KotlinCompilationServiceIsolate : ICompilationService
arguments.Add($"/box/{jarFileName}"); arguments.Add($"/box/{jarFileName}");
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt"); var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
var stdoutFilePath = Path.Combine(boxDir, "compile_stdout.txt");
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{ {
@@ -97,27 +113,57 @@ public class KotlinCompilationServiceIsolate : ICompilationService
TimeLimitSeconds = CompilationTimeLimitSeconds, TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2, WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024, MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024, StackLimitKb = 128 * 1024, // 128MB stack per process
ProcessLimit = 10, ProcessLimit = 64, // Kotlin uses JVM, needs many threads/processes
EnableNetwork = false, EnableNetwork = false,
StdoutFile = stdoutFilePath,
StderrFile = stderrFilePath, StderrFile = stderrFilePath,
WorkingDirectory = "/box", WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding> DirectoryBindings = new List<DirectoryBinding>
{ {
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", 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 = "/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 } 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; 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)) 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)) if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{ {
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim(); compilerOutput = string.IsNullOrEmpty(compilerOutput)
? isolateResult.ErrorOutput
: $"{compilerOutput}\n{isolateResult.ErrorOutput}";
} }
if (isolateResult.TimeLimitExceeded) if (isolateResult.TimeLimitExceeded)

View File

@@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Collections.Generic;
using LiquidCode.Tester.Worker.Services.Isolate; using LiquidCode.Tester.Worker.Services.Isolate;
namespace LiquidCode.Tester.Worker.Services; namespace LiquidCode.Tester.Worker.Services;
@@ -63,22 +64,44 @@ public class KotlinExecutionServiceIsolate : IExecutionService
} }
// Run in Isolate (Kotlin runs via Java) // 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 var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{ {
BoxId = boxId, BoxId = boxId,
Executable = "/usr/bin/java", Executable = "/usr/bin/java",
Arguments = new[] { "-jar", $"/box/{jarName}" }, Arguments = arguments.ToArray(),
TimeLimitSeconds = timeLimitMs / 1000.0, TimeLimitSeconds = timeLimitMs / 1000.0,
WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2, WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
MemoryLimitKb = kotlinMemoryMb * 1024, MemoryLimitKb = jvmTotalMemoryMb * 1024,
StackLimitKb = 256 * 1024, StackLimitKb = 256 * 1024,
ProcessLimit = 64, // JVM creates multiple threads ProcessLimit = 64, // JVM creates multiple threads
EnableNetwork = false, EnableNetwork = false,
StdinFile = sandboxInputPath, StdinFile = sandboxInputPath,
StdoutFile = outputFilePath, 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(); stopwatch.Stop();