Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1c2cedf
Phase 1: unify watcher as the sole execution backend
ZnPdCo Jul 5, 2026
adfa345
fix: check time/memory limits before exit code in watcher parent
ZnPdCo Jul 5, 2026
0d2a4cd
fix: decode propagated signal from exit code instead of relying on wa…
ZnPdCo Jul 5, 2026
4de6ef9
fix(watcher): bwarp self before run
ZnPdCo Jul 13, 2026
8ed4c65
style: format codes
actions-user Jul 13, 2026
6c6f830
fix typo
ZnPdCo Jul 13, 2026
fa0228b
feat: add process limit
ZnPdCo Jul 13, 2026
541eb5f
upd
ZnPdCo Jul 13, 2026
1f2a6a4
upd
ZnPdCo Jul 13, 2026
84d139b
use exit
ZnPdCo Jul 13, 2026
c56bd3b
feat: use thread
ZnPdCo Jul 13, 2026
7da06b2
install bwrap
ZnPdCo Jul 13, 2026
d9227bd
style: format codes
actions-user Jul 13, 2026
e7fad03
fix
ZnPdCo Jul 13, 2026
8c42e30
Merge branch 'phase1-watcher-refactor' of https://github.com/ZnPdCo/P…
ZnPdCo Jul 13, 2026
19a089f
upd
ZnPdCo Jul 13, 2026
cfbc54f
fix cmakelist
ZnPdCo Jul 13, 2026
e624635
update cmakelist
ZnPdCo Jul 13, 2026
ee1e78c
use pidfd and timerfd
ZnPdCo Jul 14, 2026
91fe736
fix ci
ZnPdCo Jul 14, 2026
0b32245
make diff beautiful
ZnPdCo Jul 14, 2026
7aa8999
use kqueue for macos
ZnPdCo Jul 15, 2026
d40b040
add timer in qt side
ZnPdCo Jul 15, 2026
57e8334
style: format codes
actions-user Jul 15, 2026
b75771a
fix
ZnPdCo Jul 15, 2026
ca22673
fix macos
ZnPdCo Jul 15, 2026
92af944
add sleep tle test
ZnPdCo Jul 16, 2026
48790e2
use {} to init
ZnPdCo Jul 16, 2026
a31fb80
add 1s to cpu time limit
ZnPdCo Jul 23, 2026
a3b61ae
remove hard limit
ZnPdCo Jul 23, 2026
37daeea
Remove CPU time limit
ZnPdCo Jul 23, 2026
a78b467
remove obsolete OS
ZnPdCo Jul 24, 2026
08a4a18
remove nproc
ZnPdCo Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/watcher_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
LEMONLIME_SANDBOXED: 1

jobs:
build:
Expand Down
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,9 @@ A tiny judging environment for OI contest based on Lemon + LemonPlus
| Arch | 2021-07-11 | amd64 | KDE-Xorg |
| Arch | 2020-10-31 | amd64 | KDE-Wayland |
| Ubuntu | 20.04 | amd64 | GNOME 3 |
| Ubuntu | 18.04.4 | amd64 | GNOME 3 |
| NOI Linux 2(Ubuntu) | 20.04 | amd64 | GNOME 3 |
| Linux Mint | 19.3 | amd64 | Cinnamon |
| Deepin | 15.11 | amd64 | DDE |
| Deepin | 20 (1000) | amd64 | DDE |
| Debian | 10.3.0 | amd64 | LXQt; KDE-Xorg |
| Fedora | 31-1.9 | amd64 | XFCE |
| openSUSE | Leap 15.1 | amd64 | iceWM |
| openSUSE | Tumbleweed | amd64 | KDE-Xorg |
| macOS | 15.2 Beta | arm64 | Aqua |

Expand Down
93 changes: 4 additions & 89 deletions src/core/processrunner_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QStandardPaths>
#include <QTextStream>
#include <QThread>
#include <QUuid>
Expand All @@ -29,10 +28,7 @@
ProcessRunnerResult UnixProcessRunner::run() {
ProcessRunnerResult res;
res.result = CorrectAnswer;
int extraTime = qCeil(qMax(2000, config.timeLimit * 2) * config.extraTimeRatio);

#ifdef Q_OS_LINUX
// TODO: rewrite with cgroup
QFile watcher(config.workingDirectory + QUuid::createUuid().toString(QUuid::Id128));

if (config.interpreterAsWatcher) {
Expand All @@ -45,28 +41,6 @@ ProcessRunnerResult UnixProcessRunner::run() {
auto *runner = new QProcess();
QStringList argumentsList;

argumentsList << "--dev" << "/dev";
argumentsList << "--proc" << "/proc";
argumentsList << "--ro-bind" << "/usr" << "/usr";
argumentsList << "--symlink" << "/usr/lib" << "/lib";
argumentsList << "--symlink" << "/usr/lib64" << "/lib64";
argumentsList << "--symlink" << "/usr/bin" << "/bin";
argumentsList << "--symlink" << "/usr/sbin" << "/sbin";
argumentsList << "--tmpfs" << "/tmp";

argumentsList << "--unshare-all" << "--die-with-parent";

argumentsList << "--chdir" << config.workingDirectory;

argumentsList << "--bind" << config.workingDirectory << config.workingDirectory;

if (config.standardInputCheck) {
argumentsList << "--ro-bind" << QFileInfo(config.inputFile).absoluteFilePath()
<< QFileInfo(config.inputFile).absoluteFilePath();
}

argumentsList << watcher.fileName();

argumentsList << config.executableFile;
argumentsList << config.arguments;

Expand Down Expand Up @@ -100,76 +74,17 @@ ProcessRunnerResult UnixProcessRunner::run() {
argumentsList << config.outputFileName;
}

qDebug() << argumentsList;
argumentsList << config.workingDirectory;

QString bwrapPath = QStandardPaths::findExecutable("bwrap");
if (bwrapPath.isEmpty()) {
res.score = 0;
res.result = CannotStartProgram;
res.message = QObject::tr("bwrap not found. Please install bubblewrap.");
delete runner;
return res;
}

runner->setProcessEnvironment(config.environment);
runner->setWorkingDirectory(config.workingDirectory);
runner->start(bwrapPath, argumentsList);

#else

QFile watcher(config.workingDirectory + QUuid::createUuid().toString(QUuid::Id128));

if (config.interpreterAsWatcher) {
QFile::copy(config.executableFile, watcher.fileName());
} else {
QFile::copy(":/watcher/watcher_unix", watcher.fileName());
}

watcher.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
auto *runner = new QProcess();
QStringList argumentsList;

argumentsList << config.executableFile;
argumentsList << config.arguments;

if (config.standardInputCheck) {
argumentsList << QFileInfo(config.inputFile).absoluteFilePath();
} else {
argumentsList << "";
}

if (config.standardOutputCheck) {
argumentsList << "_tmpout";
} else {
argumentsList << "";
}

argumentsList << "_tmperr";
argumentsList << QString("%1").arg(config.timeLimit);
argumentsList << QString("%1").arg(config.memoryLimit);
argumentsList << QString("%1").arg(config.rawTimeLimit);
argumentsList << QString("%1").arg(config.rawMemoryLimit);

if (config.standardInputCheck) {
argumentsList << "";
} else {
argumentsList << config.inputFileName;
}

if (config.standardOutputCheck) {
argumentsList << "";
} else {
argumentsList << config.outputFileName;
}
int extraTimeMs = qCeil(qMax(2000, config.timeLimit * 2) * config.extraTimeRatio);
argumentsList << QString("%1").arg(extraTimeMs);

qDebug() << argumentsList;

runner->setProcessEnvironment(config.environment);
runner->setWorkingDirectory(config.workingDirectory);
runner->start(watcher.fileName(), argumentsList);

#endif

if (! runner->waitForStarted(-1)) {
delete runner;
res.score = 0;
Expand All @@ -184,7 +99,7 @@ ProcessRunnerResult UnixProcessRunner::run() {

// Using rlimit to limit CPU time can only be accurate to seconds,
// so here it is rounded up to an integer second.
long long killTimeLimit = (config.timeLimit + 999) / 1000 * 1000 + extraTime;
long long killTimeLimit = (config.timeLimit + 999) / 1000 * 1000 + extraTimeMs + 1000;
while (timer.elapsed() <= killTimeLimit) {
if (runner->waitForFinished(10)) {
isProgramFinishedInExtraTimeLimit = true;
Expand Down
2 changes: 2 additions & 0 deletions unix/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ file(COPY hello.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_executable(tle tle.c)
add_executable(add add.c)
add_executable(re re.c)
add_executable(sleep_tle sleep_tle.c)

file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/scripts DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

Expand All @@ -27,6 +28,7 @@ add_test(NAME watcher_run_sh_test COMMAND python3 scripts/run_sh.py)
add_test(NAME watcher_MLE_static_test COMMAND python3 scripts/mle_static.py)
add_test(NAME watcher_unlimit_memory_test COMMAND python3 scripts/unlimit.py)
add_test(NAME watcher_TLE_test COMMAND python3 scripts/tle.py)
add_test(NAME watcher_wall_clock_TLE_test COMMAND python3 scripts/sleep_tle.py)
add_test(NAME watcher_filename_with_space_test COMMAND python3 scripts/space.py)
add_test(NAME watcher_symlink_abs_test COMMAND python3 scripts/symlink_abs.py)
add_test(NAME watcher_symlink_rel_test COMMAND python3 scripts/symlink_rel.py)
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/mle_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pid = os.getpid()
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", "", tmperr, "1000", "380", "1000", "380", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", "", tmperr, "1000", "380", "1000", "380", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

stdout, _ = p.communicate()

Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
with open(tmpin, 'w') as f:
f.writelines(['1 1'])

p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False)

time.sleep(2)
p.kill()
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./hello", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/run_sh.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "/bin/sh", "hello.sh", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "/bin/sh", "hello.sh", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/runtimeerr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
pid = os.getpid()
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./re", "", "", "", tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./re", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False)

assert(p.wait() == 2)
15 changes: 15 additions & 0 deletions unix/test/scripts/sleep_tle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import subprocess
import time
import os

pid = os.getpid()
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./sleep_tle", "", "", "", tmperr,
"1000", "100", "1000", "100", "", "", ".", "0"],
shell=False)

time.sleep(3)
p.kill()

assert(p.returncode == 3)
2 changes: 1 addition & 1 deletion unix/test/scripts/space.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

shutil.copy("./hello", "./he llo")

p = subprocess.Popen(["./watcher_unix", "./he llo", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./he llo", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/symlink_abs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

os.symlink(os.path.join(os.getcwd(), "hello"), "hello_s_abs")

p = subprocess.Popen(["./watcher_unix", "./hello_s_abs", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello_s_abs", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/symlink_rel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

os.symlink("hello", "hello_s_rel")

p = subprocess.Popen(["./watcher_unix", "./hello_s_rel", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello_s_rel", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/tle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False)

time.sleep(5)
p.kill()
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/unlimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", tmpout, tmperr, "1000", "-1", "1000", "-1", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", tmpout, tmperr, "1000", "-1", "1000", "-1", "", "", ".", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
6 changes: 6 additions & 0 deletions unix/test/sleep_tle.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <unistd.h>

int main() {
sleep(10);
return 0;
}
Loading
Loading