Java tutorial
/* * Copyright 2007 Xu, Chuan <xuchuan@gmail.com> * * This file is part of ZOJ. * * ZOJ is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either revision 3 of the License, or (at your option) any later revision. * * ZOJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with ZOJ. if not, see * <http://www.gnu.org/licenses/>. */ package cn.edu.zju.acm.onlinejudge.judgeservice; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.CopyUtils; import org.apache.log4j.Logger; import cn.edu.zju.acm.onlinejudge.bean.Limit; import cn.edu.zju.acm.onlinejudge.bean.Problem; import cn.edu.zju.acm.onlinejudge.bean.Reference; import cn.edu.zju.acm.onlinejudge.bean.Submission; import cn.edu.zju.acm.onlinejudge.bean.enumeration.JudgeReply; import cn.edu.zju.acm.onlinejudge.bean.enumeration.Language; import cn.edu.zju.acm.onlinejudge.bean.enumeration.ReferenceType; import cn.edu.zju.acm.onlinejudge.judgeservice.submissionfilter.CompoundSubmissionFilter; import cn.edu.zju.acm.onlinejudge.judgeservice.submissionfilter.SimpleSubmissionFilter; import cn.edu.zju.acm.onlinejudge.judgeservice.submissionfilter.SubmissionFilter; import cn.edu.zju.acm.onlinejudge.judgeservice.submissiontest.LanguageTest; import cn.edu.zju.acm.onlinejudge.judgeservice.submissiontest.NegationTest; import cn.edu.zju.acm.onlinejudge.persistence.PersistenceException; import cn.edu.zju.acm.onlinejudge.persistence.ProblemPersistence; import cn.edu.zju.acm.onlinejudge.persistence.ReferencePersistence; import cn.edu.zju.acm.onlinejudge.persistence.SubmissionPersistence; import cn.edu.zju.acm.onlinejudge.util.PersistenceManager; import cn.edu.zju.acm.onlinejudge.util.Utility; // TODO: add limit checks here public class JudgeClientJudgeThread extends Thread { public static enum Status { CONNECTING, WAITING, RUNNING, ERROR, STOPEED } private DataInputStream in; private DataOutputStream out; private ReferencePersistence referenceDAO = PersistenceManager.getInstance().getReferencePersistence(); private ProblemPersistence problemDAO = PersistenceManager.getInstance().getProblemPersistence(); private SubmissionPersistence submissionDAO = PersistenceManager.getInstance().getSubmissionPersistence(); private Socket socket; private InetSocketAddress address; private Logger logger; private Exception error = null; private Submission submission = null; private SubmissionQueueReader submissionQueueReader = null; private Status status = Status.CONNECTING; private JudgeClient client; private SubmissionFilter submissionFilter = null; public JudgeClientJudgeThread(JudgeClient client) { this.client = client; this.address = new InetSocketAddress(client.getHost(), client.getPort()); this.logger = Logger.getLogger(JudgeService.class.getName()); } public Status getStatus() { return this.status; } public Exception getError() { return this.error; } public Submission getSubmission() { return this.submission; } public JudgeClient getClient() { return this.client; } public SubmissionFilter getSubmissionFilter() { return this.submissionFilter; } public void setSubmissionFilter(SubmissionFilter submissionFilter) { this.submissionFilter = submissionFilter; } @Override public void interrupt() { this.logger.info("interrrupted"); super.interrupt(); Utility.closeSocket(this.socket); } @Override public void run() { try { while (!this.isInterrupted()) { try { this.process(); } catch (IOException e) { this.status = Status.ERROR; this.error = e; this.client.getService().judge(this.submission, Priority.HIGH); if (!this.client.ping()) { break; } } catch (InterruptedException e) { throw e; } catch (Exception e) { this.logger.error(e); } } } catch (InterruptedException e) { } finally { this.submissionQueueReader = null; } this.status = Status.STOPEED; } private void process() throws IOException, InterruptedException { this.status = Status.CONNECTING; this.socket = new Socket(); this.socket.setKeepAlive(true); this.socket.setSoTimeout(JudgeClient.READ_TIMEOUT); this.logger.info( "Connecting to " + this.address.getAddress().getCanonicalHostName() + ":" + this.address.getPort()); this.socket.connect(this.address, JudgeClient.CONNECTION_TIMEOUT); this.logger.info("Connected"); this.in = new DataInputStream(this.socket.getInputStream()); this.out = new DataOutputStream(this.socket.getOutputStream()); this.status = Status.RUNNING; try { while (!this.isInterrupted()) { try { this.status = Status.WAITING; if (this.submissionQueueReader == null) { CompoundSubmissionFilter submissionFilter = new CompoundSubmissionFilter(); submissionFilter.add(new SimpleSubmissionFilter( new NegationTest(new LanguageTest(this.client.getSupportedLanguages())), Priority.DENY)); submissionFilter.add(this.submissionFilter); submissionFilter.add(this.client.getSubmissionFilter()); submissionFilter.add(this.client.getService().getSubmissionFilter()); this.submissionQueueReader = this.client.getService().getSubmissionQueue() .getReader(submissionFilter); } // IMPORTANT: set to null here to avoid rejudging this one when queue.poll throws a // PersistenceException this.submission = null; this.submission = this.submissionQueueReader.poll(this); this.client.getService().judgeStart(this.submission); this.status = Status.RUNNING; try { this.judge(this.submission); } catch (JudgeServerErrorException e) { this.logger.error(e); this.submission.setJudgeReply(JudgeReply.JUDGE_INTERNAL_ERROR); } catch (JudgeClientErrorException e) { this.logger.error(e); this.submission.setJudgeReply(JudgeReply.JUDGE_INTERNAL_ERROR); } catch (PersistenceException e) { this.logger.error(e); this.submission.setJudgeReply(JudgeReply.JUDGE_INTERNAL_ERROR); } this.submissionDAO.updateSubmission(this.submission, 1); this.submission.setContent(null); } catch (PersistenceException e) { this.client.getService().judge(this.submission, Priority.HIGH); Thread.sleep(60000); } finally { this.client.getService().judgeDone(this.submission); } } } finally { Utility.closeSocket(this.socket); } } private void judge(Submission submission) throws JudgeServerErrorException, IOException, PersistenceException, JudgeClientErrorException, ProblemDataErrorException { Problem problem = this.problemDAO.getProblem(submission.getProblemId()); int reply = this.sendJudgeCommand(problem.getId(), problem.getRevision(), submission.getId()); if (reply == JudgeReply.NO_SUCH_PROBLEM.getId()) { reply = this.sendDataCommand(problem); } if (reply == JudgeReply.COMPILATION_ERROR.getId()) { int length = this.in.readInt(); byte[] bytes = new byte[length]; this.in.read(bytes); throw new ProblemDataErrorException( "Special judge compilation failure for problem " + problem.getId() + ": " + new String(bytes)); } if (reply != JudgeReply.READY.getId()) { throw new JudgeClientErrorException(); } String content = submission.getContent(); if (content == null) { content = this.submissionDAO.getSubmissionSource(submission.getId()); } reply = this.sendCompileCommand(submission.getId(), submission.getLanguage(), content); if (reply != JudgeReply.COMPILING.getId()) { throw new JudgeClientErrorException(); } submission.setJudgeReply(JudgeReply.COMPILING); reply = this.readJudgeReply(); if (reply == JudgeReply.COMPILATION_ERROR.getId()) { submission.setJudgeReply(JudgeReply.COMPILATION_ERROR); int length = this.in.readInt(); byte[] bytes = new byte[length]; this.in.read(bytes); submission.setJudgeComment(new String(bytes)); return; } else if (reply != JudgeReply.READY.getId()) { throw new JudgeClientErrorException(); } Limit limit = problem.getLimit(); reply = this.sendTestcaseCommand(1, limit.getTimeLimit(), limit.getMemoryLimit(), limit.getOutputLimit()); submission.setJudgeReply(JudgeReply.RUNNING); submission.setTimeConsumption(0); submission.setMemoryConsumption(0); while (reply == JudgeReply.RUNNING.getId()) { int timeConsumption = this.in.readInt(); int memoryConsumption = this.in.readInt(); submission.setTimeConsumption(timeConsumption); submission.setMemoryConsumption(memoryConsumption); this.logger.info("Running " + timeConsumption + " " + memoryConsumption); reply = this.readJudgeReply(); } while (reply == JudgeReply.JUDGING.getId()) { submission.setJudgeReply(JudgeReply.JUDGING); reply = this.readJudgeReply(); } if (reply == JudgeReply.JUDGE_INTERNAL_ERROR.getId()) { throw new JudgeClientErrorException(); } submission.setJudgeReply(JudgeReply.findById(reply)); if (submission.getJudgeReply() == null || submission.getJudgeReply() != JudgeReply.TIME_LIMIT_EXCEEDED && submission.getJudgeReply() != JudgeReply.MEMORY_LIMIT_EXCEEDED && submission.getJudgeReply() != JudgeReply.OUTPUT_LIMIT_EXCEEDED && submission.getJudgeReply() != JudgeReply.FLOATING_POINT_ERROR && submission.getJudgeReply() != JudgeReply.SEGMENTATION_FAULT && submission.getJudgeReply() != JudgeReply.RUNTIME_ERROR && submission.getJudgeReply() != JudgeReply.NON_ZERO_EXIT_CODE && submission.getJudgeReply() != JudgeReply.ACCEPTED && submission.getJudgeReply() != JudgeReply.WRONG_ANSWER && submission.getJudgeReply() != JudgeReply.PRESENTATION_ERROR) { throw new JudgeClientErrorException(); } } private int readJudgeReply() throws IOException { int reply = this.in.readInt(); JudgeReply r = JudgeReply.findById(reply); if (r == null) { this.logger.error("Invalid judge reply " + reply); } else if (r != JudgeReply.RUNNING) { this.logger.info(r.getDescription()); } return reply; } private int sendJudgeCommand(long problemId, int problemRevision, long submissionId) throws IOException { this.logger.info(this.address + " Judge problem:" + problemId + " revision:" + problemRevision + " submission:" + submissionId); this.sendCommand(JudgeClientCommandsFactory.createJudgeCommand(problemId, problemRevision, submissionId)); return this.readJudgeReply(); } private void zipProblemData(File outputFile, Problem problem) throws PersistenceException, JudgeServerErrorException, ProblemDataErrorException { try { ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(outputFile)); try { List<Reference> inputFiles = this.referenceDAO.getProblemReferences(problem.getId(), ReferenceType.INPUT); List<Reference> outputFiles = this.referenceDAO.getProblemReferences(problem.getId(), ReferenceType.OUTPUT); if (inputFiles.size() != outputFiles.size() && inputFiles.size() > 0 && outputFiles.size() > 0) { throw new ProblemDataErrorException( "Unequal number of inputs and outputs for problem " + problem.getId()); } for (Reference input : inputFiles) { if (input.getContent() == null) { throw new ProblemDataErrorException( "Can not find content for input with reference id " + input.getId()); } } for (Reference output : outputFiles) { if (output.getContent() == null) { throw new ProblemDataErrorException( "Can not find content for output with reference id " + output.getId()); } } Reference specialJudge = null; if (problem.isChecker()) { List<Reference> specialJudges = this.referenceDAO.getProblemReferences(problem.getId(), ReferenceType.CHECKER_SOURCE); if (specialJudges.size() == 0) { throw new ProblemDataErrorException( "Can not find special judge for problem " + problem.getId()); } if (specialJudges.size() > 1) { throw new ProblemDataErrorException( "Find more than one special judge for problem " + problem.getId()); } specialJudge = specialJudges.get(0); String contentType = specialJudge.getContentType(); if (contentType == null) { throw new ProblemDataErrorException( "Can not find source content type for special judge with reference id " + specialJudge.getId()); } byte[] content = specialJudge.getContent(); if (content == null) { throw new ProblemDataErrorException( "Can not find source content for special judge with reference id " + specialJudge.getId()); } if (content.length == 0) { throw new ProblemDataErrorException( "Empty source for special judge with reference id " + specialJudge.getId()); } } for (int i = 0; i < inputFiles.size(); i++) { zipOut.putNextEntry(new ZipEntry(String.format("%d.in", i + 1))); CopyUtils.copy(inputFiles.get(i).getContent(), zipOut); } for (int i = 0; i < outputFiles.size(); i++) { zipOut.putNextEntry(new ZipEntry(String.format("%d.out", i + 1))); CopyUtils.copy(outputFiles.get(i).getContent(), zipOut); } if (specialJudge != null) { zipOut.putNextEntry(new ZipEntry(String.format("judge.%s", specialJudge.getContentType()))); CopyUtils.copy(specialJudge.getContent(), zipOut); } } finally { zipOut.close(); } } catch (IOException e) { throw new JudgeServerErrorException("Fail to zip problem data", e); } } private int sendDataCommand(Problem problem) throws PersistenceException, JudgeServerErrorException, IOException, ProblemDataErrorException { File tempFile; try { tempFile = File.createTempFile("prob", null); } catch (IOException e) { throw new JudgeServerErrorException("Can not create temporary file", e); } try { this.zipProblemData(tempFile, problem); FileInputStream fin; try { fin = new FileInputStream(tempFile); } catch (FileNotFoundException e) { throw new JudgeServerErrorException("Can not find temporary file " + tempFile.getAbsolutePath(), e); } this.logger.info("Data size:" + tempFile.length()); this.sendCommand(JudgeClientCommandsFactory.createDataCommand((int) tempFile.length())); int reply = this.readJudgeReply(); if (reply == JudgeReply.READY.getId()) { try { CopyUtils.copy(fin, this.out); } finally { fin.close(); } reply = this.readJudgeReply(); if (reply == JudgeReply.COMPILING.getId()) { reply = this.readJudgeReply(); } } return reply; } finally { tempFile.delete(); } } private void sendCommand(byte[] bytes) throws IOException { this.out.write(bytes); this.out.flush(); } private int sendCompileCommand(long submissionId, Language language, String sourceFile) throws JudgeServerErrorException, IOException, ProblemDataErrorException { this.logger.info("Compile " + submissionId + "." + language.getOptions()); byte[] bytes = sourceFile.getBytes(); this.logger.info("Compiler:" + language.getId() + " source size:" + bytes.length); this.sendCommand(JudgeClientCommandsFactory.createCompileCommand((int) language.getId(), bytes.length)); int reply = this.readJudgeReply(); if (reply == JudgeReply.READY.getId()) { this.out.write(bytes); this.out.flush(); reply = this.readJudgeReply(); } return reply; } private int sendTestcaseCommand(int testcase, int timeLimit, int memoryLimit, int outputLimit) throws IOException { this.logger.info("Testcase:" + testcase + " TL:" + timeLimit + " ML:" + memoryLimit + " OL:" + outputLimit); this.sendCommand( JudgeClientCommandsFactory.createTestCaseCommand(testcase, timeLimit, memoryLimit, outputLimit)); return this.readJudgeReply(); } }