public async Task <ActionResult <GradeDto> > Grade([FromBody] GradeSubmissionDto gradeSubmissionDto) { // Get language info ComputerLanguage lang = await _context.ComputerLanguages.FindAsync(gradeSubmissionDto.LangId); if (lang == null) { return(BadRequest(new { Message = "The supplied computer language id doesn't exist!" })); } string executionRootDir = Grader.GetExecutionRootDir(); string randomId = RandomString(32); string sourceFileName = $"{randomId}.{lang.Extension}"; string binaryFileName = randomId; string sourceFilePath = $"{executionRootDir}{sourceFileName}"; string binaryFilePath = $"{executionRootDir}{binaryFileName}"; // Create file from source code inside rootDir System.IO.File.WriteAllText(sourceFilePath, gradeSubmissionDto.SourceCode); GradeDto result = new GradeDto(); // this will be returned by the method bool readyToRun = true; // Check if compiled language if (!string.IsNullOrEmpty(lang.CompileCmd)) { // Compile submission CompilationOutputDto co = Grader.Compile(lang, sourceFileName, binaryFileName); if (co.ExitCode != 0) { // Compile error result.Status = "CE"; // Mark status as Compile Error result.Error = co.Error; // Set message as compile error message readyToRun = false; } } if (readyToRun) { // Compiled successfully or interpreted language, so we're ready to run the solution string fileName = string.IsNullOrEmpty(lang.CompileCmd) ? sourceFileName : binaryFileName; // Grade solution result = Grader.Grade(lang, fileName, gradeSubmissionDto.Input, gradeSubmissionDto.ExpectedOutput, gradeSubmissionDto.TimeLimit, gradeSubmissionDto.MemoryLimit); // Delete binary file System.IO.File.Delete(binaryFilePath); } // Delete source file System.IO.File.Delete(sourceFilePath); return(Ok(result)); }
// Compile a source code file into a binary file public static CompilationOutputDto Compile(ComputerLanguage lang, string sourceFileName, string binaryFileName) { string rootDir = GetExecutionRootDir(); string sourceFilePath = $"{rootDir}{sourceFileName}"; string binaryFilePath = $"{rootDir}{binaryFileName}"; string compileCommand = $"{lang.CompilerFileName} {string.Format(lang.CompileCmd, sourceFilePath, binaryFilePath)}"; BashExecutor executor = new BashExecutor(compileCommand); executor.Execute(); return(new CompilationOutputDto { ExitCode = (int)executor.ExitCode, Error = !string.IsNullOrEmpty(executor.Error) ? executor.Error.Replace(sourceFilePath, "") : "", }); }
// Run program, grade a single test case and return the results public static GradeDto Grade(ComputerLanguage lang, string fileName, string input, string expectedOutput, int timeLimitMs = 0, int memoryLimitB = 0) { string rootDir = GetExecutionRootDir(); string timeOutputFilePath = $"{rootDir}time{fileName}.txt"; // create cgroup $"sudo cgcreate -g memory,pids:{fileName}".Bash(); // increase the initial memory limit for the cgroup a bit due to overhead and also set a minimum int pMemLimitB = Math.Max(memoryLimitB + 750000, 1150000); // set memory and max process limit $"sudo cgset -r memory.limit_in_bytes={pMemLimitB} -r memory.swappiness=0 -r pids.max=1 {fileName}".Bash(); // enforce maximum time limit if (timeLimitMs <= 0 || timeLimitMs > 10000) { timeLimitMs = 10000; } // timeout uses a longer time limit value because it measures real time and not cpu time. // we let the process run longer just in case, and after we inspect its cpu time from /usr/bin/time output. // timeout of zero means the associated timeout is disabled. float timeoutS = (timeLimitMs << 2) / 1000.0f; // prepare execution command string string escapedExecCmd = $"/usr/bin/time -p -o {timeOutputFilePath} sudo timeout --preserve-status {timeoutS} sudo cgexec -g memory,pids:{fileName} chroot --userspec=coderunner:no-network {rootDir} {string.Format(lang.ExecuteCmd, fileName)}".Replace("\"", "\\\""); // set initial return values GradeDto grade = new GradeDto { Status = "AC", // will stay accepted if test case passes ExecutionTime = 0, ExecutionMemory = 0, Output = "", Error = "", SubmittedAt = DateTime.Now, }; using (Process q = new Process()) { string output = ""; string error = ""; q.StartInfo.FileName = "/bin/bash"; q.StartInfo.Arguments = $"-c \"{escapedExecCmd}\""; q.StartInfo.RedirectStandardInput = true; q.StartInfo.RedirectStandardOutput = true; q.StartInfo.RedirectStandardError = true; q.StartInfo.CreateNoWindow = true; q.StartInfo.UseShellExecute = false; q.OutputDataReceived += new DataReceivedEventHandler((sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { output += e.Data + "\n"; } }); q.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { error += e.Data + "\n"; } }); q.Start(); q.BeginOutputReadLine(); q.BeginErrorReadLine(); q.StandardInput.WriteLineAsync(input); q.WaitForExit(); // fetch and check if execution CPU time actually meets the limit double userCpuTimeS = double.Parse($"grep -oP '(?<=user ).*' {timeOutputFilePath}".Bash()); double sysCpuTimeS = double.Parse($"grep -oP '(?<=sys ).*' {timeOutputFilePath}".Bash()); int totalCpuTimeMs = (int)((userCpuTimeS + sysCpuTimeS) * 1000 + 0.5); // round grade.ExecutionTime = totalCpuTimeMs; if (q.ExitCode != 0) { // Unsuccessfully executed grade.Error = error; if (q.ExitCode == 137) { // cgroup sent SIGKILL // the process exited with code 137 // meaning the memory limit was breached grade.Status = "MLE"; } else if (q.ExitCode == 143) { // timeout sent SIGTERM // the process exited with code 137 // meaning the time limit was definitely breached grade.Status = "TLE"; grade.ExecutionTime = timeLimitMs; } else { // Runtime error // Rejected grade.Status = "RTE"; } } else if (totalCpuTimeMs > timeLimitMs) { // Successfully executed but actual CPU time breaches the limit grade.Status = "TLE"; grade.ExecutionTime = timeLimitMs; } else { // Successfully executed grade.Output = output; // Check if submission output matches the expected output of test case bool correctSoFar = true; string[] outputLines = output.Trim().Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None ); string[] tcOutputLines = expectedOutput.Trim().Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None ); if (outputLines.Length != tcOutputLines.Length) { correctSoFar = false; } int idx = 0; while (correctSoFar && idx < outputLines.Length) { if (!outputLines.ElementAt(idx).TrimEnd().Equals(tcOutputLines.ElementAt(idx).TrimEnd())) { correctSoFar = false; } ++idx; } if (!correctSoFar) { // Mismatch, set status as wrong answer grade.Status = "WA"; } } } // get memory amount used string maxMemoryUsed = $"cgget -n -v -r memory.max_usage_in_bytes {fileName}".Bash().TrimEnd('\r', '\n'); grade.ExecutionMemory = Int32.Parse(maxMemoryUsed); // delete cgroup $"sudo cgdelete -g memory,pids:{fileName}".Bash(); // delete time output file System.IO.File.Delete(timeOutputFilePath); return(grade); }
public async Task <ActionResult <SubmissionDto> > PostSubmission(int taskId, [FromBody] SubmissionDto submissionDto) { int currentUserId = int.Parse(User.FindFirst(JwtRegisteredClaimNames.Sub).Value); // Get task info Entities.Task task = await _context.Tasks.SingleOrDefaultAsync(t => t.Id == taskId && (t.IsPublic || t.UserId == currentUserId)); if (task == null) { return(BadRequest()); } // Get language info ComputerLanguage lang = await _context.ComputerLanguages.FindAsync(submissionDto.LangId); if (lang == null) { return(BadRequest()); } // Get test cases await _context.Entry(task).Collection(t => t.TestCases).LoadAsync(); Submission submission = new Submission { SourceCode = submissionDto.SourceCode, LangId = submissionDto.LangId, UserId = currentUserId, TimeSubmitted = DateTime.Now, TaskId = taskId, Status = "UD", ExecutionTime = 0, ExecutionMemory = 0, }; // Save initial submission to DB to get a unique id _context.Submissions.Add(submission); await _context.SaveChangesAsync(); string executionRootDir = Grader.GetExecutionRootDir(); string sourceFileName = $"{submission.Id}.{lang.Extension}"; string binaryFileName = submission.Id.ToString(); string sourceFilePath = $"{executionRootDir}{sourceFileName}"; string binaryFilePath = $"{executionRootDir}{binaryFileName}"; // Create file from source code inside rootDir System.IO.File.WriteAllText(sourceFilePath, submissionDto.SourceCode); bool readyToRun = true; // Check if compiled language if (!string.IsNullOrEmpty(lang.CompileCmd)) { // Compile submission CompilationOutputDto co = Grader.Compile(lang, sourceFileName, binaryFileName); if (co.ExitCode != 0) { // Compile error submission.Status = "CE"; // Mark status as Compile Error submissionDto.Message = co.Error; // Set message as compile error message readyToRun = false; } } if (readyToRun) { // Compiled successfully or interpreted language, so we're ready to run the solution submission.Status = "AC"; // Submission status will stay accepted if all test cases pass (or if there aren't any TCs) string fileName = string.IsNullOrEmpty(lang.CompileCmd) ? sourceFileName : binaryFileName; int maxTimeMs = 0; // Track max execution time of test cases int maxMemoryB = 0; // Track max execution memory of test cases bool correctSoFar = true; for (int i = 0; correctSoFar && i < task.TestCases.Count; ++i) { TestCase tc = task.TestCases.ElementAt(i); GradeDto grade = Grader.Grade(lang, fileName, tc.Input, tc.Output, task.TimeLimit, task.MemoryLimit); maxTimeMs = Math.Max(maxTimeMs, grade.ExecutionTime); maxMemoryB = Math.Max(maxMemoryB, grade.ExecutionMemory); submission.Status = grade.Status; correctSoFar = grade.Status.Equals("AC"); } submission.ExecutionTime = maxTimeMs; // Set submission execution time as max out of all test cases submission.ExecutionMemory = maxMemoryB; // Set submission execution memory as max out of all test cases // Delete binary file if compiled if (!string.IsNullOrEmpty(lang.CompileCmd)) { System.IO.File.Delete(binaryFilePath); } } // Edit submission object status and stats await _context.SaveChangesAsync(); // Delete source file System.IO.File.Delete(sourceFilePath); // Prepare response DTO SubmissionDto responseDto = mapper.Map <SubmissionDto>(submission); responseDto.Message = submissionDto.Message; responseDto.Task = null; responseDto.ComputerLanguage = null; responseDto.User = null; return(CreatedAtAction("GetSubmission", new { id = submission.Id }, responseDto)); }