feat(judger): uoj_judger_v2
All checks were successful
continuous-integration/drone/push Build is passing

ref: https://github-redirect.dependabot.com/UniversalOJ/UOJ-System/pull/113

Co-authored-by: vfleaking <vfleaking@163.com>
Co-authored-by: Yefori-Go <110314400+Yefori-Go@users.noreply.github.com>
This commit is contained in:
Baoshuo Ren 2022-10-04 20:57:49 +08:00
parent 354c417737
commit bcf5ce8b06
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
18 changed files with 7279 additions and 2206 deletions

View File

@ -1,9 +1,9 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
ARG CLONE_ADDFLAG
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
ADD . /opt/uoj_judger
WORKDIR /opt/uoj_judger

View File

@ -3,7 +3,8 @@
getAptPackage(){
printf "\n\n==> Getting environment packages\n"
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
apt-get update
apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
}
setJudgeConf(){
@ -21,12 +22,6 @@ UOJEOF
su judger <<EOD
ln -s /var/uoj_data_copy /opt/uoj_judger/uoj_judger/data
cd /opt/uoj_judger && chmod +x judge_client
cat >uoj_judger/include/uoj_work_path.h <<UOJEOF
#define UOJ_WORK_PATH "/opt/uoj_judger/uoj_judger"
#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
#define UOJ_JUDGER_PYTHON3_VERSION "3.8"
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
UOJEOF
cd uoj_judger && make -j$(($(nproc) + 1))
EOD
}

View File

