Compare commits
7 Commits
bc9c162de5
...
roman
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f009c95645 | ||
|
|
7a1f22eb8e | ||
|
|
f5cb2c8e2e | ||
|
|
5ed5925a38 | ||
|
|
bd2ed7716c | ||
| a6c56ecb22 | |||
|
|
6d010ea9ad |
14
compose.yaml
14
compose.yaml
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user