Что делает команда `kill` и какие сигналы важны для бэкенд-разработчика?

Ответ

Команда kill в Unix-подобных системах отправляет сигналы процессам. Вопреки названию, она не всегда "убивает" процесс, а лишь уведомляет его о событии.

Для бэкенд-разработчика наиболее важны следующие сигналы:

  • SIGTERM (сигнал 15): Сигнал корректного завершения. Это сигнал по умолчанию для kill. Приложение должно перехватить этот сигнал и выполнить процедуру "graceful shutdown": закрыть сетевые соединения, завершить обработку текущих запросов, сохранить данные и т.д.
    kill <PID>
    # или
    kill -15 <PID>
  • SIGKILL (сигнал 9): Сигнал принудительного завершения. Этот сигнал нельзя перехватить или проигнорировать. Ядро операционной системы немедленно завершает процесс. Используется как крайняя мера, когда процесс не отвечает на SIGTERM.
    kill -9 <PID>
  • SIGHUP (сигнал 1): Сигнал для перезагрузки конфигурации. Исторически означал разрыв соединения с терминалом, но сейчас часто используется для того, чтобы дать команду процессу перечитать свои конфигурационные файлы без перезапуска.

Реализация Graceful Shutdown в Go:

В Go для обработки сигналов используется пакет os/signal. Типичный паттерн выглядит так:

import (
    "context"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // ... запуск сервера или воркера ...

    // Создаем канал для получения сигналов ОС
    quit := make(chan os.Signal, 1)
    // Уведомляем о получении SIGINT (Ctrl+C) или SIGTERM
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // Блокируемся, пока не получим сигнал
    <-quit

    // ... начинаем процедуру graceful shutdown ...
    // Например, вызываем server.Shutdown(ctx)
}

Ответ 18+ 🔞

А, слушай, про эту команду kill в линуксе — тут вообще классика жанра, блядь! Все думают, что она сразу мочит процессы, как какой-нибудь терминатор, а на самом деле она, сука, просто вежливо стучится в дверь и говорит: «Э, дружок, может, сам завершишься? А то неудобно как-то».

Вот смотри, для нас, бэкендеров, три главных сигнала — это как три степени наказания, блядь.

SIGTERM (сигнал 15) — это как вежливый намёк, типа «братан, пора закругляться». По умолчанию kill шлёт именно его. Нормальное приложение должно его перехватить и сделать graceful shutdown: допилить текущие запросы, закрыть соединения с базой, не обосраться посреди транзакции — в общем, культурно выйти.

kill <PID>
# или, для ясности, блядь
kill -15 <PID>

SIGKILL (сигнал 9) — это уже пиздец, полный игнор любых договорённостей. Его нельзя перехватить, ядро ОС просто берёт и вырубает процесс нахуй, без предупреждения. Как молотком по башке. Используется, когда процесс совсем обнаглел и на SIGTERM отвечает молчанием, сука.

kill -9 <PID>

SIGHUP (сигнал 1) — а это хитрая жопа, ёпта. Раньше означал «тебе отключили терминал», а сейчас часто используют, чтобы сказать процессу: «Э, перечитай конфиги, я их обновил, а перезапускаться тебе лень».


А теперь, блядь, как это в Go делать, чтобы не быть мудаком и завершаться культурно?

В Go за это отвечает пакет os/signal. Смотри, типичный код выглядит так, будто мы ждём, когда нам скажут «всё, хватит»:

import (
    "context"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // ... тут ты запускаешь свой сервер, воркеры и прочую хуйню ...

    // Делаем канал для сигналов от ОС
    quit := make(chan os.Signal, 1)
    // Говорим: «Слушай, как только прилетит SIGINT (Ctrl+C) или SIGTERM — пиши в этот канал»
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // Сидим тут, ждём, блядь, как дурачки
    <-quit

    // Ага, пришёл сигнал! Теперь делаем graceful shutdown:
    // останавливаем сервер, закрываем соединения, сохраняем состояние...
    // Например, вызываем server.Shutdown(ctx)
}

Вот и вся магия, ёпта. Главное — не забыть этот graceful shutdown реализовать, а то потом будешь как тот Герасим, который Муму утопил — совесть замучает, блядь, когда прод упадёт в самый неподходящий момент.