Skip to content

Commit

Permalink
Adding success_exit_codes task config parameter to allow also non-zer…
Browse files Browse the repository at this point in the history
…o exit codes (for external commands) to be treated as successful tasks.
  • Loading branch information
krulis-martin committed Jul 14, 2024
1 parent deb2189 commit 8b43daf
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 10 deletions.
28 changes: 26 additions & 2 deletions examples/job-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ tasks:
args:
- "/tmp/main.c"
- "/tmp/hello_from_codex_worker/main.c"
- task-id: "eval_test01"
- task-id: "compile_test01"
priority: 3
fatal-failure: false
dependencies:
Expand Down Expand Up @@ -54,9 +54,33 @@ tasks:
- src: /tmp/hello_from_codex_worker
dst: "evaluate"
mode: RW
- task-id: "remove_created_dir"
- task-id: "run_test01"
priority: 4
fatal-failure: false
dependencies:
- "compile_test01"
cmd:
bin: "./test"
args: []
success_exit_codes: # list of exit codes which will be accepted as success
- 0 # zero is success by default, but if success_exit_codes is present, it must be added explicitly.
- 1 # single code
- [100, 200] # an interval (inclusive)
# codes 0, 1, 100, 101, ... 199, and 200 will all be accepted
sandbox:
name: "isolate"
limits:
- hw-group-id: "group1" # determines specific limits for specific machines
time: 1 # seconds
wall-time: 1 # seconds
extra-time: 1 # seconds
parallel: 0 # time and memory limits are merged from all potential processes/threads
environ-variable:
ISOLATE_BOX: "/box"
PATH: "/usr/bin"
- task-id: "remove_created_dir"
priority: 5
fatal-failure: false
cmd:
bin: "rm"
args:
Expand Down
16 changes: 15 additions & 1 deletion src/config/task_metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class task_metadata
std::shared_ptr<sandbox_config> sandbox = nullptr,
std::string test_id = "")
: task_id(task_id), priority(priority), dependencies(deps), test_id(test_id), type(type), fatal_failure(fatal),
binary(cmd), cmd_args(args), sandbox(sandbox)
binary(cmd), cmd_args(args), success_exit_codes({true}), sandbox(sandbox)
{
}

Expand All @@ -59,6 +59,20 @@ class task_metadata
std::string binary;
/** Arguments for executed command. */
std::vector<std::string> cmd_args;
/**
* List of command exit codes which should be treated as "success" (denoted by true value at their index).
* By default, the list has only one item - true at index 0.
*/
std::vector<bool> success_exit_codes;

/**
* Safely checks whether given exit code is deemed successful (handling corner cases).
*/
bool is_success_exit_code(int code) const
{
// indices outside success_exit_codes range are handled as false
return code >= 0 && ((std::size_t) code) < success_exit_codes.size() && success_exit_codes[code];
}

/** If not null than this task is external and will be executed in given sandbox. */
std::shared_ptr<sandbox_config> sandbox;
Expand Down
48 changes: 48 additions & 0 deletions src/helpers/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,50 @@
#include "config.h"


/**
* Properly add an exit-code or an interval of exit-codes in the bitmap.
*/
void add_exit_codes(std::vector<bool> &success_exit_codes, int from_index, int to_index = -1)
{
if (to_index == -1) { to_index = from_index; }

if (from_index < 0 || to_index > 255 || from_index > to_index) { return; }

if (success_exit_codes.size() <= ((std::size_t) to_index)) { success_exit_codes.resize(to_index + 1); }

for (int i = from_index; i <= to_index; ++i) { success_exit_codes[i] = true; }
}


/**
* Process the config node with success exit codes and fill a bitmap with their enabled indices.
* The config must be a single int value or a list of values. In case of a list, each item should be either integer or a
* tuple of two integers representing from-to range (inclusive).
* @param node root of yaml sub-tree with the exit codes.
* @param success_exit_codes a bitmap being constructed
*/
void load_task_success_exit_codes(const YAML::Node &node, std::vector<bool> &success_exit_codes)
{
success_exit_codes.clear();
if (node.IsScalar()) {
add_exit_codes(success_exit_codes, node.as<int>());
} else if (node.IsSequence()) {
for (auto &subnode : node) {
if (subnode.IsScalar()) {
add_exit_codes(success_exit_codes, subnode.as<int>());
} else if (subnode.IsSequence() && subnode.size() == 2) {
add_exit_codes(success_exit_codes, subnode[0].as<int>(), subnode[1].as<int>());
} else {
throw helpers::config_exception(
"Success exit code must be a scalar (int) value or an interval (two integers in a list)");
}
}
} else {
throw helpers::config_exception("Task command success_exit_codes must be an integer or a list.");
}
}


