我在保存数据等方面优雅地退出我的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
构造函数下的某个系统调用期间。
现在,
- 如何消除slum错误并有一个真正优雅的退出
- 我发两次信号是对的吗?如果是,为什么以及如何修复
建议:
- 陷阱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中调用它。