所以我有一个编译器微服务。该服务的主要职责是编译用户发送的代码片段。这是控制器类。
package codenemy.api.Compiler.Controller;
import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Service.CompilerService;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.service.ProblemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @author chaudhary samir zafar
* @version 1.0
* @since 01/01/2023
*/
@RestController
@RequestMapping(path = "/api/compiler")
@CrossOrigin
public class CompilerController {
private final ProblemService problemService;
private final CompilerService compilerService;
@Autowired
public CompilerController(ProblemService problemService, CompilerService compilerService){
this.problemService = problemService;
this.compilerService = compilerService;
}
@PostMapping(path = "/runAllTestCases")
public ResponseEntity<MultipleTestCaseResults> runScriptForAllTestCases(@RequestBody Request request) {
return ResponseEntity.ok().body(compilerService.runScriptForAllTestCases(request, problemService.getProblem(request.problemId())));
}
@PostMapping(path = "/runAllTestCasesChallenge")
public ResponseEntity<MultipleTestCaseResults> runScriptForAllTestCasesWithChallenge(@RequestBody Request request) {
return ResponseEntity.ok().body(compilerService.runScriptForAllTestCasesWithChallenge(request, problemService.getProblem(request.problemId())));
}
@PostMapping(path = "/runSingleTestCase")
public ResponseEntity<SingleTestCaseResult> runScriptForOneTestCaseVersionTwo(@RequestBody Request request) {
Problem problem = problemService.getProblem(request.problemId());
SingleTestCaseResult singleTestCaseResult = compilerService.runScriptForOneTestCase(request, problem);
return ResponseEntity.ok().body(singleTestCaseResult);
}
}
所以在用户点击这些端点之一之后,该控制器与 CompilerService 交互。此 CompilerService 为每个端点检索正确的 LanguageCompilerService,例如,假设用户想要编译 Java,因此此服务将检索一个名为 JavaCompilerService 的对象并调用该对象上的相应方法。如果用户想要保存提交,那么将包含/运行额外的逻辑。
import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Service.LanguageCompilerService.LanguageCompilerServiceIF;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.model.ProblemLanguage;
import codenemy.api.Submission.Model.Submission;
import codenemy.api.Submission.Service.SubmissionService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* @author chaudhary samir zafar
* @version 1.0
* @since 01/01/2023
*/
@Service
@AllArgsConstructor
public class CompilerService {
private CompilerServiceFactory compilerServiceFactory;
private SubmissionService submissionService;
private ObjectMapper objectMapper;
public SingleTestCaseResult runScriptForOneTestCase(Request request, Problem problem) {
// Retrieve the correct and appropriate compiler service
LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());
// First find the problem Lang
ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());
return languageCompilerServiceIF.executeSingleTestCase(request, problem, problemLanguage);
}
public MultipleTestCaseResults runScriptForAllTestCases(Request request, Problem problem) {
// Retrieve the correct and appropriate compiler service
LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());
// First find the problem Lang
ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());
MultipleTestCaseResults multipleTestCaseResults =
languageCompilerServiceIF.executeAllTestCases(request, problem, problemLanguage);
String json;
if (multipleTestCaseResults.getError() == null) {
try {
json = objectMapper.writeValueAsString(multipleTestCaseResults);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
else {
json = "Code did not compile";
}
submissionService.addSubmission(
new Submission(0,
null,
problem,
LocalDateTime.now(),
json,
multipleTestCaseResults.getPercentage(),
multipleTestCaseResults.getPoints()),
request.userId(),
multipleTestCaseResults);
return multipleTestCaseResults;
}
public MultipleTestCaseResults runScriptForAllTestCasesWithChallenge(Request request, Problem problem) {
// Retrieve the correct and appropriate compiler service
LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());
// First find the problem Lang
ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());
return languageCompilerServiceIF.executeAllTestCases(request, problem, problemLanguage);
}
private ProblemLanguage findProblemLanguageSelected(Problem problem, String selectedLang){
return problem.getProblemLanguages()
.stream()
.filter(element -> element.getLanguage().getProgrammingLanguage().equalsIgnoreCase(selectedLang))
.findAny()
.get();
}
}
这里是 JavaCompilerService 的实现。
package codenemy.api.Compiler.Service.LanguageCompilerService;
import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Model.TestCaseResult;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.model.ProblemLanguage;
import codenemy.api.Util.CompilerUtility;
import lombok.AllArgsConstructor;
import java.util.Scanner;
/**
* @author chaudhary samir zafar
* @version 1.0
* @since 01/01/2023
*/
@AllArgsConstructor
public class JavaCompilerService implements LanguageCompilerServiceIF {
CompilerUtility compilerUtil;
private static final String JAVA_VERSION = "15.0.2";
@Override
public SingleTestCaseResult executeSingleTestCase(Request request, Problem problem, ProblemLanguage problemLanguage) {
TestCaseResult testCaseResult =
compilerUtil.getTestCaseResult
("TestRun.java", buildFinalScript(request, problemLanguage.getTestRunOne()), request.language(), JAVA_VERSION);
if (testCaseResult.getError().size() > 0) {
return new SingleTestCaseResult(null, null, null, null, false, testCaseResult.getError());
}
return compilerUtil.calculateSingleTestResultWithResponse(problem, testCaseResult);
}
@Override
public MultipleTestCaseResults executeAllTestCases(Request request, Problem problem, ProblemLanguage problemLanguage) {
TestCaseResult testCaseResult =
compilerUtil.getTestCaseResult
("TestRun.java", buildFinalScript(request, problemLanguage.getTestRunAll()), request.language(), JAVA_VERSION);
if (testCaseResult.getError().size() > 0) {
MultipleTestCaseResults multipleTestCaseResults = new MultipleTestCaseResults();
multipleTestCaseResults.setError(testCaseResult.getError());
return multipleTestCaseResults;
}
return compilerUtil.calculateAllTestResultsWithResponse(problem, testCaseResult);
}
private String buildFinalScript(Request request, String testRunOne) {
Scanner scanner = new Scanner(request.script());
StringBuilder importStatements = new StringBuilder();
StringBuilder code = new StringBuilder();
while (scanner.hasNext()){
String newLine = scanner.nextLine();
if (newLine.startsWith("import")) {
importStatements.append(newLine).append("\r\n");
} else {
code.append(newLine).append("\r\n");
}
}
scanner.close();
return importStatements + testRunOne + code;
}
}
JavaCompilerService 与 CompilerUtility 类交互。这就是我认为问题发生的地方。
public TestCaseResult getTestCaseResult(String fileName, String fileContent, String programmingLang, String version) {
TestCaseResult testCaseResult = new TestCaseResult(0, null);
List<PistonFile> fileArrayList = List.of(new PistonFile(fileName, fileContent));
PistonRequest pistonRequest = new PistonRequest(programmingLang, fileArrayList);
pistonRequest.setVersion(version);
PistonResponse pistonResponse = makeRequest(pistonRequest);
if (pistonResponse == null) {
log.info("The request to piston is breaking.");
return null;
}
if (pistonResponse.run().signal() != null && pistonResponse.run().signal().equalsIgnoreCase("SIGKILL")) {
testCaseResult.setError(List.of("Your code exceeded the runtime time limit\n"));
return testCaseResult;
}
if (!pistonResponse.run().stderr().isEmpty() || !pistonResponse.run().stderr().isBlank()) {
// Get the errors.
List<String> errors = Arrays.asList(pistonResponse.run().stderr().split("\n"));
testCaseResult.setError(errors);
return testCaseResult;
}
String[] arrOfStdOut = pistonResponse.run().stdout().split("\n");
ArrayList<String> result = new ArrayList<>();
ArrayList<String> output = new ArrayList<>();
for(String s : arrOfStdOut) {
if (s.startsWith("Result - ")) {
result.add(s.replace("Result - ", ""));
} else {
output.add(s + "\n");
}
}
testCaseResult.setOutput(output);
testCaseResult.setResult(result);
return testCaseResult;
}
public PistonResponse makeRequest(PistonRequest requestData) {
String url = "https://emkc.org/api/v2/piston/execute";
URL obj = null;
HttpURLConnection con = null;
try {
obj = new URL(url);
con = (HttpURLConnection) obj.openConnection();
// Set the request method and headers
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
ObjectMapper objectMapper = new ObjectMapper();
// Add throttling
Thread.sleep(3000); // wait for 1 second before sending the request
// Set the request body
String requestBody = objectMapper.writeValueAsString(requestData);
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(requestBody);
wr.flush();
wr.close();
// Get the response
int responseCode = con.getResponseCode();
// Check if the response is okay
if (responseCode == HttpStatus.OK.value()) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// Deserialize the response
return objectMapper.readValue(response.toString(), PistonResponse.class);
}
}
catch (Exception exception) {
log.info(exception.getMessage());
}
return null;
}
调用 getTestCaseResult 方法时,也会调用其中的 makeRequest。如果两个用户同时调用同一个端点,那么其中一个用户的 pistonResponse 返回为 null。我在 makeRequest 中进行了外部 API 调用,我相信当两个请求想要访问该代码时,它会中断其中一个连接。
这只是我对问题的理解,可能是其他原因出错。我不知道真正的根本原因和解决方案。
我认为通过实现队列来使用 Java 并发可以解决问题,但我不太熟悉它,所以进展不顺利。