@ -5,433 +5,600 @@ import sys
import os
import pipes
import socket
from threading import Thread
from threading import Thread, Lock, Condition, RLock
import fcntl
import shutil
import traceback
import time
import logging
import codecs
from contextlib import closing
from typing import *
import requests
import queue as queue
from queue import Queue, Empty
taskQ = Queue()
submission = None
jconf: dict
# path related function
def uoj_url(uri):
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
def uoj_judger_path(path = ''):
return "uoj_judger" + path
# os related funciton
def clean_up_folder(path):
for f in os.listdir(path):
f_path = os.path.join(path, f)
if os.path.isfile(f_path):
os.unlink(f_path)
else:
shutil.rmtree(f_path)
for f in os.listdir(path):
f_path = os.path.join(path, f)
if os.path.isfile(f_path):
os.unlink(f_path)
else:
shutil.rmtree(f_path)
def execute(cmd):
if os.system(cmd):
raise Exception('failed to execute: %s' % cmd)
if os.system(cmd) != 0:
raise Exception('failed to execute: %s' % cmd)
def freopen(f, g):
os.dup2(g.fileno(), f.fileno())
g.close()
os.dup2(g.fileno(), f.fileno())
g.close()
# utf 8
def uoj_text_replace(e):
return ''.join('<b>\\x%02x</b>' % b for b in e.object[e.start:e.end]), e.end
codecs.register_error('uoj_text_replace', uoj_text_replace)
# init
def init():
global jconf
os.chdir(os.path.dirname(os.path.realpath(__file__)))
with open('.conf.json', 'r') as fp:
jconf = json.load(fp)
assert 'uoj_protocol' in jconf
assert 'uoj_host' in jconf
assert 'judger_name' in jconf
assert 'judger_password' in jconf
assert 'socket_port' in jconf
assert 'socket_password' in jconf
global jconf
os.chdir(os.path.dirname(os.path.realpath(__file__)))
with open('.conf.json', 'r') as fp:
jconf = json.load(fp)
assert 'uoj_protocol' in jconf
assert 'uoj_host' in jconf
assert 'judger_name' in jconf
assert 'judger_password' in jconf
assert 'socket_port' in jconf
assert 'socket_password' in jconf
if 'judger_cpus' not in jconf:
jconf['judger_cpus'] = [
'0'] # it should be distinct physical cores! Usually, you can just set it to some even numbers
# socket server
def socket_server_loop():
SOCK_CLOEXEC = 524288
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', jconf['socket_port']))
s.listen(5)
while True:
try:
conn, addr = s.accept()
with closing(conn) as conn:
data = conn.recv(1024).decode()
assert data != None
task = json.loads(data)
assert task['password'] == jconf['socket_password']
assert 'cmd' in task
taskQ.put(task)
if task['cmd'] == 'stop':
print('the judge client is closing...')
taskQ.join()
conn.sendall(b'ok')
return 'stop'
except Exception:
print('['+time.asctime()+']', 'connection rejected', file=sys.stderr)
traceback.print_exc()
else:
print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr)
def start_judger_server():
global socket_server_thread
print_judge_client_status()
print('hello!', file=sys.stderr)
socket_server_thread = Thread(target = socket_server_loop)
socket_server_thread.setDaemon(True)
socket_server_thread.start()
judger_loop()
# path related function
def uoj_url(uri):
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
# report thread
def report_loop():
if 'is_hack' in submission:
return
while not submission_judged:
try:
with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f:
fcntl.flock(f, fcntl.LOCK_SH)
try:
status = f.read(100)
except Exception:
status = None
finally:
fcntl.flock(f, fcntl.LOCK_UN)
if status != None:
data = {}
data['update-status'] = True
data['id'] = submission['id']
if 'is_custom_test' in submission:
data['is_custom_test'] = True
data['status'] = status
uoj_interact(data)
time.sleep(0.2)
except Exception:
pass
# handle task in main thread
def handle_task():
need_restart = False
try:
while True:
task = taskQ.get_nowait()
if task['cmd'] == 'update':
try:
uoj_download('/judger', 'judger_update.zip')
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
except:
print("error when update", file=sys.stderr)
if jconf['judger_name'] == 'main_judger':
uoj_sync_judge_client()
need_restart = True
elif task['cmd'] == 'stop':
taskQ.task_done()
socket_server_thread.join()
print_judge_client_status()
print("goodbye!", file=sys.stderr)
sys.exit(0)
taskQ.task_done()
except Empty:
pass
if need_restart:
os.execl('./judge_client', './judge_client')
def uoj_judger_path(path=''):
return os.getcwd() + "/uoj_judger" + path
def print_judge_client_status():
print('[' + time.asctime() + ']', end=' ', file=sys.stderr)
if submission != None:
print(submission, end=' ', file=sys.stderr)
print(file=sys.stderr)
# interact with uoj_judger
def get_judger_result():
res = {}
with open(uoj_judger_path('/result/result.txt'), 'r') as fres:
res['score'] = 0
res['time'] = 0
res['memory'] = 0
while True:
line = fres.readline()
if line == '':
break
line = line.strip()
if line == 'details':
res['details'] = fres.read()
break
sp = line.split()
assert len(sp) >= 1
if sp[0] == 'error':
res['error'] = line[len('error') + 1:]
else:
assert len(sp) == 2
res[sp[0]] = sp[1]
res['score'] = int(res['score'])
res['time'] = int(res['time'])
res['memory'] = int(res['memory'])
res['status'] = 'Judged'
return res
class RWLock:
def __init__(self):
self.rwlock = 0
self.writers_waiting = 0
self.monitor = Lock()
self.readers_ok = Condition(self.monitor)
self.writers_ok = Condition(self.monitor)
def update_problem_data(problem_id, problem_mtime):
try:
if jconf['judger_name'] == 'main_judger':
return
copy_name = uoj_judger_path('/data/%d' % problem_id)
copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id)
if os.path.isdir(copy_name):
quoted_copy_name = pipes.quote(copy_name)
if os.path.getmtime(copy_name) >= problem_mtime:
execute('touch -a %s' % quoted_copy_name)
return
else:
execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name))
del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99]
for fname in del_list:
quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname))
os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname))
uoj_download('/problem/%d' % problem_id, copy_zip_name)
execute('cd %s && unzip -o -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id))
except Exception:
print_judge_client_status()
traceback.print_exc()
raise Exception('failed to update problem data of #%d' % problem_id)
else:
print_judge_client_status()
print('updated problem data of #%d successfully' % problem_id, file=sys.stderr)
def acquire_read(self):
self.monitor.acquire()
while self.rwlock < 0 or self.writers_waiting:
self.readers_ok.wait()
self.rwlock += 1
self.monitor.release()
def acquire_write(self):
self.monitor.acquire()
while self.rwlock != 0:
self.writers_waiting += 1
self.writers_ok.wait()
self.writers_waiting -= 1
self.rwlock = -1
self.monitor.release()
def promote(self):
self.monitor.acquire()
self.rwlock -= 1
while self.rwlock != 0:
self.writers_waiting += 1
self.writers_ok.wait()
self.writers_waiting -= 1
self.rwlock = -1
self.monitor.release()
def demote(self):
self.monitor.acquire()
self.rwlock = 1
self.readers_ok.notifyAll()
self.monitor.release()
def release(self):
self.monitor.acquire()
if self.rwlock < 0:
self.rwlock = 0
else:
self.rwlock -= 1
wake_writers = self.writers_waiting and self.rwlock == 0
wake_readers = self.writers_waiting == 0
self.monitor.release()
if wake_writers:
self.writers_ok.acquire()
self.writers_ok.notify()
self.writers_ok.release()
elif wake_readers:
self.readers_ok.acquire()
self.readers_ok.notify_all()
self.readers_ok.release()
class Judger:
problem_data_lock = RWLock()
send_and_fetch_lock = RLock()
judger_id: int
cpus: str
main_path: str
submission: Optional[dict]
submission_judged: bool
main_thread: Optional[Thread]
report_thread: Optional[Thread]
_taskQ: Queue
def log_judge_client_status(self):
logging.info('submission: ' + str(self.submission))
def __init__(self, judger_id, cpus):
self.judger_id = judger_id
self.cpus = cpus
self.main_path = '/tmp/' + jconf['judger_name'] + '/' + str(self.judger_id)
self.submission = None
self.main_thread = Thread(target=self._main_loop)
self.report_thread = None
self.submission_judged = False
self._taskQ = Queue()
def start(self):
execute('mkdir -p %s' % (self.main_path + '/result'))
execute('mkdir -p %s' % (self.main_path + '/work'))
self.log_judge_client_status()
logging.info('hello from judger #%d' % self.judger_id)
self.main_thread.start()
def suspend(self):
self._taskQ.put('suspend')
self._taskQ.join()
def resume(self):
self._taskQ.put('resume')
self._taskQ.join()
def exit(self):
self._taskQ.put('exit')
self._taskQ.join()
self.main_thread.join()
# report thread
def _report_loop(self):
if 'is_hack' in self.submission:
return
while not self.submission_judged:
try:
with open(self.main_path + '/result/cur_status.txt', 'r') as f:
fcntl.flock(f, fcntl.LOCK_SH)
try:
status = f.read(100)
except Exception:
status = None
finally:
fcntl.flock(f, fcntl.LOCK_UN)
if status != None:
data = {}
data['update-status'] = True
data['id'] = self.submission['id']
if 'is_custom_test' in self.submission:
data['is_custom_test'] = True
data['status'] = status
uoj_interact(data)
time.sleep(0.2)
except Exception:
pass
def _get_result(self):
res = {}
with open(self.main_path + '/result/result.txt', 'r', encoding='utf-8', errors='uoj_text_replace') as fres:
res['score'] = 0
res['time'] = 0
res['memory'] = 0
while True:
line = fres.readline()
if line == '':
break
line = line.strip()
if line == 'details':
res['details'] = fres.read()
break
sp = line.split()
assert len(sp) >= 1
if sp[0] == 'error':
res['error'] = line[len('error') + 1:]
else:
assert len(sp) == 2
res[sp[0]] = sp[1]
res['score'] = int(res['score'])
res['time'] = int(res['time'])
res['memory'] = int(res['memory'])
res['status'] = 'Judged'
return res
def _remove_problem_data(self, problem_id):
quoted_path = pipes.quote(uoj_judger_path(f'/data/{problem_id}'))
execute(f'chmod 700 {quoted_path} -R && rm -rf {quoted_path}')
def _update_problem_data_atime(self, problem_id):
path = uoj_judger_path(f'/data/{problem_id}')
execute(f'touch -a {pipes.quote(path)}')
def _update_problem_data(self, problem_id, problem_mtime):
Judger.problem_data_lock.acquire_read()
try:
copy_name = uoj_judger_path(f'/data/{problem_id}')
copy_zip_name = uoj_judger_path(f'/data/{problem_id}.zip')
if os.path.isdir(copy_name):
if os.path.getmtime(copy_name) >= problem_mtime:
self._update_problem_data_atime(problem_id)
return
else:
Judger.problem_data_lock.promote()
self._remove_problem_data(problem_id)
else:
Judger.problem_data_lock.promote()
del_list = sorted(os.listdir(uoj_judger_path('/data')),
key=lambda p: os.path.getatime(uoj_judger_path(f'/data/{p}')))[:-99]
for p in del_list:
self._remove_problem_data(p)
uoj_download(f'/problem/{problem_id}', copy_zip_name)
execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (
uoj_judger_path('/data'), problem_id, problem_id, problem_id))
os.utime(uoj_judger_path(f'/data/{problem_id}'), (time.time(), problem_mtime))
except Exception:
self.log_judge_client_status()
logging.exception('problem update error')
raise Exception(f'failed to update problem data of #{problem_id}')
else:
self.log_judge_client_status()
logging.info(f'updated problem data of #{problem_id} successfully')
finally:
Judger.problem_data_lock.release()
def _judge(self):
clean_up_folder(self.main_path + '/work')
clean_up_folder(self.main_path + '/result')
self._update_problem_data(self.submission['problem_id'], self.submission['problem_mtime'])
with open(self.main_path + '/work/submission.conf', 'w') as fconf:
uoj_download(self.submission['content']['file_name'], self.main_path + '/work/all.zip')
execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(self.main_path + '/work'))
for k, v in self.submission['content']['config']:
print(k, v, file=fconf)
if 'is_hack' in self.submission:
if self.submission['hack']['input_type'] == 'USE_FORMATTER':
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input_raw.txt')
execute('%s <%s >%s' % (
pipes.quote(uoj_judger_path('/run/formatter')),
pipes.quote(self.main_path + '/work/hack_input_raw.txt'),
pipes.quote(self.main_path + '/work/hack_input.txt')
))
else:
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input.txt')
print('test_new_hack_only on', file=fconf)
elif 'is_custom_test' in self.submission:
print('custom_test on', file=fconf)
self.submission_judged = False
self.report_thread = Thread(target=self._report_loop)
self.report_thread.start()
Judger.problem_data_lock.acquire_read()
try:
main_judger_cmd = pipes.quote(uoj_judger_path('/main_judger'))
if self.cpus != 'all':
main_judger_cmd = 'taskset -c %s %s' % (pipes.quote(self.cpus), main_judger_cmd)
execute('cd %s && %s' % (pipes.quote(self.main_path), main_judger_cmd))
finally:
Judger.problem_data_lock.release()
self.submission_judged = True
self.report_thread.join()
self.report_thread = None
return self._get_result()
def _send_and_fetch(self, result=None, fetch_new=True):
"""send judgement result, and fetch new submission to judge"""
data = {}
files = {}
if not fetch_new:
data['fetch_new'] = False
if result is not None:
data['submit'] = True
if 'is_hack' in self.submission:
data['is_hack'] = True
data['id'] = self.submission['hack']['id']
if result != False and result['score']:
try:
logging.info("succ hack!")
files = {
('hack_input', open(self.main_path + '/work/hack_input.txt', 'rb')),
('std_output', open(self.main_path + '/work/std_output.txt', 'rb'))
}
except Exception:
self.log_judge_client_status()
logging.exception('hack: submit error')
result = False
elif 'is_custom_test' in self.submission:
data['is_custom_test'] = True
data['id'] = self.submission['id']
else:
data['id'] = self.submission['id']
if 'judge_time' in self.submission:
data['judge_time'] = self.submission['judge_time']
if 'tid' in self.submission:
data['tid'] = self.submission['tid']
if result == False:
result = {
'score': 0,
'error': 'Judgment Failed',
'details': 'Unknown Error'
}
result['status'] = 'Judged'
data['result'] = json.dumps(result, ensure_ascii=False)
while True:
Judger.send_and_fetch_lock.acquire()
try:
ret = uoj_interact(data, files)
except Exception:
self.log_judge_client_status()
logging.exception('uoj_interact error')
else:
break
finally:
Judger.send_and_fetch_lock.release()
time.sleep(2)
try:
self.submission = json.loads(ret)
except Exception:
if ret != 'Nothing to judge':
logging.info(ret)
self.submission = None
return False
else:
return True
def _main_loop(self):
while True:
if not self._taskQ.empty():
while True:
task = self._taskQ.get()
if task == 'resume':
self._taskQ.task_done()
break
elif task == 'exit':
self._taskQ.task_done()
return
else:
self._taskQ.task_done()
if not self._send_and_fetch():
time.sleep(2)
continue
self.log_judge_client_status()
logging.info('judging')
while True:
try:
res = self._judge()
except Exception:
self.log_judge_client_status()
logging.exception('judge error')
res = False
if not self._send_and_fetch(result=res, fetch_new=self._taskQ.empty()):
break
class JudgerServer:
taskQ: Queue
judgers: List[Judger]
socket_thread: Thread
def __init__(self):
self.taskQ = Queue()
self.judgers = [Judger(i, cpus) for i, cpus in enumerate(jconf['judger_cpus'])]
self.socket_thread = Thread(target=self._socket_loop)
def _socket_loop(self):
try:
SOCK_CLOEXEC = 524288
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', jconf['socket_port']))
s.listen(5)
while True:
try:
conn, addr = s.accept()
with closing(conn) as conn:
data = conn.recv(1024)
assert data != None
task = json.loads(data)
assert task['password'] == jconf['socket_password']
assert 'cmd' in task
self.taskQ.put(task)
if task['cmd'] == 'stop':
logging.info('the judge client is closing...')
self.taskQ.join()
conn.sendall(b'ok')
return 'stop'
except Exception:
logging.exception('connection rejected')
else:
logging.info('a new task accomplished')
except Exception:
self.taskQ.put({'cmd': 'stop'})
logging.exception('socket server error!')
def run(self):
self.socket_thread.start()
for judger in self.judgers:
judger.start()
while True:
task = self.taskQ.get()
need_restart = False
block_wait = False
for judger in self.judgers:
judger.suspend()
try:
while True:
if task['cmd'] in ['update', 'self-update']:
try:
uoj_download('/judger', 'judger_update.zip')
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
except:
print(sys.stderr, "error when update")
if jconf['judger_name'] == 'main_judger':
uoj_sync_judge_client()
need_restart = True
elif task['cmd'] == 'stop':
self.taskQ.task_done()
self.socket_thread.join()
for judger in self.judgers:
judger.exit()
logging.info("goodbye!")
sys.exit(0)
self.taskQ.task_done()
task = self.taskQ.get(block=block_wait)
except Empty:
pass
if need_restart:
os.execl('./judge_client', './judge_client')
for judger in self.judgers:
judger.resume()
def judge():
global report_thread
global submission_judged
clean_up_folder(uoj_judger_path('/work'))
clean_up_folder(uoj_judger_path('/result'))
update_problem_data(submission['problem_id'], submission['problem_mtime'])
with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf:
uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip'))
execute("cd %s && unzip -o -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work')))
for k, v in submission['content']['config']:
print(k, v, file=fconf)
if 'is_hack' in submission:
if submission['hack']['input_type'] == 'USE_FORMATTER':
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt'))
execute('%s <%s >%s' % (
pipes.quote(uoj_judger_path('/run/formatter')),
pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')),
pipes.quote(uoj_judger_path('/work/hack_input.txt'))))
else:
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt'))
print('test_new_hack_only on', file=fconf)
elif 'is_custom_test' in submission:
print('custom_test on', file=fconf)
report_thread = Thread(target = report_loop)
report_thread.setDaemon(True)
submission_judged = False
report_thread.start()
execute(pipes.quote(uoj_judger_path('/main_judger')))
submission_judged = True
report_thread.join()
return get_judger_result()
# interact with uoj web server
def uoj_interact(data, files = {}):
data = data.copy()
data.update({
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
})
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
# TODO: set a larger timeout
def uoj_interact(data, files={}):
data = data.copy()
data.update({
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
})
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
def uoj_download(uri, filename):
data = {
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
}
with open(filename, 'wb') as f:
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
for chunk in r.iter_content(chunk_size=65536):
if chunk:
f.write(chunk)
data = {
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
}
with open(filename, 'wb') as f:
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
for chunk in r.iter_content(chunk_size=65536):
if chunk:
f.write(chunk)
def uoj_sync_judge_client():
data = {
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
}
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
if ret != "ok":
raise Exception('failed to sync judge clients: %s' % ret)
data = {
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
}
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
if ret != "ok":
raise Exception('failed to sync judge clients: %s' % ret)
def send_and_fetch(result = None, fetch_new = True):
global submission
"""send judgement result, and fetch new submission to judge"""
data = {}
files = {}
if not fetch_new:
data['fetch_new'] = False
if result != None:
data['submit'] = True
if 'is_hack' in submission:
data['is_hack'] = True
data['id'] = submission['hack']['id']
if result != False and result['score']:
try:
print("succ hack!", file=sys.stderr)
files = {
('hack_input', open('uoj_judger/work/hack_input.txt', 'r')),
('std_output', open('uoj_judger/work/std_output.txt', 'r'))
}
except Exception:
print_judge_client_status()
traceback.print_exc()
result = False
elif 'is_custom_test' in submission:
data['is_custom_test'] = True
data['id'] = submission['id']
else:
data['id'] = submission['id']
if result == False:
result = {
'score': 0,
'error': 'Judgement Failed',
'details': 'Unknown Error'
}
result['status'] = 'Judged'
data['result'] = json.dumps(result, ensure_ascii=False)
while True:
try:
ret = uoj_interact(data, files)
print(ret)
except Exception:
print_judge_client_status()
traceback.print_exc()
else:
break
time.sleep(2)
try:
submission = json.loads(ret)
except Exception as e:
submission = None
return False
else:
return True
# judge client
def judger_loop():
ok = False
while True:
fetch_new = True
if ok and not (taskQ.empty() and socket_server_thread.isAlive()):
fetch_new = False
if not ok:
while True:
if not taskQ.empty():
handle_task()
if not socket_server_thread.isAlive():
raise Exception('socket server exited unexpectedly')
if send_and_fetch():
break
print('['+time.asctime()+']', 'Nothing to judge...')
time.sleep(2)
ok = True
print_judge_client_status()
print('judging', file=sys.stderr)
try:
res = judge()
except Exception:
print_judge_client_status()
traceback.print_exc()
res = False
ok = send_and_fetch(result=res,fetch_new=fetch_new)
# main function
def main():
init()
if len(sys.argv) == 1:
start_judger_server()
if len(sys.argv) == 2:
if sys.argv[1] == 'start':
pid = os.fork()
if pid == -1:
raise Exception('fork failed')
elif pid > 0:
return
else:
freopen(sys.stdout, open(os.devnull, 'wb'))
freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0))
start_judger_server()
elif sys.argv[1] == 'update':
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.connect(('127.0.0.1', jconf['socket_port']))
s.sendall(json.dumps({
'password': jconf['socket_password'],
'cmd': 'update'
}).encode())
return
except Exception:
traceback.print_exc()
raise Exception('update failed')
elif sys.argv[1] == 'stop':
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.connect(('127.0.0.1', jconf['socket_port']))
s.sendall(json.dumps({
'password': jconf['socket_password'],
'cmd': 'stop'
}).encode())
if s.recv(10).decode() != 'ok':
raise Exception('stop failed')
return
except Exception:
traceback.print_exc()
raise Exception('stop failed')
raise Exception('invalid argument')
init()
logging.basicConfig(filename='log/judge.log', level=logging.INFO,
format='%(asctime)-15s [%(levelname)s]: %(message)s')
if len(sys.argv) == 1:
JudgerServer().run()
if len(sys.argv) == 2:
if sys.argv[1] == 'start':
pid = os.fork()
if pid == -1:
raise Exception('fork failed')
elif pid > 0:
return
else:
freopen(sys.stdout, open(os.devnull, 'w'))
freopen(sys.stderr, open('log/judge.log', 'a', encoding='utf-8', errors='uoj_text_replace'))
JudgerServer().run()
elif sys.argv[1] in ['update', 'self-update']:
try:
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.connect(('127.0.0.1', jconf['socket_port']))
s.sendall(json.dumps({
'password': jconf['socket_password'],
'cmd': sys.argv[1]
}).encode())
return
except OSError:
JudgerServer.update(broadcast=sys.argv[1] == 'update')
return
except Exception:
logging.exception('update error')
raise Exception('update failed')
elif sys.argv[1] == 'stop':
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.connect(('127.0.0.1', jconf['socket_port']))
s.sendall(json.dumps({
'password': jconf['socket_password'],
'cmd': 'stop'
}).encode())
if s.recv(10) != b'ok':
raise Exception('stop failed')
return
except Exception:
logging.exception('stop error')
raise Exception('stop failed')
raise Exception('invalid argument')
try:
main()
main()
except Exception:
print_judge_client_status()
traceback.print_exc()
sys.exit(1)
logging.exception('critical error!')
sys.exit(1)

