blob: 95806f90604d80df0a98b9b570fa1bcf63e486ff [file] [log] [blame]
51isoftb54753f2014-01-29 21:22:40 +08001/*
2 * File: JudgerThread.cpp
3 * Author: 51isoft
4 *
5 * Created on 2014年1月13日, 下午10:10
6 */
7
8#include "JudgerThread.h"
51isoft2e8fd9d2014-01-30 01:28:36 +08009#include "Bott.h"
51isoftb54753f2014-01-29 21:22:40 +080010
11/**
12 * Initialize a judger handler
13 * @param _socket The client sockfd
14 * @param oj OJ name
15 */
16JudgerThread::JudgerThread(SocketHandler * _socket, string _oj) {
17 db = new DatabaseHandler();
18 oj = _oj;
19 socket = _socket;
20 current_submit = NULL;
21}
22
23JudgerThread::~JudgerThread() {
24 delete db;
25 delete socket;
51isoft8bd73c52014-04-02 13:57:25 +080026 // Don't delete it, since it will be requeued
27 // if (current_submit) delete current_submit;
51isoftb54753f2014-01-29 21:22:40 +080028}
29
51isoft2e8fd9d2014-01-30 01:28:36 +080030/**
31 * Load infos for a regular run, and store it to a bott file
32 * @param bott The bott file we store infos to
33 * @param runid Regular runid
34 */
35void JudgerThread::prepareBottForRun(Bott * bott, string runid) {
36 // load basic info from status table
37 map<string, string> info = db->Getall_results("\
38 SELECT status.source AS source, \
39 status.runid AS runid, \
40 status.language AS language, \
41 status.pid AS pid, \
42 problem.ignore_noc AS ignore_noc \
43 FROM status, problem \
44 WHERE status.pid = problem.pid AND runid = '" + runid + "' \
45 ")[0];
46
47 bott->Setsrc(info["source"]);
48 bott->Setrunid(info["runid"]);
49 bott->Setlanguage(info["language"]);
50 bott->Setpid(info["pid"]);
51
52 // DO_TESTALL will ignore time_limit and just use case_limit
53 // NEED_JUDGE will set the time_limit together with case_limit
54 // a little bit hacky though
55 if (info["ignore_noc"] == "1") {
56 bott->Settype(DO_TESTALL);
57 } else {
58 bott->Settype(NEED_JUDGE);
59 }
60
61 // load additional info from problem table
62 info = db->Getall_results("\
63 SELECT number_of_testcase, time_limit, case_time_limit, memory_limit, special_judge_status, vname, vid \
64 FROM problem \
65 WHERE pid = '" + info["pid"] + "' \
66 ")[0];
67
68 bott->Setnumber_of_testcases(info["number_of_testcase"]);
69 bott->Settime_limit(info["time_limit"]);
70 bott->Setcase_limit(info["case_time_limit"]);
71 bott->Setmemory_limit(info["memory_limit"]);
72 bott->Setspj(info["special_judge_status"]);
73 bott->Setvname(info["vname"]);
74 bott->Setvid(info["vid"]);
75
76 bott->save();
77}
78
79/**
51isoft3d5d5cc2014-01-30 19:01:58 +080080 * Load infos for a challenge, and store it to a bott file
81 * @param bott The bott file we store infos to
82 * @param id Challenge id
83 */
84void JudgerThread::prepareBottForChallenge(Bott * bott, string id) {
85 // load basic info from challenge table
86 map<string, string> info = db->Getall_results("\
87 SELECT source, \
88 cha_id, \
89 language, \
90 pid, \
91 data_type, \
92 data_lang, \
93 data_detail \
94 FROM status,challenge \
95 WHERE status.runid = challenge.runid AND cha_id = '" + id + "' \
96 ")[0];
97
98 bott->Settype(DO_CHALLENGE);
99 bott->Setsrc(info["source"]);
100 bott->Setcha_id(info["cha_id"]);
101 bott->Setlanguage(info["language"]);
102 bott->Setpid(info["pid"]);
103 bott->Setdata_type(info["data_type"]);
104 bott->Setdata_lang(info["data_lang"]);
105 bott->Setdata_detail(info["data_detail"]);
106
107 // load additional info from problem table
108 info = db->Getall_results("\
109 SELECT case_time_limit, memory_limit, special_judge_status \
110 FROM problem \
111 WHERE pid = '" + info["pid"] + "' \
112 ")[0];
113
114 bott->Setcase_limit(info["case_time_limit"]);
115 bott->Setmemory_limit(info["memory_limit"]);
116 bott->Setspj(info["special_judge_status"]);
117
118 bott->save();
119}
120
121/**
122 * Update the runid result in status table
123 * @param runid Runid
124 * @param result Current result
125 */
126void JudgerThread::updateRunResult(string runid, string result) {
127 db->query("UPDATE status SET result='" + db->escape(result) + "' WHERE runid = '" + db->escape(runid) + "'");
128}
129
130/**
131 * Update the runid status with details in status table
132 * @param bott Result details
133 */
134void JudgerThread::updateRunStatus(Bott * bott) {
135 db->query("\
136 UPDATE status \
137 SET result = '" + db->escape(bott->Getresult()) + "', \
138 memory_used = '" + db->escape(bott->Getmemory_used()) + "', \
139 time_used = '" + db->escape(bott->Gettime_used()) + "', \
140 ce_info = '" + db->escape(bott->Getce_info()) + "' \
141 WHERE runid = '" + db->escape(bott->Getrunid()) + "'");
142}
143
144/**
145 * Update the Challenge status with details in related (challenge and status) table
146 * @param bott Result details
147 */
148void JudgerThread::updateChallengeStatus(Bott * bott) {
149 db->query("\
150 UPDATE challenge \
151 SET result = '" + db->escape(bott->Getcha_result()) + "', \
152 cha_detail = '" + db->escape(bott->Getcha_detail()) + "' \
153 WHERE cha_id = '" + db->escape(bott->Getcha_id()) + "'");
154
155 LOG("Challenge result, cha_id: " + bott->Getcha_id() + ", result: " + bott->Getcha_result());
156
157 if (bott->Getcha_result().find("Challenge Success") != string::npos) {
158 // challenge success, update status table
159 db->query("\
160 UPDATE status, challenge \
161 SET status.result = 'Challenged' \
162 WHERE status.runid = challenge.runid AND \
163 challenge.cha_id = '" + db->escape(bott->Getcha_id()) + "' \
164 ");
165 }
166}
167
168/**
169 * Update the challenge result in challenge table
170 * @param id Challenge id
171 * @param result Current result
172 */
173void JudgerThread::updateChallengeResult(string id, string result) {
174 db->query("UPDATE challenge SET cha_result='" + db->escape(result) + "' WHERE cha_id = '" + db->escape(id) + "'");
175}
176
177/**
178 * Update statistics for a regular run
179 * @param runid Runid
180 * @param result Verdict result
181 */
182void JudgerThread::updateStatistics(string runid, string result) {
183
184 // load basic info of this run
185 map<string, string> run_info = db->Getall_results("\
186 SELECT username, status.pid as pid, vname \
187 FROM status, problem \
51isoft2cadb822014-03-21 00:21:13 +0800188 WHERE runid = '" + db->escape(runid) + "' AND problem.pid = status.pid \
51isoft3d5d5cc2014-01-30 19:01:58 +0800189 ")[0];
190
51isoft8bd73c52014-04-02 13:57:25 +0800191 LOG("Updating statistics, runid: " + runid + ", user: " + run_info["username"] + ", result: " + result + ", pid: " + run_info["pid"]);
51isoft3d5d5cc2014-01-30 19:01:58 +0800192
193 if (result.find("Accept") != string::npos) {
194 map<string, string> info = db->Getall_results("\
195 SELECT count(*) \
196 FROM status \
197 WHERE username = '" + db->escape(run_info["username"]) + "' AND \
198 pid = '" + db->escape(run_info["pid"]) + "' AND \
199 result = 'Accepted' \
200 ")[0];
201 if (info["0"] == "1") {
202 // first time AC, add total_ac to user
203 db->query("UPDATE user SET total_ac = total_ac + 1 WHERE username='" + db->escape(run_info["username"]) + "'");
204 if (run_info["vname"] == CONFIG->Getlocal_identifier()) {
205 // if it's local problem, update local_ac
206 db->query("UPDATE user SET local_ac = local_ac + 1 WHERE username='" + db->escape(run_info["username"]) + "'");
207 }
208 }
209 // update problem stats
210 db->query("UPDATE problem SET total_ac = total_ac + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
211 } else if (result.find("Wrong Answer") != string::npos) {
212 db->query("UPDATE problem SET total_wa = total_wa + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
213 } else if (result.find("Runtime Error") != string::npos) {
214 db->query("UPDATE problem SET total_re = total_re + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
215 } else if (result.find("Presentation Error") != string::npos) {
216 db->query("UPDATE problem SET total_pe = total_pe + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
217 } else if (result.find("Time Limit Exceed") != string::npos) {
218 db->query("UPDATE problem SET total_tle = total_tle + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
219 } else if (result.find("Memory Limit Exceed") != string::npos) {
220 db->query("UPDATE problem SET total_mle = total_mle + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
221 } else if (result.find("Output Limit Exceed") != string::npos) {
222 db->query("UPDATE problem SET total_ole = total_ole + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
223 } else if (result.find("Restricted Function") != string::npos) {
224 db->query("UPDATE problem SET total_rf = total_rf + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
225 } else if (result.find("Compile Error") != string::npos) {
226 db->query("UPDATE problem SET total_ce = total_ce + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
227 }
228}
229
230/**
51isoft2e8fd9d2014-01-30 01:28:36 +0800231 * Main loop of the judger handler
232 */
51isoftb54753f2014-01-29 21:22:40 +0800233void JudgerThread::run() {
234 while (true) {
51isoft83d92ff2014-02-04 16:48:23 +0800235 if (!socket->checkAlive()) {
236 LOG("Connection lost. OJ: " + oj);
237 return;
238 }
51isoftb54753f2014-01-29 21:22:40 +0800239 usleep(50000); // sleep 50ms
240 if (current_submit) {
51isoft2e8fd9d2014-01-30 01:28:36 +0800241 // got a new judge task
51isoft3d5d5cc2014-01-30 19:01:58 +0800242 string filename;
243 Bott * bott;
51isoft2e8fd9d2014-01-30 01:28:36 +0800244 if ( current_submit->Gettype() == NEED_JUDGE ||
245 current_submit->Gettype() == DO_PRETEST ||
246 current_submit->Gettype() == DO_TESTALL ) {
247 // A regular run
248 LOG("Load infos of Runid: " + current_submit->Getid());
51isoft2e8fd9d2014-01-30 01:28:36 +0800249
51isoft3d5d5cc2014-01-30 19:01:58 +0800250 // prepare file for judger
251 filename = Bott::RAW_FILES_DIRECTORY + current_submit->Getid() + Bott::EXTENTION;
51isoft2cadb822014-03-21 00:21:13 +0800252 bott = new Bott();
253 bott->Setout_filename(filename);
51isoft3d5d5cc2014-01-30 19:01:58 +0800254 prepareBottForRun(bott, current_submit->Getid());
255 delete bott;
256
257 // set runid result to Judging
258 updateRunResult(current_submit->Getid(), "Judging");
259
260 try {
51isoft2cadb822014-03-21 00:21:13 +0800261 LOG("Sending to judger...");
51isoft3d5d5cc2014-01-30 19:01:58 +0800262 socket->sendFile(filename);
263 filename = Bott::RESULTS_DIRECTORY + current_submit->Getid() + "res" + Bott::EXTENTION;
264 socket->receiveFile(filename);
51isoft2cadb822014-03-21 00:21:13 +0800265 LOG("Got result back from judger.");
51isoft3d5d5cc2014-01-30 19:01:58 +0800266 } catch (Exception & e) {
267 LOG("Connection lost, requeue Runid: " + current_submit->Getid());
268 updateRunResult(current_submit->Getid(), "Judge Error & Requeued");
269 return;
270 }
271
272 // parse and process result from judger
273 bott = new Bott(filename);
274 updateRunStatus(bott);
275 updateStatistics(bott->Getrunid(), bott->Getresult());
51isoft2e8fd9d2014-01-30 01:28:36 +0800276 }
277 else if ( current_submit->Gettype() == DO_CHALLENGE ) {
278 // A challenge
51isoft3d5d5cc2014-01-30 19:01:58 +0800279 LOG("Load infos of Challenge id: " + current_submit->Getid());
280 filename = Bott::CHA_RAW_FILES_DIRECTORY + current_submit->Getid() + Bott::EXTENTION;
281 bott = new Bott(filename);
282 prepareBottForChallenge(bott, current_submit->Getid());
283 delete bott;
284
285 // set challenge id result to Testing
286 updateChallengeResult(current_submit->Getid(), "Testing");
287
288 try {
289 socket->sendFile(filename);
290 filename = Bott::CHA_RESULTS_DIRECTORY + current_submit->Getid() + "res" + Bott::EXTENTION;
291 socket->receiveFile(filename);
292 } catch (Exception & e) {
293 LOG("Connection lost, requeue Challenge id: " + current_submit->Getid());
294 updateChallengeResult(current_submit->Getid(), "Test Error & Requeued");
295 return;
296 }
297
298 // parse and process result from judger
299 bott = new Bott(filename);
300 updateChallengeStatus(bott);
51isoft2e8fd9d2014-01-30 01:28:36 +0800301 }
51isoft3d5d5cc2014-01-30 19:01:58 +0800302
303 // judge finished
304 delete bott;
305 delete current_submit;
306 current_submit = NULL;
51isoftb54753f2014-01-29 21:22:40 +0800307 }
308 }
309}