优雅的信号处理在侮辱



我在保存数据等方面优雅地退出我的slum作业时遇到问题。

我的程序中有一个信号处理程序,它设置了一个标志,然后在主循环中查询该标志,然后优雅地退出并保存数据。一般方案是这样的:

#include <utility>
#include <atomic>
#include <fstream>
#include <unistd.h>
namespace {
std::atomic<bool> sigint_received = false;
}
void sigint_handler(int) {
sigint_received = true;
}
int main() {
std::signal(SIGTERM, sigint_handler);
while(true) {
usleep(10);  // There are around 100 iterations per second
if (sigint_received)
break;
}
std::ofstream out("result.dat");
if (!out)
return 1;
out << "Here I save the data";
return 0;
}

批处理脚本非常复杂,因为:

  • 我想要数百个并行的、低线程数的独立任务,但我的集群每个用户只允许16个作业
  • 集群中的srun总是声明整个节点,即使我不想要所有核心,所以为了在单个节点上运行多个进程,我必须使用bash

因此,批处理脚本是如此混乱(4个进程有2个节点(:

#!/bin/bash -l
#SBATCH -N 2
#SBATCH more slurm stuff, such as --time, etc.
srun -N 1 -n 1 bash -c '
./my_program input1 &
./my_program input2 &
wait
' &
srun -N 1 -n 1 bash -c '
./my_program input3 &
./my_program input4 &
wait
' &
wait

现在,为了传播由slurm发送的信号,我有一个更大的混乱,比如(按照这个答案,特别是双重等待(:

#!/bin/bash -l
#SBATCH -N 2
#SBATCH more slurm stuff, such as --time, etc.
trap 'kill $(jobs -p) && wait' TERM
srun -N 1 -n 1 bash -c '
trap '"'"'kill $(jobs -p) && wait'"'"' TERM
./my_program input1 &
./my_program input2 &
wait
' &
srun -N 1 -n 1 bash -c '
trap '"'"'kill $(jobs -p) && wait'"'"' TERM
./my_program input3 &
./my_program input4 &
wait
' &
wait

在大多数情况下,它是有效的。但是,首先,我在输出的末尾收到错误消息:

run: error: nid00682: task 0: Exited with exit code 143
srun: Terminating job step 732774.7
srun: error: nid00541: task 0: Exited with exit code 143
srun: Terminating job step 732774.4
...

更糟糕的是,300多个进程中有4-6个在if (!out)-errno上实际失败;中断的系统调用";。同样,在这个指导下,我猜我的信号处理程序被调用了两次——第二次是在std::ofstream构造函数下的某个系统调用期间。

现在,

  1. 如何消除slum错误并有一个真正优雅的退出
  2. 我发两次信号是对的吗?如果是,为什么以及如何修复

建议:

  • 陷阱EXIT,而不是信号。EXIT发生一次,TERM可以多次交付
  • 使用declare -f传输代码,使用declare -p将变量传输到不相关的子shell
  • kill可能会失败,我认为你不应该在上面使用&&
  • 使用xargs(或parallel(,而不是使用kill $(jobs -p)重新设计车轮
  • 提取物";数据";(input1 input2 ...(;代码";(待完成的工作(

一些东西:

# The input.
input="$(cat <<'EOF'
input1
input2
input3
input4
EOF
)"
work() {
# Normally write work to be done.
# For each argument, run `my_program` in parallel.
printf "%sn" "$@" | xargs -d'n' -P0 ./my_program
}
# For each two arguments run `srun....` with a shell that runs `work` in parallel.
# Note - declare -f outputs source-able definition of the function.
# "No more hand escaping!"
# Then the work function is called with arguments passed by xargs inside the spawned shell.
xargs -P0 -n2 -d'n' <<<"$input" 
srun -N 1 -n 1 
bash -c "$(declare -f work)"'; work "$@"' --

-P0是GNU xargs特有的。GNU xargs专门处理退出状态255,如果您希望xargs在任何程序失败时终止,则可以编写类似xargs ... bash -c './my_program "$@" || exit 255' -- || exit 255的包装器。

如果srun保留了环境变量,那么导出工作函数export -f work,然后像xargs ... srun ... bash -c 'work "$@"' --一样在子shell中调用它。

最新更新