Пакет · thread · глубокое погружение

Переиспользование PID и сигналы

Thread управляет процессом через хэндл proc_open; Signal — через «голый» PID. Именно эта разница объясняет, почему один безопасен для управления жизненным циклом, а другой требует осторожности.

Почему Thread надёжен

Thread никогда не шлёт сигнал «сырому» числу вслепую. isAlive(), join() и каждый метод сигнала проверяют хэндл proc_open через proc_get_status, который привязан именно к тому дочернему процессу, что породил родитель. Когда этот процесс завершается и его пожинают, хэндл знает об этом — нет окна, в котором хэндл мог бы указывать на другой процесс.

Почему «сырые» PID рискованны

Signal вызывает posix_kill($pid, ...) для числа, которое вы передали. PID — это конечный, переиспользуемый ресурс: как только процесс завершается и его пожинают, ОС вольна отдать его номер совершенно постороннему процессу. Поэтому PID, который вы захватили мгновение назад, к моменту отправки сигнала может принадлежать чему-то совсем другому — и ваш SIGTERM/SIGKILL попадёт не в ту цель.

Поэтому Signal задокументирован как «только свежие PID», и поэтому управление жизненным циклом должно идти через Thread.

Тонкость с зомби

Внутри isProcessRunning() есть более острый угол. Зомби (состояние Z) — это процесс, который уже завершился, но ещё не пожат родителем. Зомби всё ещё отвечает на posix_kill($pid, 0) — проба возвращает «существует», хотя процесс не может выполнять работу и должен считаться исчезнувшим.

Поэтому isProcessRunning() не ограничивается пробой. Убедившись, что PID отвечает, он проверяет состояние процесса и считает зомби не работающим:

  • Linux — читает /proc/<pid>/status и ищет ^State:\s+Z.
  • macOS / BSD — выполняет ps -o state= -p <pid> (с приведением $pid к int, так что инъекции в shell нет) и проверяет ведущий Z.
text
posix_kill(pid, 0)  →  false             → не работает
                  →  true, состояние Z → не работает (зомби)
                  →  true, живой       → работает

Практическое правило

Используйте методы Thread, чтобы останавливать и дожидаться работы, которую вы запустили. Берите Signal только с PID, полученным непосредственно перед вызовом — например, при пожинании известного, ещё живого дочернего процесса.

Связанное