@memba

В чем проблема отправки сигнала дочернему процессу?

Третий день не могу нагуглить. Все очень просто. Есть родительский процесс, который плодить дочерний процесс. Позже родительский процесс должен убить дочерний. Но не через KIll, а отправить сигнал на корректное завершение. Дочерний процесс ловит сигнал и завершает свою работу.

Простейшие примеры:

Родитель (parent.go)
package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"time"
)

func main() {
	// Запускаем дочерний процесс
	cmd := exec.Command("./child")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Start()

	if err != nil {
		fmt.Println("Child process not starting")
		os.Exit(1)
	}

	pid := cmd.Process.Pid

	fmt.Println("Child PID:", pid)

	// Отправляем сигнал о желании завершить процесс
	err = cmd.Process.Signal(syscall.SIGTERM)

	if err != nil {
		fmt.Println("Signal not sent:", err)
	}

	// Тикаем 10 секунд, в ожидании завершения процесса
	tick := time.NewTicker(1 * time.Second)
	i := 1
	for range tick.C {
		_, err := os.FindProcess(pid)
		if err != nil {
			break
		}
		if i == 10 {
			break
		}
		fmt.Println(i)
		i++
	}
	tick.Stop()

	// Убиваем процесс если он так и не получил сигнал!
	err = cmd.Process.Kill()

	if err != nil {
		fmt.Println("Child process not die")
	}

	fmt.Println("Exiting")
}


Дочерний (child.go)
package main

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

func main() {
	// Слушаем сигнал
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM)

	// Зависаем в ожидании сигнала
	<-c

	signal.Stop(c)

	fmt.Println("Child finish!")
}


Запустив ./parent он запускает ./child и сразу отправляет ему сигнал на завершение. Но ничего не происходит, процесс остается жить.

Я проверил на Windows и Linux.
Я проверил прослушку (signal.Notify) на Ctrl+C - она работает.
Я не получаю ошибки от cmd.Process.Signal(...)

UPD:

Если вызвать cmd.Process.Kill(), что тоже самое, что и cmd.Process.Signal(syscall.SIGKILL), то дочерний процесс умрет, только когда завершится родительский. Поймать сигнал в дочернем процессе не получается. Дочерний процесс умирает после родительского на этапе ожидания данных из канала (<-c). Бред какой-то.
  • Вопрос задан
  • 725 просмотров
Решения вопроса 1
Tyranron
@Tyranron
Извините за прямоту, но проблема в недостаточном знании Вами матчасти.
Что почитать по теме: Zombie process, а также Docker and the PID 1 zombie reaping problem.

А теперь ближе к Вашему примеру.
Cуть: и SITERM'ом, и SIGKILL'ом Вы child процесс успешно убиваете, после чего он превращается в зомби-процесс, то есть все еще висит в памяти под своим PID ожидая reaping (системного вызова wait() от parent процесса). После завершения parent процесса наш зомби-child усыновляется init процессом, который его и reap'ит.
Если запустить Ваши программы в том виде, котором Вы их запостили, и во время тиканья написать ps, то увидим следующую картину:
459 ttys000    0:00.03 -bash
498 ttys000    0:00.00 ./parent
499 ttys000    0:00.00 (child)
То, что child указан в скобках, как раз и означает, что процесс был остановлен (то есть он совершил системный вызов exit()) и ожидает reaping'а (когда результат exit()'а прочтут wait()'ом).
Если перед посылкой child'у добавитьtime.Sleep(10 * time.Second) и сразу после вывода child'овского PID'а набрать глянуть что творится в процессах, то увидим такую картину:
459 ttys000    0:00.04 -bash
510 ttys000    0:00.00 ./parent
511 ttys000    0:00.00 ./child
Сравните с тем как он отображается в процессах после того, как был убит.
А если мы после посылки сигнала child'у добавимcmd.Process.Wait(), то Ваш цикл ticker'а все равно, возможно, еще будет крутится, если в системе появился новый процесс с таким PID'ом, но если при этом заглянуть в ps, то увидим, что нашего child'а уже нет совсем:
459 ttys000    0:00.05 -bash
536 ttys000    0:00.01 ./parent

Кстати, о волшебной силе Process.Wait() пишется прямо в его доке.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы