Define a runner timeout
continuous-integration/drone/push Build is passing Details

pull/3/head
Clément FRÉVILLE 2 years ago
parent c5913c7cc9
commit 4ec0b7f710

@ -4,7 +4,6 @@
#include <filesystem>
#include <iostream>
#include <spawn.h>
#include <string_view>
#include <toml++/toml.h>
#include <unistd.h>
#include <wait.h>
@ -22,7 +21,6 @@ sk::runner_backend detect_backend() {
}
int status = 0;
waitpid(pid, &status, 0);
std::cout << status << std::endl;
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
std::cerr << "Using Docker" << std::endl;
return sk::runner_backend::Docker;
@ -58,8 +56,9 @@ int main(int argc, char **argv) {
std::stringstream buffer;
buffer << t.rdbuf();
std::string code = buffer.str();
sk::program program{code, "ghcr.io/moshell-lang/moshell"};
sk::program program{"sample-code", code, "ghcr.io/moshell-lang/moshell:master"};
sk::run_result result = runner.run_blocking(program);
std::cout << "exited with: " << result.exit_code << "\n";
std::cout << "out: " << result.out << "\n";
std::cout << "err: " << result.err << "\n";
return 0;
@ -79,7 +78,7 @@ int main(int argc, char **argv) {
break;
}
std::string_view jobId(static_cast<char *>(request.data()), JOB_ID_LEN);
std::string jobId(static_cast<char *>(request.data()), JOB_ID_LEN);
uint32_t imageLen = sk::read_uint32(static_cast<char *>(request.data()) + JOB_ID_LEN);
uint32_t codeLen = sk::read_uint32(static_cast<char *>(request.data()) + JOB_ID_LEN + sizeof(uint32_t));
@ -91,7 +90,7 @@ int main(int argc, char **argv) {
std::string requestString(static_cast<char *>(request.data()) + MIN_MESSAGE_LEN + imageLen, codeLen);
std::cout << "Executing " << codeLen << " bytes code.\n";
sk::program program{requestString, imageString};
sk::program program{jobId, requestString, imageString};
sk::run_result result = runner.run_blocking(program);
std::cout << "Result: " << result.out << std::endl;

@ -5,6 +5,7 @@
namespace sk {
struct program {
std::string name;
std::string code;
std::string image;
};

@ -5,14 +5,25 @@
#include <fcntl.h>
#include <poll.h>
#include <spawn.h>
#include <sys/timerfd.h>
#include <system_error>
#include <unistd.h>
#include <wait.h>
static constexpr int TIMEOUT_SECONDS = 2;
// Define a helper to throw a system error if a syscall fails
static auto ensure = [](int res) -> void {
if (res == -1) {
throw std::system_error{errno, std::generic_category()};
}
};
namespace sk {
runner::runner(runner_backend backend) : backend{backend} {}
run_result runner::run_blocking(const program &program) {
// Open file descriptors ahead of time
int in_pipe[2];
int out_pipe[2];
int err_pipe[2];
@ -20,6 +31,17 @@ run_result runner::run_blocking(const program &program) {
throw std::system_error{errno, std::generic_category()};
}
// Create a timer that will be polled when the program runs too long
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timerfd == -1) {
throw std::system_error{errno, std::generic_category()};
}
itimerspec timer{};
timer.it_value.tv_sec = TIMEOUT_SECONDS;
if (timerfd_settime(timerfd, 0, &timer, nullptr) == -1) {
throw std::system_error{errno, std::generic_category()};
}
// Avoid blocking on stdin, and being interrupted if the pipe is closed prematurely
int flags = fcntl(in_pipe[1], F_GETFL, 0);
fcntl(in_pipe[1], F_SETFL, flags | O_NONBLOCK);
@ -29,19 +51,21 @@ run_result runner::run_blocking(const program &program) {
posix_spawn_file_actions_addclose(&actions, in_pipe[1]);
posix_spawn_file_actions_addclose(&actions, out_pipe[0]);
posix_spawn_file_actions_addclose(&actions, err_pipe[0]);
posix_spawn_file_actions_addclose(&actions, timerfd);
posix_spawn_file_actions_adddup2(&actions, in_pipe[0], STDIN_FILENO);
posix_spawn_file_actions_adddup2(&actions, out_pipe[1], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&actions, err_pipe[1], STDERR_FILENO);
posix_spawn_file_actions_addclose(&actions, in_pipe[0]);
posix_spawn_file_actions_addclose(&actions, out_pipe[1]);
posix_spawn_file_actions_addclose(&actions, err_pipe[1]);
const char *const docker_args[] = {"docker", "run", "--rm", "-i", "--pull=never", "--cap-drop=ALL", "--network=none", "--memory=64m", "--memory-swap=64m", "--pids-limit=128", program.image.c_str(), nullptr};
const char *const bwrap_args[] = {"bwrap", "--ro-bind", "/usr", "/usr", "--dir", "/tmp", "--dir", "/var", "--proc", "/proc", "--dev", "/dev", "--symlink", "usr/lib", "/lib", "--symlink", "usr/lib64", "/lib64", "--symlink", "usr/bin", "/bin", "--symlink", "usr/sbin", "/sbin", "--unshare-all", "/bin/sh", nullptr};
const char *const docker_args[] = {"docker", "run", "--rm", "-i", "--name", program.name.c_str(), "--pull=never", "--cap-drop=ALL", "--network=none", "--memory=64m", "--memory-swap=64m", "--pids-limit=128", program.image.c_str(), nullptr};
const char *const bwrap_args[] = {"bwrap", "--ro-bind", "/usr", "/usr", "--dir", "/tmp", "--dir", "/var", "--proc", "/proc", "--dev", "/dev", "--symlink", "usr/lib", "/lib", "--symlink", "usr/lib64", "/lib64", "--symlink", "usr/bin", "/bin", "--symlink", "usr/sbin", "/sbin", "--unshare-all", "--die-with-parent", "/bin/sh", nullptr};
const char *const *args = docker_args;
if (backend == runner_backend::BubbleWrap) {
args = bwrap_args;
}
pid_t pid;
bool killed = false;
int exit_code;
if (posix_spawnp(&pid, args[0], &actions, nullptr, const_cast<char *const *>(args), nullptr) != 0) {
throw std::system_error{errno, std::generic_category()};
@ -58,7 +82,7 @@ run_result runner::run_blocking(const program &program) {
std::array<char, 1024> buffer{};
std::string out;
std::string err;
std::array<pollfd, 3> plist = {pollfd{in_pipe[1], POLLOUT | POLLHUP, 0}, pollfd{out_pipe[0], POLLIN, 0}, pollfd{err_pipe[0], POLLIN, 0}};
std::array<pollfd, 4> plist = {pollfd{in_pipe[1], POLLOUT | POLLHUP, 0}, pollfd{out_pipe[0], POLLIN, 0}, pollfd{err_pipe[0], POLLIN, 0}, pollfd{timerfd, POLLIN, 0}};
pollfd *pfds = plist.data();
nfds_t nfds = plist.size();
@ -94,8 +118,8 @@ run_result runner::run_blocking(const program &program) {
}
}
// Poll stdout and stderr
for (nfds_t i = nfds - 2; i < nfds; ++i) {
// Poll stdout, stderr and the timer
for (nfds_t i = nfds - 3; i < nfds; ++i) {
if (pfds[i].revents & POLLIN) {
ssize_t bytes_read = read(pfds[i].fd, buffer.data(), buffer.size());
if (bytes_read == -1) {
@ -103,6 +127,15 @@ run_result runner::run_blocking(const program &program) {
}
if (pfds[i].fd == out_pipe[0]) {
out.append(buffer.data(), bytes_read);
} else if (pfds[i].fd == timerfd) {
if (backend == runner_backend::Docker) {
const char *const kill_args[] = {"docker", "kill", program.name.c_str(), nullptr};
pid_t kill_pid;
ensure(posix_spawnp(&kill_pid, kill_args[0], nullptr, nullptr, const_cast<char *const *>(kill_args), nullptr));
} else {
ensure(kill(pid, SIGINT));
}
killed = true;
} else {
err.append(buffer.data(), bytes_read);
}
@ -119,8 +152,9 @@ run_result runner::run_blocking(const program &program) {
waitpid(pid, &exit_code, 0);
close(out_pipe[0]);
close(err_pipe[0]);
close(timerfd);
posix_spawn_file_actions_destroy(&actions);
return run_result{out, err};
return run_result{out, err, killed ? 124 : exit_code};
}
}

@ -7,6 +7,7 @@ namespace sk {
struct [[nodiscard]] run_result {
std::string out;
std::string err;
int exit_code;
};
enum class runner_backend { BubbleWrap, Docker };

Loading…
Cancel
Save