std::shared_ptr<job_metadata> helpers::build_job_metadata(const YAML::Node &conf)
{
std::shared_ptr<job_metadata> job_meta = std::make_shared<job_metadata>();
Expand Down Expand Up @@ -80,6 +124,10 @@ std::shared_ptr<job_metadata> helpers::build_job_metadata(const YAML::Node &conf
if (ctask["cmd"]["args"] && ctask["cmd"]["args"].IsSequence()) {
task_meta->cmd_args = ctask["cmd"]["args"].as<std::vector<std::string>>();
} // can be omitted... no throw

if (ctask["cmd"]["success_exit_codes"]) {
load_task_success_exit_codes(ctask["cmd"]["success_exit_codes"], task_meta->success_exit_codes);
}
} else {
throw config_exception("Command in task is not a map");
}
Expand Down
31 changes: 24 additions & 7 deletions src/tasks/external_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ std::shared_ptr<task_results> external_task::run()
res->sandbox_status =
std::unique_ptr<sandbox_results>(new sandbox_results(sandbox_->run(task_meta_->binary, task_meta_->cmd_args)));

// fix status if non-zero exit codes are treated as execution success
postprocess_exit_codes(res);

// get output from stdout and stderr
get_results_output(res);

Expand All @@ -102,6 +105,21 @@ std::shared_ptr<task_results> external_task::run()
return res;
}

void external_task::postprocess_exit_codes(std::shared_ptr<task_results> result)
{
bool success = task_meta_->is_success_exit_code(result->sandbox_status->exitcode);
bool status_ok = result->sandbox_status->status == isolate_status::OK;
if (success && result->sandbox_status->status == isolate_status::RE) {
// we need to override the status to ok, based on acceptable exit-code
result->sandbox_status->status = isolate_status::OK;
result->sandbox_status->message = "";
} else if (!success && result->sandbox_status->status == isolate_status::OK) {
// this happens if zero is not a successfuly exit-code
result->sandbox_status->status = isolate_status::RE;
result->sandbox_status->message = "Exited with code 0, which is not considered a success (exit codes override)";
}
}

std::shared_ptr<sandbox_limits> external_task::get_limits()
{
return limits_;
Expand Down Expand Up @@ -168,14 +186,10 @@ void external_task::process_results_output(
std_err.read(&result_stderr[0], max_length);

// if there was something in stdout, write it to result
if (std_out.gcount() != 0) {
result->output_stdout = result_stdout.substr(0, std_out.gcount());
}
if (std_out.gcount() != 0) { result->output_stdout = result_stdout.substr(0, std_out.gcount()); }

// if there was something in stderr, write it to result
if (std_err.gcount() != 0) {
result->output_stderr = result_stderr.substr(0, std_err.gcount());
}
if (std_err.gcount() != 0) { result->output_stderr = result_stderr.substr(0, std_err.gcount()); }

// be nice and close streams
std_out.close();
Expand Down Expand Up @@ -227,7 +241,10 @@ void external_task::make_binary_executable(const std::string &binary)

// determine if file has executable bits set
fs::file_status stat = status(binary_path);
if ((stat.permissions() & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec)) != fs::perms::none) { return; }
if ((stat.permissions() & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec)) !=
fs::perms::none) {
return;
}

fs::permissions(
binary_path, fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec, fs::perm_options::add);
Expand Down
9 changes: 9 additions & 0 deletions src/tasks/external_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,26 @@ class external_task : public task_base
*/
void make_binary_executable(const std::string &binary);

/**
* A task may accept different exit codes (than 0) as success. However, sandbox results may indicated nonzero exit
* codes as failures. This method fixes such situations.
*/
void postprocess_exit_codes(std::shared_ptr<task_results> result);

/**
* Initialize output if requested.
*/
void results_output_init();

/**
* Get configuration limited content of the stdout and stderr and return it.
* @param result to which stdout and err will be assigned
*/
void get_results_output(std::shared_ptr<task_results> result);

void process_results_output(
const std::shared_ptr<task_results> &result, const fs::path &stdout_path, const fs::path &stderr_path);

void process_carboncopy_output(const fs::path &stdout_path, const fs::path &stderr_path);

/** Worker default configuration */
Expand Down

0 comments on commit 8b43daf

Please sign in to comment.