blob: 95806f90604d80df0a98b9b570fa1bcf63e486ff [file] [log] [blame]
/*
* File: JudgerThread.cpp
* Author: 51isoft
*
* Created on 2014年1月13日, 下午10:10
*/
#include "JudgerThread.h"
#include "Bott.h"
/**
* Initialize a judger handler
* @param _socket The client sockfd
* @param oj OJ name
*/
JudgerThread::JudgerThread(SocketHandler * _socket, string _oj) {
db = new DatabaseHandler();
oj = _oj;
socket = _socket;
current_submit = NULL;
}
JudgerThread::~JudgerThread() {
delete db;
delete socket;
// Don't delete it, since it will be requeued
// if (current_submit) delete current_submit;
}
/**
* Load infos for a regular run, and store it to a bott file
* @param bott The bott file we store infos to
* @param runid Regular runid
*/
void JudgerThread::prepareBottForRun(Bott * bott, string runid) {
// load basic info from status table
map<string, string> info = db->Getall_results("\
SELECT status.source AS source, \
status.runid AS runid, \
status.language AS language, \
status.pid AS pid, \
problem.ignore_noc AS ignore_noc \
FROM status, problem \
WHERE status.pid = problem.pid AND runid = '" + runid + "' \
")[0];
bott->Setsrc(info["source"]);
bott->Setrunid(info["runid"]);
bott->Setlanguage(info["language"]);
bott->Setpid(info["pid"]);
// DO_TESTALL will ignore time_limit and just use case_limit
// NEED_JUDGE will set the time_limit together with case_limit
// a little bit hacky though
if (info["ignore_noc"] == "1") {
bott->Settype(DO_TESTALL);
} else {
bott->Settype(NEED_JUDGE);
}
// load additional info from problem table
info = db->Getall_results("\
SELECT number_of_testcase, time_limit, case_time_limit, memory_limit, special_judge_status, vname, vid \
FROM problem \
WHERE pid = '" + info["pid"] + "' \
")[0];
bott->Setnumber_of_testcases(info["number_of_testcase"]);
bott->Settime_limit(info["time_limit"]);
bott->Setcase_limit(info["case_time_limit"]);
bott->Setmemory_limit(info["memory_limit"]);
bott->Setspj(info["special_judge_status"]);
bott->Setvname(info["vname"]);
bott->Setvid(info["vid"]);
bott->save();
}
/**
* Load infos for a challenge, and store it to a bott file
* @param bott The bott file we store infos to
* @param id Challenge id
*/
void JudgerThread::prepareBottForChallenge(Bott * bott, string id) {
// load basic info from challenge table
map<string, string> info = db->Getall_results("\
SELECT source, \
cha_id, \
language, \
pid, \
data_type, \
data_lang, \
data_detail \
FROM status,challenge \
WHERE status.runid = challenge.runid AND cha_id = '" + id + "' \
")[0];
bott->Settype(DO_CHALLENGE);
bott->Setsrc(info["source"]);
bott->Setcha_id(info["cha_id"]);
bott->Setlanguage(info["language"]);
bott->Setpid(info["pid"]);
bott->Setdata_type(info["data_type"]);
bott->Setdata_lang(info["data_lang"]);
bott->Setdata_detail(info["data_detail"]);
// load additional info from problem table
info = db->Getall_results("\
SELECT case_time_limit, memory_limit, special_judge_status \
FROM problem \
WHERE pid = '" + info["pid"] + "' \
")[0];
bott->Setcase_limit(info["case_time_limit"]);
bott->Setmemory_limit(info["memory_limit"]);
bott->Setspj(info["special_judge_status"]);
bott->save();
}
/**
* Update the runid result in status table
* @param runid Runid
* @param result Current result
*/
void JudgerThread::updateRunResult(string runid, string result) {
db->query("UPDATE status SET result='" + db->escape(result) + "' WHERE runid = '" + db->escape(runid) + "'");
}
/**
* Update the runid status with details in status table
* @param bott Result details
*/
void JudgerThread::updateRunStatus(Bott * bott) {
db->query("\
UPDATE status \
SET result = '" + db->escape(bott->Getresult()) + "', \
memory_used = '" + db->escape(bott->Getmemory_used()) + "', \
time_used = '" + db->escape(bott->Gettime_used()) + "', \
ce_info = '" + db->escape(bott->Getce_info()) + "' \
WHERE runid = '" + db->escape(bott->Getrunid()) + "'");
}
/**
* Update the Challenge status with details in related (challenge and status) table
* @param bott Result details
*/
void JudgerThread::updateChallengeStatus(Bott * bott) {
db->query("\
UPDATE challenge \
SET result = '" + db->escape(bott->Getcha_result()) + "', \
cha_detail = '" + db->escape(bott->Getcha_detail()) + "' \
WHERE cha_id = '" + db->escape(bott->Getcha_id()) + "'");
LOG("Challenge result, cha_id: " + bott->Getcha_id() + ", result: " + bott->Getcha_result());
if (bott->Getcha_result().find("Challenge Success") != string::npos) {
// challenge success, update status table
db->query("\
UPDATE status, challenge \
SET status.result = 'Challenged' \
WHERE status.runid = challenge.runid AND \
challenge.cha_id = '" + db->escape(bott->Getcha_id()) + "' \
");
}
}
/**
* Update the challenge result in challenge table
* @param id Challenge id
* @param result Current result
*/
void JudgerThread::updateChallengeResult(string id, string result) {
db->query("UPDATE challenge SET cha_result='" + db->escape(result) + "' WHERE cha_id = '" + db->escape(id) + "'");
}
/**
* Update statistics for a regular run
* @param runid Runid
* @param result Verdict result
*/
void JudgerThread::updateStatistics(string runid, string result) {
// load basic info of this run
map<string, string> run_info = db->Getall_results("\
SELECT username, status.pid as pid, vname \
FROM status, problem \
WHERE runid = '" + db->escape(runid) + "' AND problem.pid = status.pid \
")[0];
LOG("Updating statistics, runid: " + runid + ", user: " + run_info["username"] + ", result: " + result + ", pid: " + run_info["pid"]);
if (result.find("Accept") != string::npos) {
map<string, string> info = db->Getall_results("\
SELECT count(*) \
FROM status \
WHERE username = '" + db->escape(run_info["username"]) + "' AND \
pid = '" + db->escape(run_info["pid"]) + "' AND \
result = 'Accepted' \
")[0];
if (info["0"] == "1") {
// first time AC, add total_ac to user
db->query("UPDATE user SET total_ac = total_ac + 1 WHERE username='" + db->escape(run_info["username"]) + "'");
if (run_info["vname"] == CONFIG->Getlocal_identifier()) {
// if it's local problem, update local_ac
db->query("UPDATE user SET local_ac = local_ac + 1 WHERE username='" + db->escape(run_info["username"]) + "'");
}
}
// update problem stats
db->query("UPDATE problem SET total_ac = total_ac + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Wrong Answer") != string::npos) {
db->query("UPDATE problem SET total_wa = total_wa + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Runtime Error") != string::npos) {
db->query("UPDATE problem SET total_re = total_re + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Presentation Error") != string::npos) {
db->query("UPDATE problem SET total_pe = total_pe + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Time Limit Exceed") != string::npos) {
db->query("UPDATE problem SET total_tle = total_tle + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Memory Limit Exceed") != string::npos) {
db->query("UPDATE problem SET total_mle = total_mle + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Output Limit Exceed") != string::npos) {
db->query("UPDATE problem SET total_ole = total_ole + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Restricted Function") != string::npos) {
db->query("UPDATE problem SET total_rf = total_rf + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
} else if (result.find("Compile Error") != string::npos) {
db->query("UPDATE problem SET total_ce = total_ce + 1 WHERE pid='" + db->escape(run_info["pid"]) + "'");
}
}
/**
* Main loop of the judger handler
*/
void JudgerThread::run() {
while (true) {
if (!socket->checkAlive()) {
LOG("Connection lost. OJ: " + oj);
return;
}
usleep(50000); // sleep 50ms
if (current_submit) {
// got a new judge task
string filename;
Bott * bott;
if ( current_submit->Gettype() == NEED_JUDGE ||
current_submit->Gettype() == DO_PRETEST ||
current_submit->Gettype() == DO_TESTALL ) {
// A regular run
LOG("Load infos of Runid: " + current_submit->Getid());
// prepare file for judger
filename = Bott::RAW_FILES_DIRECTORY + current_submit->Getid() + Bott::EXTENTION;
bott = new Bott();
bott->Setout_filename(filename);
prepareBottForRun(bott, current_submit->Getid());
delete bott;
// set runid result to Judging
updateRunResult(current_submit->Getid(), "Judging");
try {
LOG("Sending to judger...");
socket->sendFile(filename);
filename = Bott::RESULTS_DIRECTORY + current_submit->Getid() + "res" + Bott::EXTENTION;
socket->receiveFile(filename);
LOG("Got result back from judger.");
} catch (Exception & e) {
LOG("Connection lost, requeue Runid: " + current_submit->Getid());
updateRunResult(current_submit->Getid(), "Judge Error & Requeued");
return;
}
// parse and process result from judger
bott = new Bott(filename);
updateRunStatus(bott);
updateStatistics(bott->Getrunid(), bott->Getresult());
}
else if ( current_submit->Gettype() == DO_CHALLENGE ) {
// A challenge
LOG("Load infos of Challenge id: " + current_submit->Getid());
filename = Bott::CHA_RAW_FILES_DIRECTORY + current_submit->Getid() + Bott::EXTENTION;
bott = new Bott(filename);
prepareBottForChallenge(bott, current_submit->Getid());
delete bott;
// set challenge id result to Testing
updateChallengeResult(current_submit->Getid(), "Testing");
try {
socket->sendFile(filename);
filename = Bott::CHA_RESULTS_DIRECTORY + current_submit->Getid() + "res" + Bott::EXTENTION;
socket->receiveFile(filename);
} catch (Exception & e) {
LOG("Connection lost, requeue Challenge id: " + current_submit->Getid());
updateChallengeResult(current_submit->Getid(), "Test Error & Requeued");
return;
}
// parse and process result from judger
bott = new Bott(filename);
updateChallengeStatus(bott);
}
// judge finished
delete bott;
delete current_submit;
current_submit = NULL;
}
}
}