View File

@ -1,5 +1,5 @@
INCLUDE_PATH = include
CXXFLAGS = -I./include -O2
CXXFLAGS = -I./include -O2 --std=c++17 -Wall -lstdc++fs
EXE_CHECKER = \
builtin/checker/bcmp \
@ -26,24 +26,35 @@ EXE = main_judger \
run/formatter \
run/run_program \
run/run_interaction \
builtin/judger/judger
run/compile \
builtin/judger/judger \
$(EXE_CHECKER)
all: $(EXE) $(EXE_CHECKER)
runner: $(EXE)
checker: $(EXE_CHECKER)
OBJ = tests/catch_amalgamated.o tests/test.o
all: $(EXE)
% : %.cpp
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@
run/run_program: include/uoj_env.h run/run_program_conf.h
run/formatter : include/testlib.h
run/run_interaction: run/run_interaction.cpp include/uoj_env.h
$(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@
run/formatter : include/testlib.h
run/compile : include/uoj_run.h
builtin/judger/judger: include
main_judger: include
run/run_program : run/run_program.cpp run/run_program_conf.h run/run_program_sandbox.h include/uoj_run.h
$(CXX) $(CXXFLAGS) $< -o $@ -lseccomp -pthread
run/run_interaction : run/run_interaction.cpp include/uoj_run.h
$(CXX) $(CXXFLAGS) $< -o $@ -pthread
builtin/judger/judger: include/*.h
main_judger: include/*.h
tests/test.o: include/*.h
tests/test: tests/catch_amalgamated.o tests/test.o
$(CXX) $(CXXFLAGS) $^ -o $@ -pthread
$(EXE_CHECKER): include/testlib.h
clean:
rm -f $(EXE) $(EXE_CHECKER)
rm -f $(EXE) $(OBJ)

View File

@ -1,150 +1,51 @@
#include "uoj_judger.h"
struct SubtaskInfo {
bool passed;
int score;
PointInfo submit_answer_test_point_with_auto_generation(int i, TestPointConfig tpc = TestPointConfig()) {
static set<string> compiled;
static set<int> generated;
SubtaskInfo() {
int output_file_id = conf_int("output_file_id", i, i);
tpc.submit_answer = true;
tpc.output_file_name = work_path + "/" + conf_output_file_name(conf_int("output_file_id", i, i));
string gen_name = conf_str("output_gen", output_file_id, "");
if (gen_name.empty()) {
generated.insert(output_file_id);
}
if (generated.count(output_file_id)) {
return test_point("", i, tpc);
}
SubtaskInfo(const bool &_p, const int &_s)
: passed(_p), score(_s){}
};
void ordinary_test() {
int n = conf_int("n_tests", 10);
int m = conf_int("n_ex_tests", 0);
int nT = conf_int("n_subtasks", 0);
tpc.limit = conf_run_limit("gen", output_file_id, RL_GENERATOR_DEFAULT);
generated.insert(output_file_id);
if (!conf_is("submit_answer", "on")) {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (!compiled.count(gen_name)) {
report_judge_status_f("Compiling %s", gen_name.c_str());
if (auto c_ret = compile_submission_program(gen_name); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
compiled.insert(gen_name);
}
bool passed = true;
if (nT == 0) {
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
PointInfo po = test_point("answer", i);
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
add_point_info(po);
}
} else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") {
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
PointInfo po = test_point("answer", i);
if (po.scr != 100) {
passed = false;
po.scr = i == 1 ? 0 : -100;
add_point_info(po);
break;
} else {
po.scr = i == 1 ? 100 : 0;
add_point_info(po);
}
}
tpc.submit_answer = false;
return test_point(gen_name, i, tpc);
}
void ordinary_test() {
if (conf_is("submit_answer", "on")) {
PrimaryDataTestConfig pdtc;
pdtc.disable_ex_tests = true;
primary_data_test([](int i) {
return submit_answer_test_point_with_auto_generation(i);
}, pdtc);
} else {
map<int, SubtaskInfo> subtasks;
map<int,int> minScore;
for (int t = 1; t <= nT; t++) {
string subtaskType = conf_str("subtask_type", t, "packed");
int startI = conf_int("subtask_end", t - 1, 0) + 1;
int endI = conf_int("subtask_end", t, 0);
vector<PointInfo> points;
minScore[t] = 100;
vector<int> dependences;
if (conf_str("subtask_dependence", t, "none") == "many") {
string cur = "subtask_dependence_" + vtos(t);
int p = 1;
while (conf_int(cur, p, 0) != 0) {
dependences.push_back(conf_int(cur, p, 0));
p++;
}
} else if (conf_int("subtask_dependence", t, 0) != 0) {
dependences.push_back(conf_int("subtask_dependence", t, 0));
}
bool skipped = false;
for (vector<int>::iterator it = dependences.begin(); it != dependences.end(); it++) {
if (subtaskType == "packed") {
if (!subtasks[*it].passed) {
skipped = true;
break;
}
} else if (subtaskType == "min") {
minScore[t] = min(minScore[t], minScore[*it]);
}
}
if (skipped) {
add_subtask_info(t, 0, "Skipped", points);
continue;
}
int tfull = conf_int("subtask_score", t, 100 / nT);
int tscore = scale_score(minScore[t], tfull);
string info = "Accepted";
for (int i = startI; i <= endI; i++) {
report_judge_status_f("Judging Test #%d of Subtask #%d", i, t);
PointInfo po = test_point("answer", i);
if (subtaskType == "packed") {
if (po.scr != 100) {
passed = false;
po.scr = i == startI ? 0 : -tfull;
tscore = 0;
points.push_back(po);
info = po.info;
break;
} else {
po.scr = i == startI ? tfull : 0;
tscore = tfull;
points.push_back(po);
}
} else if (subtaskType == "min") {
minScore[t] = min(minScore[t], po.scr);
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, tfull);
if (po.scr <= tscore) {
tscore = po.scr;
points.push_back(po);
info = po.info;
} else {
points.push_back(po);
}
}
}
subtasks[t] = SubtaskInfo(info == "Accepted", tscore);
add_subtask_info(t, tscore, info, points);
report_judge_status_f("Compiling");
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
}
if (conf_is("submit_answer", "on") || !passed) {
end_judge_ok();
}
tot_score = 100;
for (int i = 1; i <= m; i++) {
report_judge_status_f("Judging Extra Test #%d", i);
PointInfo po = test_point("answer", -i);
if (po.scr != 100) {
po.num = -1;
po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
po.scr = -3;
add_point_info(po);
end_judge_ok();
}
}
if (m != 0) {
PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
add_point_info(po);
primary_data_test([](int i) {
return test_point("answer", i);
});
}
end_judge_ok();
}
@ -153,8 +54,7 @@ void hack_test() {
if (conf_is("submit_answer", "on")) {
end_judge_judgement_failed("Hack is not supported in this problem.");
} else {
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
TestPointConfig tpc;
@ -170,80 +70,52 @@ void hack_test() {
void sample_test() {
if (conf_is("submit_answer", "on")) {
int n = conf_int("n_tests", 10);
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
if (conf_is("check_existence_only_in_sample_test", "on")) {
if (conf_is("check_existence_only_in_sample_test", "on")) {
main_data_test([](int i) {
TestPointConfig tpc = TestPointConfig();
tpc.auto_complete(i);
string usrout = file_preview(tpc.output_file_name);
if (usrout == "") {
add_point_info(PointInfo(i, 0, -1, -1,
"default",
file_preview(tpc.input_file_name), usrout,
"wrong answer empty file\n"));
} else {
PointInfo po = PointInfo(i, 100, -1, -1,
"default",
file_preview(tpc.input_file_name), usrout,
"ok nonempty file\n");
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
add_point_info(po);
}
} else {
PointInfo po = test_point("answer", i);
tpc.checker = "nonempty";
return submit_answer_test_point_with_auto_generation(i, tpc);
});
} else {
main_data_test([](int i) {
PointInfo po = submit_answer_test_point_with_auto_generation(i);
if (po.scr != 0) {
po.info = "Accepted";
po.scr = 100;
}
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
po.res = "no comment";
add_point_info(po);
}
return po;
});
}
end_judge_ok();
} else {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
int n = conf_int("n_sample_tests", 0);
bool passed = true;
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Sample Test #%d", i);
PointInfo po = test_point("answer", -i);
po.num = i;
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, 100 / n);
add_point_info(po);
}
if (passed) {
tot_score = 100;
}
sample_data_test([](int i) {
return test_point("answer", i);
});
end_judge_ok();
}
}
void custom_test() {
if (conf_is("submit_answer", "on")) {
end_judge_judgement_failed("Custom test is not supported in this problem.");
} else {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
report_judge_status_f("Judging");
add_custom_test_info(ordinary_custom_test("answer"));
end_judge_ok();
report_judge_status_f("Compiling");
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
CustomTestConfig ctc;
if (conf_is("submit_answer", "on")) {
ctc.base_limit = conf_run_limit("gen", 0, RL_GENERATOR_DEFAULT);
}
report_judge_status_f("Judging");
add_custom_test_info(ordinary_custom_test("answer", ctc));
end_judge_ok();
}
int main(int argc, char **argv) {

View File

@ -1,38 +0,0 @@
#include "uoj_work_path.h"
#define UOJ_DATA_PATH UOJ_WORK_PATH "/data"
#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result"
#define RS_SPJ_BASE 1000
#define failed_spj RS_SPJ_BASE
#define successed_hack 100000
#define RS_SPJ RS_SPJ_BASE
#define RS_HACK successed_hack
#define RS_AC 0
#define RS_WA 1
#define RS_RE 2
#define RS_MLE 3
#define RS_TLE 4
#define RS_OLE 5
#define RS_DGS 6
#define RS_JGF 7
#define RS_SPJ_AC (RS_SPJ + RS_AC)
#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE)
#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE)
#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE)
#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE)
#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS)
#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF)
#define RS_HK_RE (successed_hack + RS_RE)
#define RS_HK_MLE (successed_hack + RS_MLE)
#define RS_HK_TLE (successed_hack + RS_TLE)
#define RS_HK_OLE (successed_hack + RS_OLE)
#define RS_HK_DGS (successed_hack + RS_DGS)
#define RS_HK_JGF (successed_hack + RS_JGF)
#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE)
#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE)
#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE)
#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE)
#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS)
#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,438 @@
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <fstream>
#include <cstdarg>
#include <filesystem>
#include <exception>
#include <stdexcept>
#define UOJ_GCC "/usr/bin/gcc-11"
#define UOJ_GPLUSPLUS "/usr/bin/g++-11"
#define UOJ_PYTHON2_7 "/usr/bin/python2.7"
#define UOJ_PYTHON3 "/usr/bin/python3.10"
#define UOJ_FPC "/usr/bin/fpc"
#define UOJ_OPEN_JDK8 "/usr/lib/jvm/java-8-openjdk-amd64"
#define UOJ_OPEN_JDK11 "/usr/lib/jvm/java-11-openjdk-amd64"
#define UOJ_OPEN_JDK17 "/usr/lib/jvm/java-17-openjdk-amd64"
std::string escapeshellarg(int arg) {
return std::to_string(arg);
}
std::string escapeshellarg(const std::string &arg) {
std::string res = "'";
for (char c : arg) {
if (c == '\'') {
res += "'\\''";
} else {
res += c;
}
}
res += "'";
return res;
}
template <typename T>
std::ostream& spaced_out(std::ostream &out, const T &arg) {
return out << arg;
}
template <typename T, typename... Args>
std::ostream& spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
return spaced_out(out << arg << " ", rest...);
}
template <typename T>
std::ostream& add_spaced_out(std::ostream &out, const T &arg) {
return out << " " << arg;
}
template <typename T, typename... Args>
std::ostream& add_spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
return spaced_out(out << " " << arg, rest...);
}
template <typename... Args>
int execute(const Args& ...args) {
std::ostringstream sout;
spaced_out(sout, args...);
#ifdef UOJ_SHOW_EVERY_CMD
std::cerr << sout.str() << std::endl;
#endif
int status = system(sout.str().c_str());
if (status == -1 || !WIFEXITED(status)) {
return -1;
}
return WEXITSTATUS(status);
}
int executef(const char *fmt, ...) {
const int L = 1 << 10;
char cmd[L];
va_list ap;
va_start(ap, fmt);
int res = vsnprintf(cmd, L, fmt, ap);
if (res < 0 || res >= L) {
return -1;
}
res = execute(cmd);
va_end(ap);
return res;
}
class cannot_determine_class_name_error : std::invalid_argument {
public:
explicit cannot_determine_class_name_error()
: std::invalid_argument("cannot determine the class name!") {}
};
std::string get_class_name_from_file(const std::string &fname) {
std::ifstream fin(fname);
if (!fin) {
throw cannot_determine_class_name_error();
}
std::string class_name;
if (!(fin >> class_name)) {
throw cannot_determine_class_name_error();
}
if (class_name.length() > 100) {
throw cannot_determine_class_name_error();
}
for (char &c : class_name) {
if (!isalnum(c) && c != '_') {
throw cannot_determine_class_name_error();
}
}
return class_name;
}
bool put_class_name_to_file(const std::string &fname, const std::string &class_name) {
std::ofstream fout(fname);
if (!fout) {
return false;
}
if (!(fout << class_name << std::endl)) {
return false;
}
return true;
}
std::map<std::string, std::string> lang_upgrade_map = {
{"Java7" , "Java8" },
{"Java14", "Java17" },
};
std::string upgraded_lang(const std::string &lang) {
return lang_upgrade_map.count(lang) ? lang_upgrade_map[lang] : lang;
}
namespace runp {
namespace fs = std::filesystem;
fs::path run_path;
struct limits_t {
int time;
int memory;
int output;
int real_time;
int stack;
limits_t() = default;
limits_t(const int &_time, const int &_memory, const int &_output)
: time(_time), memory(_memory), output(_output), real_time(-1), stack(-1) {
}
};
// result type
enum RS_TYPE {
RS_AC = 0,
RS_WA = 1,
RS_RE = 2,
RS_MLE = 3,
RS_TLE = 4,
RS_OLE = 5,
RS_DGS = 6,
RS_JGF = 7
};
inline std::string rstype_str(RS_TYPE id) {
switch (id) {
case RS_AC: return "Accepted";
case RS_WA: return "Wrong Answer";
case RS_RE : return "Runtime Error";
case RS_MLE: return "Memory Limit Exceeded";
case RS_TLE: return "Time Limit Exceeded";
case RS_OLE: return "Output Limit Exceeded";
case RS_DGS: return "Dangerous Syscalls";
case RS_JGF: return "Judgment Failed";
default : return "Unknown Result";
}
}
inline std::string get_type_from_lang(std::string lang) {
lang = upgraded_lang(lang);
if (lang == "Python2.7") {
return "python2.7";
} else if (lang == "Python3") {
return "python3";
} else if (lang == "Java8") {
return "java8";
} else if (lang == "Java11") {
return "java11";
} else if (lang ==