Изящная обработка сигналов в slarm

avatar
PKua
1 июля 2021 в 17:18
351
1
0

У меня проблема с изящным выходом из моих заданий slurm с сохранением данных и т.д.

В моей программе есть обработчик сигнала, который устанавливает флаг, который затем запрашивается в основном цикле, после чего следует изящный выход с сохранением данных. Общая схема примерно такая:

#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

Из-за этого в пакетном скрипте такая каша (2 узла на 4 процесса):

#!/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
...

и, что еще хуже, примерно 4-6 из более чем 300 процессов на самом деле терпят неудачу на if (!out) - errno выдает "Прерванный системный вызов". Опять же, руководствуясь этим, я предполагаю, что мой обработчик сигнала вызывается два раза - второй во время какого-то системного вызова в конструкторе std::ofstream.

Сейчас,

  1. Как избавиться от ошибок slurm и обеспечить корректный выход?
  2. Правильно ли я понимаю, что сигнал отправляется два раза? Если да, то почему и как это исправить?
Источник
KamilCuk
1 июля 2021 в 17:46
0

Сохраняет ли srun переменные среды?

PKua
1 июля 2021 в 18:04
0

@KamilCul Думаю, да, это сработало с OMP_NUM_THREADS

Ответы (1)

avatar
KamilCuk
1 июля 2021 в 17:54
0

Предложения:

  • ловушка ВЫХОД, а не сигнал. EXIT происходит один раз, TERM может быть доставлен несколько раз.
  • используйте declare -f для передачи кода и declare -p для передачи переменных в несвязанную подоболочку
  • 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 "%s\n" "$@" | 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 ... bash -c './my_program "$@" || exit 255' -- || exit 255, если вы хотите, чтобы xargs завершилась в случае сбоя любой из программ.

Если srun сохраняет переменные среды, то экспортируйте рабочую функцию export -f work и просто вызовите ее в дочерней оболочке, например xargs ... srun ... bash -c 'work "$@"' --.