From f357e83ea8beb5bb6f017358be3cbf13befdf923 Mon Sep 17 00:00:00 2001 From: Dmis Date: Sun, 20 Jun 2021 19:01:36 +0300 Subject: [PATCH] signal unsafety --- README.md | 1 + concurrency/signal_unsafe.md | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 concurrency/signal_unsafe.md diff --git a/README.md b/README.md index 6cd3453..4218a5b 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ 2. [shared_ptr](concurrency/shared_ptr.md) 3. [thread::join](concurrency/jthread.md) 4. [Повторный захват мьютекса](concurrency/double_lock.md) + 5. [Signal-unsafe](concurrency/signal_unsafe.md) --- diff --git a/concurrency/signal_unsafe.md b/concurrency/signal_unsafe.md new file mode 100644 index 0000000..7a78466 --- /dev/null +++ b/concurrency/signal_unsafe.md @@ -0,0 +1,46 @@ +# Сигнало(не)безопасность + +Разработчик любого сколько-нибудь серьезного приложения рано или поздно вынужден озаботиться +вопросами поведения программы в различных краевых и внештатных ситуациях: запрос досрочного завершения, внезапное закрытие терминала, обработка маловероятных ошибочных состояний. Во многих этих случаях приходится иметь дело с довольно примитивным механизмом межпроцессного взаимодействия — с обработкой сигналов. + +Программист регистрирует обработчики нужных ему сигналов и забот не знает, очень часть допуская серьезную ошибку — +выполняет в обработчике сигналов код, который там выполнять небезопасно: выделяет память, делает I/O, захватывает блокировки... + +Сигналы прерывают нормальный ход исполнения программы и могут быть обработаны в произвольном потоке. +Поток мог начать выделять память, захватить блокировку в аллокаторе и в этот момент быть прерванным сигналом. Если обработчик сигнала в свою очередь запросит выделение памяти... Будет повторный захват блокировки в одном и том же потоке. Неопределенное поведение. + +Очень легко можно продемонстрировать проблему на следующем примере +```C++ +std::mutex global_lock; + +int main() { + std::signal(SIGINT, [](int){ + std::scoped_lock lock {global_lock}; + printf("SIGINT!\n"); + }); + + { + std::scoped_lock lock {global_lock}; + printf("start long job\n"); + sleep(10); + printf("end long job\n"); + } + sleep(10); +} +``` + +Если мы скомпилируем эту программу под Linux (не забыв указать `-pthread`), запустим и нажмем `Ctrl+C`, то она зависнет навсегда из-за повторного захвата мьютекса одним и тем же потоком. Если же забудем `-pthread`, то не зависнет и отработает «ожидаемым» образом. + +Под Windows эта программа также работает «ожидаемо» из-за специфики обработки сигналов — там для обработки SIGINT/SIGTERM всегда неявно порождается новый поток. + +В любом случае этот код некорректен из-за использования сигналонебезопасной функции внутри обработчика сигналов. + +### Как бороться? + +1. Делать обработчики сигналов как можно более простыми +2. Сверяться с документацией, безопасно ли использование той или иной функции в контексте обработчика сигналов +3. Отключать автоматический прием сигналов и выполнять их обработку в рамках обычного исполнения программы (см., например, `sigprocmask` и `sigwait`) + +### Полезные ссылки +1. https://man7.org/linux/man-pages/man7/signal-safety.7.html +2. https://www.gnu.org/software/libc/manual/html_node/Blocking-Signals.html \ No newline at end of file